@textbus/collaborate 3.0.0-alpha.3 → 3.0.0-alpha.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,732 @@
1
+ 'use strict';
2
+
3
+ var core = require('@textbus/core');
4
+ var di = require('@tanbo/di');
5
+ var stream = require('@tanbo/stream');
6
+ var yjs = require('yjs');
7
+
8
+ /******************************************************************************
9
+ Copyright (c) Microsoft Corporation.
10
+
11
+ Permission to use, copy, modify, and/or distribute this software for any
12
+ purpose with or without fee is hereby granted.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
15
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
16
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
17
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
18
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
19
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20
+ PERFORMANCE OF THIS SOFTWARE.
21
+ ***************************************************************************** */
22
+
23
+ function __decorate(decorators, target, key, desc) {
24
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
25
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
26
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
27
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
28
+ }
29
+
30
+ function __param(paramIndex, decorator) {
31
+ return function (target, key) { decorator(target, key, paramIndex); }
32
+ }
33
+
34
+ function __metadata(metadataKey, metadataValue) {
35
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
36
+ }
37
+
38
+ const collaborateErrorFn = core.makeError('Collaborate');
39
+ class ContentMap {
40
+ constructor() {
41
+ this.slotAndYTextMap = new WeakMap();
42
+ this.yTextAndSLotMap = new WeakMap();
43
+ }
44
+ set(key, value) {
45
+ if (key instanceof core.Slot) {
46
+ this.slotAndYTextMap.set(key, value);
47
+ this.yTextAndSLotMap.set(value, key);
48
+ }
49
+ else {
50
+ this.slotAndYTextMap.set(value, key);
51
+ this.yTextAndSLotMap.set(key, value);
52
+ }
53
+ }
54
+ get(key) {
55
+ if (key instanceof core.Slot) {
56
+ return this.slotAndYTextMap.get(key) || null;
57
+ }
58
+ return this.yTextAndSLotMap.get(key) || null;
59
+ }
60
+ delete(key) {
61
+ if (key instanceof core.Slot) {
62
+ const v = this.slotAndYTextMap.get(key);
63
+ this.slotAndYTextMap.delete(key);
64
+ if (v) {
65
+ this.yTextAndSLotMap.delete(v);
66
+ }
67
+ }
68
+ else {
69
+ const v = this.yTextAndSLotMap.get(key);
70
+ this.yTextAndSLotMap.delete(key);
71
+ if (v) {
72
+ this.slotAndYTextMap.delete(v);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ exports.Collaborate = class Collaborate {
78
+ constructor(stackSize, rootComponentRef, controller, scheduler, factory, selection, starter) {
79
+ this.stackSize = stackSize;
80
+ this.rootComponentRef = rootComponentRef;
81
+ this.controller = controller;
82
+ this.scheduler = scheduler;
83
+ this.factory = factory;
84
+ this.selection = selection;
85
+ this.starter = starter;
86
+ this.yDoc = new yjs.Doc();
87
+ this.backEvent = new stream.Subject();
88
+ this.forwardEvent = new stream.Subject();
89
+ this.changeEvent = new stream.Subject();
90
+ this.pushEvent = new stream.Subject();
91
+ this.manager = null;
92
+ this.subscriptions = [];
93
+ this.updateFromRemote = false;
94
+ this.contentSyncCaches = new WeakMap();
95
+ this.slotStateSyncCaches = new WeakMap();
96
+ this.slotsSyncCaches = new WeakMap();
97
+ this.componentStateSyncCaches = new WeakMap();
98
+ this.localChangesAppliedEvent = new stream.Subject();
99
+ this.selectionChangeEvent = new stream.Subject();
100
+ this.contentMap = new ContentMap();
101
+ this.updateRemoteActions = [];
102
+ this.noRecord = {};
103
+ this.onBack = this.backEvent.asObservable();
104
+ this.onForward = this.forwardEvent.asObservable();
105
+ this.onChange = this.changeEvent.asObservable();
106
+ this.onPush = this.pushEvent.asObservable();
107
+ this.onLocalChangesApplied = this.localChangesAppliedEvent.asObservable();
108
+ }
109
+ get canBack() {
110
+ var _a;
111
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canUndo()) || false;
112
+ }
113
+ get canForward() {
114
+ var _a;
115
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canRedo()) || false;
116
+ }
117
+ listen() {
118
+ const root = this.yDoc.getMap('RootComponent');
119
+ const rootComponent = this.rootComponentRef.component;
120
+ this.manager = new yjs.UndoManager(root, {
121
+ trackedOrigins: new Set([this.yDoc])
122
+ });
123
+ const cursorKey = 'cursor-position';
124
+ this.manager.on('stack-item-added', event => {
125
+ event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation());
126
+ if (this.manager.undoStack.length > this.stackSize) {
127
+ this.manager.undoStack.shift();
128
+ }
129
+ if (event.origin === this.yDoc) {
130
+ this.pushEvent.next();
131
+ }
132
+ this.changeEvent.next();
133
+ });
134
+ this.manager.on('stack-item-popped', event => {
135
+ const position = event.stackItem.meta.get(cursorKey);
136
+ if (position) {
137
+ this.restoreCursorLocation(position);
138
+ }
139
+ });
140
+ this.subscriptions.push(this.selection.onChange.subscribe(() => {
141
+ const paths = this.selection.getPaths();
142
+ this.selectionChangeEvent.next(paths);
143
+ }), this.scheduler.onDocChanged.pipe(stream.map(item => {
144
+ return item.filter(i => {
145
+ return i.from !== core.ChangeOrigin.Remote;
146
+ });
147
+ }), stream.filter(item => {
148
+ return item.length;
149
+ })).subscribe(() => {
150
+ const updates = [];
151
+ let update = null;
152
+ for (const item of this.updateRemoteActions) {
153
+ if (!update) {
154
+ update = {
155
+ record: item.record,
156
+ actions: []
157
+ };
158
+ updates.push(update);
159
+ }
160
+ if (update.record === item.record) {
161
+ update.actions.push(item.action);
162
+ }
163
+ else {
164
+ update = {
165
+ record: item.record,
166
+ actions: [item.action]
167
+ };
168
+ updates.push(update);
169
+ }
170
+ }
171
+ this.updateRemoteActions = [];
172
+ for (const item of updates) {
173
+ this.yDoc.transact(() => {
174
+ item.actions.forEach(fn => {
175
+ fn();
176
+ });
177
+ }, item.record ? this.yDoc : this.noRecord);
178
+ }
179
+ this.localChangesAppliedEvent.next();
180
+ }));
181
+ this.syncRootComponent(root, rootComponent);
182
+ }
183
+ back() {
184
+ var _a;
185
+ if (this.canBack) {
186
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.undo();
187
+ this.backEvent.next();
188
+ }
189
+ }
190
+ forward() {
191
+ var _a;
192
+ if (this.canForward) {
193
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.redo();
194
+ this.forwardEvent.next();
195
+ }
196
+ }
197
+ clear() {
198
+ var _a;
199
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.clear();
200
+ this.changeEvent.next();
201
+ }
202
+ destroy() {
203
+ var _a;
204
+ this.subscriptions.forEach(i => i.unsubscribe());
205
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.destroy();
206
+ }
207
+ syncRootComponent(root, rootComponent) {
208
+ let slots = root.get('slots');
209
+ if (!slots) {
210
+ slots = new yjs.Array();
211
+ rootComponent.slots.toArray().forEach(i => {
212
+ const sharedSlot = this.createSharedSlotBySlot(i);
213
+ slots.push([sharedSlot]);
214
+ });
215
+ this.yDoc.transact(() => {
216
+ root.set('state', rootComponent.state);
217
+ root.set('slots', slots);
218
+ });
219
+ }
220
+ else if (slots.length === 0) {
221
+ rootComponent.updateState(() => {
222
+ return root.get('state');
223
+ });
224
+ this.yDoc.transact(() => {
225
+ rootComponent.slots.toArray().forEach(i => {
226
+ const sharedSlot = this.createSharedSlotBySlot(i);
227
+ slots.push([sharedSlot]);
228
+ });
229
+ });
230
+ }
231
+ else {
232
+ rootComponent.updateState(() => {
233
+ return root.get('state');
234
+ });
235
+ rootComponent.slots.clean();
236
+ slots.forEach(sharedSlot => {
237
+ const slot = this.createSlotBySharedSlot(sharedSlot);
238
+ this.syncSlotContent(sharedSlot.get('content'), slot);
239
+ this.syncSlotState(sharedSlot, slot);
240
+ rootComponent.slots.insert(slot);
241
+ });
242
+ }
243
+ this.syncComponentState(root, rootComponent);
244
+ this.syncComponentSlots(slots, rootComponent);
245
+ }
246
+ restoreCursorLocation(position) {
247
+ const anchorPosition = yjs.createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc);
248
+ const focusPosition = yjs.createAbsolutePositionFromRelativePosition(position.focus, this.yDoc);
249
+ if (anchorPosition && focusPosition) {
250
+ const focusSlot = this.contentMap.get(focusPosition.type);
251
+ const anchorSlot = this.contentMap.get(anchorPosition.type);
252
+ if (focusSlot && anchorSlot) {
253
+ this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index);
254
+ return;
255
+ }
256
+ }
257
+ this.selection.unSelect();
258
+ }
259
+ getRelativeCursorLocation() {
260
+ const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection;
261
+ if (anchorSlot) {
262
+ const anchorYText = this.contentMap.get(anchorSlot);
263
+ if (anchorYText) {
264
+ const anchorPosition = yjs.createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
265
+ if (focusSlot) {
266
+ const focusYText = this.contentMap.get(focusSlot);
267
+ if (focusYText) {
268
+ const focusPosition = yjs.createRelativePositionFromTypeIndex(focusYText, focusOffset);
269
+ return {
270
+ focus: focusPosition,
271
+ anchor: anchorPosition
272
+ };
273
+ }
274
+ }
275
+ }
276
+ }
277
+ return null;
278
+ }
279
+ syncSlotContent(content, slot) {
280
+ this.contentMap.set(slot, content);
281
+ const syncRemote = (ev, tr) => {
282
+ this.runRemoteUpdate(tr, () => {
283
+ slot.retain(0);
284
+ ev.keysChanged.forEach(key => {
285
+ const change = ev.keys.get(key);
286
+ if (!change) {
287
+ return;
288
+ }
289
+ const updateType = change.action;
290
+ if (updateType === 'update' || updateType === 'add') {
291
+ const attribute = this.factory.getAttribute(key);
292
+ if (attribute) {
293
+ slot.setAttribute(attribute, content.getAttribute(key));
294
+ }
295
+ }
296
+ else if (updateType === 'delete') {
297
+ const attribute = this.factory.getAttribute(key);
298
+ if (attribute) {
299
+ slot.removeAttribute(attribute);
300
+ }
301
+ }
302
+ });
303
+ ev.delta.forEach(action => {
304
+ if (Reflect.has(action, 'retain')) {
305
+ if (action.attributes) {
306
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
307
+ if (formats.length) {
308
+ slot.retain(action.retain, formats);
309
+ }
310
+ slot.retain(slot.index + action.retain);
311
+ }
312
+ else {
313
+ slot.retain(action.retain);
314
+ }
315
+ }
316
+ else if (action.insert) {
317
+ const index = slot.index;
318
+ let length = 1;
319
+ if (typeof action.insert === 'string') {
320
+ length = action.insert.length;
321
+ slot.insert(action.insert, remoteFormatsToLocal(this.factory, action.attributes));
322
+ }
323
+ else {
324
+ const sharedComponent = action.insert;
325
+ const component = this.createComponentBySharedComponent(sharedComponent);
326
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
327
+ this.syncComponentState(sharedComponent, component);
328
+ slot.insert(component);
329
+ }
330
+ if (this.selection.isSelected) {
331
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
332
+ this.selection.setAnchor(slot, this.selection.anchorOffset + length);
333
+ }
334
+ if (slot === this.selection.focusSlot && this.selection.focusOffset > index) {
335
+ this.selection.setFocus(slot, this.selection.focusOffset + length);
336
+ }
337
+ }
338
+ }
339
+ else if (action.delete) {
340
+ const index = slot.index;
341
+ slot.retain(slot.index);
342
+ slot.delete(action.delete);
343
+ if (this.selection.isSelected) {
344
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
345
+ this.selection.setAnchor(slot, this.selection.startOffset - action.delete);
346
+ }
347
+ if (slot === this.selection.focusSlot && this.selection.focusOffset >= index) {
348
+ this.selection.setFocus(slot, this.selection.focusOffset - action.delete);
349
+ }
350
+ }
351
+ }
352
+ });
353
+ });
354
+ };
355
+ content.observe(syncRemote);
356
+ const sub = slot.onContentChange.subscribe(actions => {
357
+ this.runLocalUpdate(() => {
358
+ var _a;
359
+ let offset = 0;
360
+ let length = 0;
361
+ for (const action of actions) {
362
+ if (action.type === 'retain') {
363
+ const formats = action.formats;
364
+ if (formats) {
365
+ const keys = Object.keys(formats);
366
+ let length = keys.length;
367
+ keys.forEach(key => {
368
+ const formatter = this.factory.getFormatter(key);
369
+ if (!formatter) {
370
+ length--;
371
+ Reflect.deleteProperty(formats, key);
372
+ }
373
+ });
374
+ if (length) {
375
+ content.format(offset, action.offset, formats);
376
+ }
377
+ }
378
+ else {
379
+ offset = action.offset;
380
+ }
381
+ }
382
+ else if (action.type === 'insert') {
383
+ const delta = content.toDelta();
384
+ const isEmpty = delta.length === 1 && delta[0].insert === core.Slot.emptyPlaceholder;
385
+ if (typeof action.content === 'string') {
386
+ length = action.content.length;
387
+ content.insert(offset, action.content, action.formats || {});
388
+ }
389
+ else {
390
+ length = 1;
391
+ const sharedComponent = this.createSharedComponentByComponent(action.ref);
392
+ content.insertEmbed(offset, sharedComponent, action.formats || {});
393
+ }
394
+ if (isEmpty && offset === 0) {
395
+ content.delete(content.length - 1, 1);
396
+ }
397
+ offset += length;
398
+ }
399
+ else if (action.type === 'delete') {
400
+ const delta = content.toDelta();
401
+ if (content.length) {
402
+ content.delete(offset, action.count);
403
+ }
404
+ if (content.length === 0) {
405
+ content.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
406
+ }
407
+ }
408
+ else if (action.type === 'attrSet') {
409
+ content.setAttribute(action.name, action.value);
410
+ }
411
+ else if (action.type === 'attrRemove') {
412
+ content.removeAttribute(action.name);
413
+ }
414
+ }
415
+ });
416
+ });
417
+ sub.add(slot.onChildComponentRemove.subscribe(components => {
418
+ components.forEach(c => {
419
+ this.cleanSubscriptionsByComponent(c);
420
+ });
421
+ }));
422
+ this.contentSyncCaches.set(slot, () => {
423
+ content.unobserve(syncRemote);
424
+ sub.unsubscribe();
425
+ });
426
+ }
427
+ syncSlotState(remoteSlot, slot) {
428
+ const syncRemote = (ev, tr) => {
429
+ this.runRemoteUpdate(tr, () => {
430
+ ev.keysChanged.forEach(key => {
431
+ if (key === 'state') {
432
+ const state = ev.target.get('state');
433
+ slot.updateState(draft => {
434
+ if (typeof draft === 'object' && draft !== null) {
435
+ Object.assign(draft, state);
436
+ }
437
+ else {
438
+ return state;
439
+ }
440
+ });
441
+ }
442
+ });
443
+ });
444
+ };
445
+ remoteSlot.observe(syncRemote);
446
+ const sub = slot.onStateChange.subscribe(change => {
447
+ this.runLocalUpdate(() => {
448
+ remoteSlot.set('state', change.newState);
449
+ }, change.record);
450
+ });
451
+ this.slotStateSyncCaches.set(slot, () => {
452
+ remoteSlot.unobserve(syncRemote);
453
+ sub.unsubscribe();
454
+ });
455
+ }
456
+ syncComponentSlots(remoteSlots, component) {
457
+ const slots = component.slots;
458
+ const syncRemote = (ev, tr) => {
459
+ this.runRemoteUpdate(tr, () => {
460
+ let index = 0;
461
+ slots.retain(index);
462
+ ev.delta.forEach(action => {
463
+ if (Reflect.has(action, 'retain')) {
464
+ index += action.retain;
465
+ slots.retain(index);
466
+ }
467
+ else if (action.insert) {
468
+ action.insert.forEach(item => {
469
+ const slot = this.createSlotBySharedSlot(item);
470
+ slots.insert(slot);
471
+ this.syncSlotContent(item.get('content'), slot);
472
+ this.syncSlotState(item, slot);
473
+ index++;
474
+ });
475
+ }
476
+ else if (action.delete) {
477
+ slots.retain(index);
478
+ slots.delete(action.delete);
479
+ }
480
+ });
481
+ });
482
+ };
483
+ remoteSlots.observe(syncRemote);
484
+ const sub = slots.onChange.subscribe(operations => {
485
+ this.runLocalUpdate(() => {
486
+ const applyActions = operations.apply;
487
+ let index;
488
+ applyActions.forEach(action => {
489
+ if (action.type === 'retain') {
490
+ index = action.offset;
491
+ }
492
+ else if (action.type === 'insertSlot') {
493
+ const sharedSlot = this.createSharedSlotBySlot(action.ref);
494
+ remoteSlots.insert(index, [sharedSlot]);
495
+ index++;
496
+ }
497
+ else if (action.type === 'delete') {
498
+ remoteSlots.delete(index, action.count);
499
+ }
500
+ });
501
+ });
502
+ });
503
+ sub.add(slots.onChildSlotRemove.subscribe(slots => {
504
+ slots.forEach(slot => {
505
+ this.cleanSubscriptionsBySlot(slot);
506
+ });
507
+ }));
508
+ this.slotsSyncCaches.set(component, () => {
509
+ remoteSlots.unobserve(syncRemote);
510
+ sub.unsubscribe();
511
+ });
512
+ }
513
+ syncComponentState(remoteComponent, component) {
514
+ const syncRemote = (ev, tr) => {
515
+ this.runRemoteUpdate(tr, () => {
516
+ ev.keysChanged.forEach(key => {
517
+ if (key === 'state') {
518
+ const state = ev.target.get('state');
519
+ component.updateState(draft => {
520
+ if (typeof draft === 'object' && draft !== null) {
521
+ Object.assign(draft, state);
522
+ }
523
+ else {
524
+ return state;
525
+ }
526
+ });
527
+ }
528
+ });
529
+ });
530
+ };
531
+ remoteComponent.observe(syncRemote);
532
+ const sub = component.onStateChange.subscribe(change => {
533
+ this.runLocalUpdate(() => {
534
+ remoteComponent.set('state', change.newState);
535
+ }, change.record);
536
+ });
537
+ this.componentStateSyncCaches.set(component, () => {
538
+ remoteComponent.unobserve(syncRemote);
539
+ sub.unsubscribe();
540
+ });
541
+ }
542
+ runLocalUpdate(fn, record = true) {
543
+ if (this.updateFromRemote || this.controller.readonly) {
544
+ return;
545
+ }
546
+ this.updateRemoteActions.push({
547
+ record,
548
+ action: fn
549
+ });
550
+ }
551
+ runRemoteUpdate(tr, fn) {
552
+ if (tr.origin === this.yDoc) {
553
+ return;
554
+ }
555
+ this.updateFromRemote = true;
556
+ if (tr.origin === this.manager) {
557
+ this.scheduler.historyApplyTransact(fn);
558
+ }
559
+ else {
560
+ this.scheduler.remoteUpdateTransact(fn);
561
+ }
562
+ this.updateFromRemote = false;
563
+ }
564
+ createSharedComponentByComponent(component) {
565
+ const sharedComponent = new yjs.Map();
566
+ sharedComponent.set('state', component.state);
567
+ sharedComponent.set('name', component.name);
568
+ const sharedSlots = new yjs.Array();
569
+ sharedComponent.set('slots', sharedSlots);
570
+ component.slots.toArray().forEach(slot => {
571
+ const sharedSlot = this.createSharedSlotBySlot(slot);
572
+ sharedSlots.push([sharedSlot]);
573
+ });
574
+ this.syncComponentSlots(sharedSlots, component);
575
+ this.syncComponentState(sharedComponent, component);
576
+ return sharedComponent;
577
+ }
578
+ createSharedSlotBySlot(slot) {
579
+ const sharedSlot = new yjs.Map();
580
+ sharedSlot.set('schema', slot.schema);
581
+ sharedSlot.set('state', slot.state);
582
+ const sharedContent = new yjs.Text();
583
+ sharedSlot.set('content', sharedContent);
584
+ let offset = 0;
585
+ slot.toDelta().forEach(i => {
586
+ let formats = {};
587
+ if (i.formats) {
588
+ i.formats.forEach(item => {
589
+ formats[item[0].name] = item[1];
590
+ });
591
+ }
592
+ else {
593
+ formats = null;
594
+ }
595
+ if (typeof i.insert === 'string') {
596
+ sharedContent.insert(offset, i.insert, formats);
597
+ }
598
+ else {
599
+ const sharedComponent = this.createSharedComponentByComponent(i.insert);
600
+ sharedContent.insertEmbed(offset, sharedComponent, formats);
601
+ }
602
+ offset += i.insert.length;
603
+ });
604
+ slot.getAttributes().forEach(item => {
605
+ sharedContent.setAttribute(item[0].name, item[1]);
606
+ });
607
+ this.syncSlotContent(sharedContent, slot);
608
+ this.syncSlotState(sharedSlot, slot);
609
+ return sharedSlot;
610
+ }
611
+ createComponentBySharedComponent(yMap) {
612
+ const sharedSlots = yMap.get('slots');
613
+ const slots = [];
614
+ sharedSlots.forEach(sharedSlot => {
615
+ const slot = this.createSlotBySharedSlot(sharedSlot);
616
+ slots.push(slot);
617
+ });
618
+ const name = yMap.get('name');
619
+ const state = yMap.get('state');
620
+ const instance = this.factory.createComponentByData(name, {
621
+ state,
622
+ slots
623
+ });
624
+ if (instance) {
625
+ instance.slots.toArray().forEach((slot, index) => {
626
+ let sharedSlot = sharedSlots.get(index);
627
+ if (!sharedSlot) {
628
+ sharedSlot = this.createSharedSlotBySlot(slot);
629
+ sharedSlots.push([sharedSlot]);
630
+ }
631
+ this.syncSlotState(sharedSlot, slot);
632
+ this.syncSlotContent(sharedSlot.get('content'), slot);
633
+ });
634
+ return instance;
635
+ }
636
+ throw collaborateErrorFn(`cannot find component factory \`${name}\`.`);
637
+ }
638
+ createSlotBySharedSlot(sharedSlot) {
639
+ const content = sharedSlot.get('content');
640
+ const delta = content.toDelta();
641
+ const slot = this.factory.createSlot({
642
+ schema: sharedSlot.get('schema'),
643
+ state: sharedSlot.get('state'),
644
+ attributes: {},
645
+ formats: {},
646
+ content: []
647
+ });
648
+ const attrs = content.getAttributes();
649
+ Object.keys(attrs).forEach(key => {
650
+ const attribute = this.factory.getAttribute(key);
651
+ if (attribute) {
652
+ slot.setAttribute(attribute, attrs[key]);
653
+ }
654
+ });
655
+ for (const action of delta) {
656
+ if (action.insert) {
657
+ if (typeof action.insert === 'string') {
658
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
659
+ slot.insert(action.insert, formats);
660
+ }
661
+ else {
662
+ const sharedComponent = action.insert;
663
+ const component = this.createComponentBySharedComponent(sharedComponent);
664
+ slot.insert(component, remoteFormatsToLocal(this.factory, action.attributes));
665
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
666
+ this.syncComponentState(sharedComponent, component);
667
+ }
668
+ }
669
+ else {
670
+ throw collaborateErrorFn('unexpected delta action.');
671
+ }
672
+ }
673
+ return slot;
674
+ }
675
+ cleanSubscriptionsBySlot(slot) {
676
+ this.contentMap.delete(slot);
677
+ [this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
678
+ if (fn) {
679
+ fn();
680
+ }
681
+ });
682
+ slot.sliceContent().forEach(i => {
683
+ if (typeof i !== 'string') {
684
+ this.cleanSubscriptionsByComponent(i);
685
+ }
686
+ });
687
+ }
688
+ cleanSubscriptionsByComponent(component) {
689
+ [this.slotsSyncCaches.get(component), this.componentStateSyncCaches.get(component)].forEach(fn => {
690
+ if (fn) {
691
+ fn();
692
+ }
693
+ });
694
+ component.slots.toArray().forEach(slot => {
695
+ this.cleanSubscriptionsBySlot(slot);
696
+ });
697
+ }
698
+ };
699
+ exports.Collaborate = __decorate([
700
+ di.Injectable(),
701
+ __param(0, di.Inject(core.HISTORY_STACK_SIZE)),
702
+ __metadata("design:paramtypes", [Number, core.RootComponentRef,
703
+ core.Controller,
704
+ core.Scheduler,
705
+ core.Factory,
706
+ core.Selection,
707
+ core.Starter])
708
+ ], exports.Collaborate);
709
+ function remoteFormatsToLocal(factory, attrs) {
710
+ const formats = [];
711
+ if (attrs) {
712
+ Object.keys(attrs).forEach(key => {
713
+ const formatter = factory.getFormatter(key);
714
+ if (formatter) {
715
+ formats.push([formatter, attrs[key]]);
716
+ }
717
+ });
718
+ }
719
+ return formats;
720
+ }
721
+
722
+ const collaborateModule = {
723
+ providers: [
724
+ exports.Collaborate,
725
+ {
726
+ provide: core.History,
727
+ useExisting: exports.Collaborate
728
+ }
729
+ ]
730
+ };
731
+
732
+ exports.collaborateModule = collaborateModule;