@textbus/collaborate 3.0.0-alpha.15 → 3.0.0-alpha.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1026 @@
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
+ var browser = require('@textbus/browser');
8
+
9
+ /*! *****************************************************************************
10
+ Copyright (c) Microsoft Corporation.
11
+
12
+ Permission to use, copy, modify, and/or distribute this software for any
13
+ purpose with or without fee is hereby granted.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
16
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
17
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
18
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
19
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
20
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21
+ PERFORMANCE OF THIS SOFTWARE.
22
+ ***************************************************************************** */
23
+
24
+ function __decorate(decorators, target, key, desc) {
25
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
26
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
27
+ 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;
28
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
29
+ }
30
+
31
+ function __param(paramIndex, decorator) {
32
+ return function (target, key) { decorator(target, key, paramIndex); }
33
+ }
34
+
35
+ function __metadata(metadataKey, metadataValue) {
36
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
37
+ }
38
+
39
+ class CollaborateSelectionAwarenessDelegate {
40
+ }
41
+ exports.CollaborateCursor = class CollaborateCursor {
42
+ constructor(container, awarenessDelegate, nativeSelection, scheduler, selection) {
43
+ this.container = container;
44
+ this.awarenessDelegate = awarenessDelegate;
45
+ this.nativeSelection = nativeSelection;
46
+ this.scheduler = scheduler;
47
+ this.selection = selection;
48
+ this.host = browser.createElement('div', {
49
+ styles: {
50
+ position: 'absolute',
51
+ left: 0,
52
+ top: 0,
53
+ width: '100%',
54
+ height: '100%',
55
+ pointerEvents: 'none',
56
+ zIndex: 1
57
+ }
58
+ });
59
+ this.canvasContainer = browser.createElement('div', {
60
+ styles: {
61
+ position: 'absolute',
62
+ left: 0,
63
+ top: 0,
64
+ width: '100%',
65
+ height: '100%',
66
+ overflow: 'hidden'
67
+ }
68
+ });
69
+ this.canvas = browser.createElement('canvas', {
70
+ styles: {
71
+ position: 'absolute',
72
+ opacity: 0.5,
73
+ left: 0,
74
+ top: 0,
75
+ width: '100%',
76
+ height: document.documentElement.clientHeight + 'px',
77
+ pointerEvents: 'none',
78
+ }
79
+ });
80
+ this.context = this.canvas.getContext('2d');
81
+ this.tooltips = browser.createElement('div', {
82
+ styles: {
83
+ position: 'absolute',
84
+ left: 0,
85
+ top: 0,
86
+ width: '100%',
87
+ height: '100%',
88
+ pointerEvents: 'none',
89
+ fontSize: '12px',
90
+ zIndex: 10
91
+ }
92
+ });
93
+ this.onRectsChange = new stream.Subject();
94
+ this.subscription = new stream.Subscription();
95
+ this.currentSelection = [];
96
+ this.canvasContainer.append(this.canvas);
97
+ this.host.append(this.canvasContainer, this.tooltips);
98
+ container.prepend(this.host);
99
+ this.subscription.add(this.onRectsChange.subscribe(rects => {
100
+ for (const rect of rects) {
101
+ this.context.fillStyle = rect.color;
102
+ this.context.beginPath();
103
+ this.context.rect(rect.left, rect.top, rect.width, rect.height);
104
+ this.context.fill();
105
+ this.context.closePath();
106
+ }
107
+ }), stream.fromEvent(window, 'resize').subscribe(() => {
108
+ this.canvas.style.height = document.documentElement.clientHeight + 'px';
109
+ this.refresh();
110
+ }), this.scheduler.onDocChanged.subscribe(() => {
111
+ this.refresh();
112
+ }));
113
+ }
114
+ refresh() {
115
+ this.draw(this.currentSelection);
116
+ }
117
+ destroy() {
118
+ this.subscription.unsubscribe();
119
+ }
120
+ draw(paths) {
121
+ this.currentSelection = paths;
122
+ const containerRect = this.container.getBoundingClientRect();
123
+ this.canvas.style.top = containerRect.top * -1 + 'px';
124
+ this.canvas.width = this.canvas.offsetWidth;
125
+ this.canvas.height = this.canvas.offsetHeight;
126
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
127
+ const users = [];
128
+ paths.filter(i => {
129
+ return i.paths.anchor.length && i.paths.focus.length;
130
+ }).forEach(item => {
131
+ const anchorPaths = [...item.paths.anchor];
132
+ const focusPaths = [...item.paths.focus];
133
+ const anchorOffset = anchorPaths.pop();
134
+ const anchorSlot = this.selection.findSlotByPaths(anchorPaths);
135
+ const focusOffset = focusPaths.pop();
136
+ const focusSlot = this.selection.findSlotByPaths(focusPaths);
137
+ if (!anchorSlot || !focusSlot) {
138
+ return;
139
+ }
140
+ const { focus, anchor } = this.nativeSelection.getPositionByRange({
141
+ focusOffset,
142
+ anchorOffset,
143
+ focusSlot,
144
+ anchorSlot
145
+ });
146
+ if (!focus || !anchor) {
147
+ return;
148
+ }
149
+ const nativeRange = document.createRange();
150
+ nativeRange.setStart(anchor.node, anchor.offset);
151
+ nativeRange.setEnd(focus.node, focus.offset);
152
+ if ((anchor.node !== focus.node || anchor.offset !== focus.offset) && nativeRange.collapsed) {
153
+ nativeRange.setStart(focus.node, focus.offset);
154
+ nativeRange.setEnd(anchor.node, anchor.offset);
155
+ }
156
+ let rects = false;
157
+ if (this.awarenessDelegate) {
158
+ rects = this.awarenessDelegate.getRects({
159
+ focusOffset,
160
+ anchorOffset,
161
+ focusSlot,
162
+ anchorSlot
163
+ }, nativeRange);
164
+ }
165
+ if (!rects) {
166
+ rects = nativeRange.getClientRects();
167
+ }
168
+ const selectionRects = [];
169
+ for (let i = rects.length - 1; i >= 0; i--) {
170
+ const rect = rects[i];
171
+ selectionRects.push({
172
+ id: item.id,
173
+ color: item.color,
174
+ username: item.username,
175
+ left: rect.left - containerRect.left,
176
+ top: rect.top,
177
+ width: rect.width,
178
+ height: rect.height,
179
+ });
180
+ }
181
+ this.onRectsChange.next(selectionRects);
182
+ const cursorRange = nativeRange.cloneRange();
183
+ cursorRange.setStart(focus.node, focus.offset);
184
+ cursorRange.collapse(true);
185
+ const cursorRect = browser.getLayoutRectByRange(cursorRange);
186
+ const rect = {
187
+ id: item.id,
188
+ username: item.username,
189
+ color: item.color,
190
+ left: cursorRect.left - containerRect.left,
191
+ top: cursorRect.top - containerRect.top,
192
+ width: 2,
193
+ height: cursorRect.height
194
+ };
195
+ if (rect.left < 0 || rect.top < 0 || rect.left > containerRect.width) {
196
+ return;
197
+ }
198
+ users.push(rect);
199
+ });
200
+ this.drawUserCursor(users);
201
+ }
202
+ drawUserCursor(rects) {
203
+ for (let i = 0; i < rects.length; i++) {
204
+ const rect = rects[i];
205
+ const { cursor, userTip, anchor } = this.getUserCursor(i);
206
+ Object.assign(cursor.style, {
207
+ left: rect.left + 'px',
208
+ top: rect.top + 'px',
209
+ width: rect.width + 'px',
210
+ height: rect.height + 'px',
211
+ background: rect.color,
212
+ display: 'block'
213
+ });
214
+ anchor.style.background = rect.color;
215
+ userTip.innerText = rect.username;
216
+ userTip.style.background = rect.color;
217
+ }
218
+ for (let i = rects.length; i < this.tooltips.children.length; i++) {
219
+ this.tooltips.removeChild(this.tooltips.children[i]);
220
+ }
221
+ }
222
+ getUserCursor(index) {
223
+ let child = this.tooltips.children[index];
224
+ if (child) {
225
+ const anchor = child.children[0];
226
+ return {
227
+ cursor: child,
228
+ anchor,
229
+ userTip: anchor.children[0]
230
+ };
231
+ }
232
+ const userTip = browser.createElement('span', {
233
+ styles: {
234
+ position: 'absolute',
235
+ display: 'none',
236
+ left: '50%',
237
+ transform: 'translateX(-50%)',
238
+ marginBottom: '2px',
239
+ bottom: '100%',
240
+ whiteSpace: 'nowrap',
241
+ color: '#fff',
242
+ boxShadow: '0 1px 2px rgba(0,0,0,.1)',
243
+ borderRadius: '3px',
244
+ padding: '3px 5px',
245
+ pointerEvents: 'none',
246
+ }
247
+ });
248
+ const anchor = browser.createElement('span', {
249
+ styles: {
250
+ position: 'absolute',
251
+ top: '-2px',
252
+ left: '-2px',
253
+ width: '6px',
254
+ height: '6px',
255
+ pointerEvents: 'auto',
256
+ pointer: 'cursor',
257
+ },
258
+ children: [userTip],
259
+ on: {
260
+ mouseenter() {
261
+ userTip.style.display = 'block';
262
+ },
263
+ mouseleave() {
264
+ userTip.style.display = 'none';
265
+ }
266
+ }
267
+ });
268
+ child = browser.createElement('span', {
269
+ styles: {
270
+ position: 'absolute',
271
+ },
272
+ children: [
273
+ anchor
274
+ ]
275
+ });
276
+ this.tooltips.append(child);
277
+ return {
278
+ cursor: child,
279
+ anchor,
280
+ userTip
281
+ };
282
+ }
283
+ };
284
+ exports.CollaborateCursor = __decorate([
285
+ di.Injectable(),
286
+ __param(0, di.Inject(browser.VIEW_CONTAINER)),
287
+ __param(1, di.Optional()),
288
+ __metadata("design:paramtypes", [HTMLElement,
289
+ CollaborateSelectionAwarenessDelegate,
290
+ browser.SelectionBridge,
291
+ core.Scheduler,
292
+ core.Selection])
293
+ ], exports.CollaborateCursor);
294
+
295
+ function createUnknownComponent(factoryName, canInsertInlineComponent) {
296
+ const unknownComponent = core.defineComponent({
297
+ type: canInsertInlineComponent ? core.ContentType.InlineComponent : core.ContentType.BlockComponent,
298
+ name: 'UnknownComponent',
299
+ setup() {
300
+ console.error(`cannot find component factory \`${factoryName}\`.`);
301
+ return {
302
+ render() {
303
+ return core.VElement.createElement('textbus-unknown-component', {
304
+ style: {
305
+ display: canInsertInlineComponent ? 'inline' : 'block',
306
+ color: '#f00'
307
+ }
308
+ }, unknownComponent.name);
309
+ }
310
+ };
311
+ }
312
+ });
313
+ return unknownComponent;
314
+ }
315
+
316
+ const collaborateErrorFn = core.makeError('Collaborate');
317
+ class ContentMap {
318
+ constructor() {
319
+ this.slotAndYTextMap = new WeakMap();
320
+ this.yTextAndSLotMap = new WeakMap();
321
+ }
322
+ set(key, value) {
323
+ if (key instanceof core.Slot) {
324
+ this.slotAndYTextMap.set(key, value);
325
+ this.yTextAndSLotMap.set(value, key);
326
+ }
327
+ else {
328
+ this.slotAndYTextMap.set(value, key);
329
+ this.yTextAndSLotMap.set(key, value);
330
+ }
331
+ }
332
+ get(key) {
333
+ if (key instanceof core.Slot) {
334
+ return this.slotAndYTextMap.get(key) || null;
335
+ }
336
+ return this.yTextAndSLotMap.get(key) || null;
337
+ }
338
+ delete(key) {
339
+ if (key instanceof core.Slot) {
340
+ const v = this.slotAndYTextMap.get(key);
341
+ this.slotAndYTextMap.delete(key);
342
+ if (v) {
343
+ this.yTextAndSLotMap.delete(v);
344
+ }
345
+ }
346
+ else {
347
+ const v = this.yTextAndSLotMap.get(key);
348
+ this.yTextAndSLotMap.delete(key);
349
+ if (v) {
350
+ this.slotAndYTextMap.delete(v);
351
+ }
352
+ }
353
+ }
354
+ }
355
+ exports.Collaborate = class Collaborate {
356
+ constructor(stackSize, rootComponentRef, collaborateCursor, controller, scheduler, factory, selection, starter) {
357
+ this.stackSize = stackSize;
358
+ this.rootComponentRef = rootComponentRef;
359
+ this.collaborateCursor = collaborateCursor;
360
+ this.controller = controller;
361
+ this.scheduler = scheduler;
362
+ this.factory = factory;
363
+ this.selection = selection;
364
+ this.starter = starter;
365
+ this.yDoc = new yjs.Doc();
366
+ this.backEvent = new stream.Subject();
367
+ this.forwardEvent = new stream.Subject();
368
+ this.changeEvent = new stream.Subject();
369
+ this.pushEvent = new stream.Subject();
370
+ this.manager = null;
371
+ this.subscriptions = [];
372
+ this.updateFromRemote = false;
373
+ this.contentSyncCaches = new WeakMap();
374
+ this.slotStateSyncCaches = new WeakMap();
375
+ this.slotsSyncCaches = new WeakMap();
376
+ this.componentStateSyncCaches = new WeakMap();
377
+ this.selectionChangeEvent = new stream.Subject();
378
+ this.contentMap = new ContentMap();
379
+ this.updateRemoteActions = [];
380
+ this.noRecord = {};
381
+ this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(stream.delay());
382
+ this.onBack = this.backEvent.asObservable();
383
+ this.onForward = this.forwardEvent.asObservable();
384
+ this.onChange = this.changeEvent.asObservable();
385
+ this.onPush = this.pushEvent.asObservable();
386
+ }
387
+ get canBack() {
388
+ var _a;
389
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canUndo()) || false;
390
+ }
391
+ get canForward() {
392
+ var _a;
393
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canRedo()) || false;
394
+ }
395
+ listen() {
396
+ const root = this.yDoc.getMap('RootComponent');
397
+ const rootComponent = this.rootComponentRef.component;
398
+ this.manager = new yjs.UndoManager(root, {
399
+ trackedOrigins: new Set([this.yDoc])
400
+ });
401
+ const cursorKey = 'cursor-position';
402
+ this.manager.on('stack-item-added', event => {
403
+ event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation());
404
+ if (this.manager.undoStack.length > this.stackSize) {
405
+ this.manager.undoStack.shift();
406
+ }
407
+ if (event.origin === this.yDoc) {
408
+ this.pushEvent.next();
409
+ }
410
+ this.changeEvent.next();
411
+ });
412
+ this.manager.on('stack-item-popped', event => {
413
+ const position = event.stackItem.meta.get(cursorKey);
414
+ if (position) {
415
+ this.restoreCursorLocation(position);
416
+ }
417
+ });
418
+ this.subscriptions.push(this.selection.onChange.subscribe(() => {
419
+ const paths = this.selection.getPaths();
420
+ this.selectionChangeEvent.next(paths);
421
+ }), this.scheduler.onDocChanged.pipe(stream.map(item => {
422
+ return item.filter(i => {
423
+ return i.from !== core.ChangeOrigin.Remote;
424
+ });
425
+ }), stream.filter(item => {
426
+ return item.length;
427
+ })).subscribe(() => {
428
+ const updates = [];
429
+ let update = null;
430
+ for (const item of this.updateRemoteActions) {
431
+ if (!update) {
432
+ update = {
433
+ record: item.record,
434
+ actions: []
435
+ };
436
+ updates.push(update);
437
+ }
438
+ if (update.record === item.record) {
439
+ update.actions.push(item.action);
440
+ }
441
+ else {
442
+ update = {
443
+ record: item.record,
444
+ actions: [item.action]
445
+ };
446
+ updates.push(update);
447
+ }
448
+ }
449
+ this.updateRemoteActions = [];
450
+ for (const item of updates) {
451
+ this.yDoc.transact(() => {
452
+ item.actions.forEach(fn => {
453
+ fn();
454
+ });
455
+ }, item.record ? this.yDoc : this.noRecord);
456
+ }
457
+ }));
458
+ this.syncRootComponent(root, rootComponent);
459
+ }
460
+ updateRemoteSelection(paths) {
461
+ this.collaborateCursor.draw(paths);
462
+ }
463
+ back() {
464
+ var _a;
465
+ if (this.canBack) {
466
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.undo();
467
+ this.backEvent.next();
468
+ }
469
+ }
470
+ forward() {
471
+ var _a;
472
+ if (this.canForward) {
473
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.redo();
474
+ this.forwardEvent.next();
475
+ }
476
+ }
477
+ clear() {
478
+ var _a;
479
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.clear();
480
+ this.changeEvent.next();
481
+ }
482
+ destroy() {
483
+ var _a;
484
+ this.subscriptions.forEach(i => i.unsubscribe());
485
+ this.collaborateCursor.destroy();
486
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.destroy();
487
+ }
488
+ syncRootComponent(root, rootComponent) {
489
+ let slots = root.get('slots');
490
+ if (!slots) {
491
+ slots = new yjs.Array();
492
+ rootComponent.slots.toArray().forEach(i => {
493
+ const sharedSlot = this.createSharedSlotBySlot(i);
494
+ slots.push([sharedSlot]);
495
+ });
496
+ this.yDoc.transact(() => {
497
+ root.set('state', rootComponent.state);
498
+ root.set('slots', slots);
499
+ });
500
+ }
501
+ else if (slots.length === 0) {
502
+ rootComponent.updateState(() => {
503
+ return root.get('state');
504
+ });
505
+ this.yDoc.transact(() => {
506
+ rootComponent.slots.toArray().forEach(i => {
507
+ const sharedSlot = this.createSharedSlotBySlot(i);
508
+ slots.push([sharedSlot]);
509
+ });
510
+ });
511
+ }
512
+ else {
513
+ rootComponent.updateState(() => {
514
+ return root.get('state');
515
+ });
516
+ rootComponent.slots.clean();
517
+ slots.forEach(sharedSlot => {
518
+ const slot = this.createSlotBySharedSlot(sharedSlot);
519
+ this.syncSlotContent(sharedSlot.get('content'), slot);
520
+ this.syncSlotState(sharedSlot, slot);
521
+ rootComponent.slots.insert(slot);
522
+ });
523
+ }
524
+ this.syncComponentState(root, rootComponent);
525
+ this.syncComponentSlots(slots, rootComponent);
526
+ }
527
+ restoreCursorLocation(position) {
528
+ const anchorPosition = yjs.createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc);
529
+ const focusPosition = yjs.createAbsolutePositionFromRelativePosition(position.focus, this.yDoc);
530
+ if (anchorPosition && focusPosition) {
531
+ const focusSlot = this.contentMap.get(focusPosition.type);
532
+ const anchorSlot = this.contentMap.get(anchorPosition.type);
533
+ if (focusSlot && anchorSlot) {
534
+ this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index);
535
+ return;
536
+ }
537
+ }
538
+ this.selection.unSelect();
539
+ }
540
+ getRelativeCursorLocation() {
541
+ const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection;
542
+ if (anchorSlot) {
543
+ const anchorYText = this.contentMap.get(anchorSlot);
544
+ if (anchorYText) {
545
+ const anchorPosition = yjs.createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
546
+ if (focusSlot) {
547
+ const focusYText = this.contentMap.get(focusSlot);
548
+ if (focusYText) {
549
+ const focusPosition = yjs.createRelativePositionFromTypeIndex(focusYText, focusOffset);
550
+ return {
551
+ focus: focusPosition,
552
+ anchor: anchorPosition
553
+ };
554
+ }
555
+ }
556
+ }
557
+ }
558
+ return null;
559
+ }
560
+ syncSlotContent(content, slot) {
561
+ this.contentMap.set(slot, content);
562
+ const syncRemote = (ev, tr) => {
563
+ this.runRemoteUpdate(tr, () => {
564
+ slot.retain(0);
565
+ ev.keysChanged.forEach(key => {
566
+ const change = ev.keys.get(key);
567
+ if (!change) {
568
+ return;
569
+ }
570
+ const updateType = change.action;
571
+ if (updateType === 'update' || updateType === 'add') {
572
+ const attribute = this.factory.getAttribute(key);
573
+ if (attribute) {
574
+ slot.setAttribute(attribute, content.getAttribute(key));
575
+ }
576
+ }
577
+ else if (updateType === 'delete') {
578
+ const attribute = this.factory.getAttribute(key);
579
+ if (attribute) {
580
+ slot.removeAttribute(attribute);
581
+ }
582
+ }
583
+ });
584
+ ev.delta.forEach(action => {
585
+ if (Reflect.has(action, 'retain')) {
586
+ if (action.attributes) {
587
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
588
+ if (formats.length) {
589
+ slot.retain(action.retain, formats);
590
+ }
591
+ slot.retain(slot.index + action.retain);
592
+ }
593
+ else {
594
+ slot.retain(action.retain);
595
+ }
596
+ }
597
+ else if (action.insert) {
598
+ const index = slot.index;
599
+ let length = 1;
600
+ if (typeof action.insert === 'string') {
601
+ length = action.insert.length;
602
+ slot.insert(action.insert, remoteFormatsToLocal(this.factory, action.attributes));
603
+ }
604
+ else {
605
+ const sharedComponent = action.insert;
606
+ const canInsertInlineComponent = slot.schema.includes(core.ContentType.InlineComponent);
607
+ const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent);
608
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
609
+ this.syncComponentState(sharedComponent, component);
610
+ slot.insert(component);
611
+ }
612
+ if (this.selection.isSelected) {
613
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
614
+ this.selection.setAnchor(slot, this.selection.anchorOffset + length);
615
+ }
616
+ if (slot === this.selection.focusSlot && this.selection.focusOffset > index) {
617
+ this.selection.setFocus(slot, this.selection.focusOffset + length);
618
+ }
619
+ }
620
+ }
621
+ else if (action.delete) {
622
+ const index = slot.index;
623
+ slot.retain(slot.index);
624
+ slot.delete(action.delete);
625
+ if (this.selection.isSelected) {
626
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
627
+ this.selection.setAnchor(slot, this.selection.startOffset - action.delete);
628
+ }
629
+ if (slot === this.selection.focusSlot && this.selection.focusOffset >= index) {
630
+ this.selection.setFocus(slot, this.selection.focusOffset - action.delete);
631
+ }
632
+ }
633
+ }
634
+ });
635
+ });
636
+ };
637
+ content.observe(syncRemote);
638
+ const sub = slot.onContentChange.pipe(stream.filter(() => {
639
+ return !this.scheduler.ignoreChanges;
640
+ })).subscribe(actions => {
641
+ this.runLocalUpdate(() => {
642
+ var _a;
643
+ let offset = 0;
644
+ let length = 0;
645
+ for (const action of actions) {
646
+ if (action.type === 'retain') {
647
+ const formats = action.formats;
648
+ if (formats) {
649
+ const keys = Object.keys(formats);
650
+ let length = keys.length;
651
+ keys.forEach(key => {
652
+ const formatter = this.factory.getFormatter(key);
653
+ if (!formatter) {
654
+ length--;
655
+ Reflect.deleteProperty(formats, key);
656
+ }
657
+ });
658
+ if (length) {
659
+ content.format(offset, action.offset, formats);
660
+ }
661
+ }
662
+ else {
663
+ offset = action.offset;
664
+ }
665
+ }
666
+ else if (action.type === 'insert') {
667
+ const delta = content.toDelta();
668
+ const isEmpty = delta.length === 1 && delta[0].insert === core.Slot.emptyPlaceholder;
669
+ if (typeof action.content === 'string') {
670
+ length = action.content.length;
671
+ content.insert(offset, action.content, action.formats || {});
672
+ }
673
+ else {
674
+ length = 1;
675
+ const sharedComponent = this.createSharedComponentByComponent(action.ref);
676
+ content.insertEmbed(offset, sharedComponent, action.formats || {});
677
+ }
678
+ if (isEmpty && offset === 0) {
679
+ content.delete(content.length - 1, 1);
680
+ }
681
+ offset += length;
682
+ }
683
+ else if (action.type === 'delete') {
684
+ const delta = content.toDelta();
685
+ if (content.length) {
686
+ content.delete(offset, action.count);
687
+ }
688
+ if (content.length === 0) {
689
+ content.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
690
+ }
691
+ }
692
+ else if (action.type === 'attrSet') {
693
+ content.setAttribute(action.name, action.value);
694
+ }
695
+ else if (action.type === 'attrRemove') {
696
+ content.removeAttribute(action.name);
697
+ }
698
+ }
699
+ });
700
+ });
701
+ sub.add(slot.onChildComponentRemove.subscribe(components => {
702
+ components.forEach(c => {
703
+ this.cleanSubscriptionsByComponent(c);
704
+ });
705
+ }));
706
+ this.contentSyncCaches.set(slot, () => {
707
+ content.unobserve(syncRemote);
708
+ sub.unsubscribe();
709
+ });
710
+ }
711
+ syncSlotState(remoteSlot, slot) {
712
+ const syncRemote = (ev, tr) => {
713
+ this.runRemoteUpdate(tr, () => {
714
+ ev.keysChanged.forEach(key => {
715
+ if (key === 'state') {
716
+ const state = ev.target.get('state');
717
+ slot.updateState(draft => {
718
+ if (typeof draft === 'object' && draft !== null) {
719
+ Object.assign(draft, state);
720
+ }
721
+ else {
722
+ return state;
723
+ }
724
+ });
725
+ }
726
+ });
727
+ });
728
+ };
729
+ remoteSlot.observe(syncRemote);
730
+ const sub = slot.onStateChange.pipe(stream.filter(() => {
731
+ return !this.scheduler.ignoreChanges;
732
+ })).subscribe(change => {
733
+ this.runLocalUpdate(() => {
734
+ remoteSlot.set('state', change.newState);
735
+ }, change.record);
736
+ });
737
+ this.slotStateSyncCaches.set(slot, () => {
738
+ remoteSlot.unobserve(syncRemote);
739
+ sub.unsubscribe();
740
+ });
741
+ }
742
+ syncComponentSlots(remoteSlots, component) {
743
+ const slots = component.slots;
744
+ const syncRemote = (ev, tr) => {
745
+ this.runRemoteUpdate(tr, () => {
746
+ let index = 0;
747
+ slots.retain(index);
748
+ ev.delta.forEach(action => {
749
+ if (Reflect.has(action, 'retain')) {
750
+ index += action.retain;
751
+ slots.retain(index);
752
+ }
753
+ else if (action.insert) {
754
+ action.insert.forEach(item => {
755
+ const slot = this.createSlotBySharedSlot(item);
756
+ slots.insert(slot);
757
+ this.syncSlotContent(item.get('content'), slot);
758
+ this.syncSlotState(item, slot);
759
+ index++;
760
+ });
761
+ }
762
+ else if (action.delete) {
763
+ slots.retain(index);
764
+ slots.delete(action.delete);
765
+ }
766
+ });
767
+ });
768
+ };
769
+ remoteSlots.observe(syncRemote);
770
+ const sub = slots.onChange.pipe(stream.filter(() => {
771
+ return !this.scheduler.ignoreChanges;
772
+ })).subscribe(operations => {
773
+ this.runLocalUpdate(() => {
774
+ const applyActions = operations.apply;
775
+ let index;
776
+ applyActions.forEach(action => {
777
+ if (action.type === 'retain') {
778
+ index = action.offset;
779
+ }
780
+ else if (action.type === 'insertSlot') {
781
+ const sharedSlot = this.createSharedSlotBySlot(action.ref);
782
+ remoteSlots.insert(index, [sharedSlot]);
783
+ index++;
784
+ }
785
+ else if (action.type === 'delete') {
786
+ remoteSlots.delete(index, action.count);
787
+ }
788
+ });
789
+ });
790
+ });
791
+ sub.add(slots.onChildSlotRemove.subscribe(slots => {
792
+ slots.forEach(slot => {
793
+ this.cleanSubscriptionsBySlot(slot);
794
+ });
795
+ }));
796
+ this.slotsSyncCaches.set(component, () => {
797
+ remoteSlots.unobserve(syncRemote);
798
+ sub.unsubscribe();
799
+ });
800
+ }
801
+ syncComponentState(remoteComponent, component) {
802
+ const syncRemote = (ev, tr) => {
803
+ this.runRemoteUpdate(tr, () => {
804
+ ev.keysChanged.forEach(key => {
805
+ if (key === 'state') {
806
+ const state = ev.target.get('state');
807
+ component.updateState(draft => {
808
+ if (typeof draft === 'object' && draft !== null) {
809
+ Object.assign(draft, state);
810
+ }
811
+ else {
812
+ return state;
813
+ }
814
+ });
815
+ }
816
+ });
817
+ });
818
+ };
819
+ remoteComponent.observe(syncRemote);
820
+ const sub = component.onStateChange.pipe(stream.filter(() => {
821
+ return !this.scheduler.ignoreChanges;
822
+ })).subscribe(change => {
823
+ this.runLocalUpdate(() => {
824
+ remoteComponent.set('state', change.newState);
825
+ }, change.record);
826
+ });
827
+ this.componentStateSyncCaches.set(component, () => {
828
+ remoteComponent.unobserve(syncRemote);
829
+ sub.unsubscribe();
830
+ });
831
+ }
832
+ runLocalUpdate(fn, record = true) {
833
+ if (this.updateFromRemote || this.controller.readonly) {
834
+ return;
835
+ }
836
+ this.updateRemoteActions.push({
837
+ record,
838
+ action: fn
839
+ });
840
+ }
841
+ runRemoteUpdate(tr, fn) {
842
+ if (tr.origin === this.yDoc) {
843
+ return;
844
+ }
845
+ this.updateFromRemote = true;
846
+ if (tr.origin === this.manager) {
847
+ this.scheduler.historyApplyTransact(fn);
848
+ }
849
+ else {
850
+ this.scheduler.remoteUpdateTransact(fn);
851
+ }
852
+ this.updateFromRemote = false;
853
+ }
854
+ createSharedComponentByComponent(component) {
855
+ const sharedComponent = new yjs.Map();
856
+ sharedComponent.set('state', component.state);
857
+ sharedComponent.set('name', component.name);
858
+ const sharedSlots = new yjs.Array();
859
+ sharedComponent.set('slots', sharedSlots);
860
+ component.slots.toArray().forEach(slot => {
861
+ const sharedSlot = this.createSharedSlotBySlot(slot);
862
+ sharedSlots.push([sharedSlot]);
863
+ });
864
+ this.syncComponentSlots(sharedSlots, component);
865
+ this.syncComponentState(sharedComponent, component);
866
+ return sharedComponent;
867
+ }
868
+ createSharedSlotBySlot(slot) {
869
+ const sharedSlot = new yjs.Map();
870
+ sharedSlot.set('schema', slot.schema);
871
+ sharedSlot.set('state', slot.state);
872
+ const sharedContent = new yjs.Text();
873
+ sharedSlot.set('content', sharedContent);
874
+ let offset = 0;
875
+ slot.toDelta().forEach(i => {
876
+ let formats = {};
877
+ if (i.formats) {
878
+ i.formats.forEach(item => {
879
+ formats[item[0].name] = item[1];
880
+ });
881
+ }
882
+ else {
883
+ formats = null;
884
+ }
885
+ if (typeof i.insert === 'string') {
886
+ sharedContent.insert(offset, i.insert, formats);
887
+ }
888
+ else {
889
+ const sharedComponent = this.createSharedComponentByComponent(i.insert);
890
+ sharedContent.insertEmbed(offset, sharedComponent, formats);
891
+ }
892
+ offset += i.insert.length;
893
+ });
894
+ slot.getAttributes().forEach(item => {
895
+ sharedContent.setAttribute(item[0].name, item[1]);
896
+ });
897
+ this.syncSlotContent(sharedContent, slot);
898
+ this.syncSlotState(sharedSlot, slot);
899
+ return sharedSlot;
900
+ }
901
+ createComponentBySharedComponent(yMap, canInsertInlineComponent) {
902
+ const sharedSlots = yMap.get('slots');
903
+ const slots = [];
904
+ sharedSlots.forEach(sharedSlot => {
905
+ const slot = this.createSlotBySharedSlot(sharedSlot);
906
+ slots.push(slot);
907
+ });
908
+ const name = yMap.get('name');
909
+ const state = yMap.get('state');
910
+ const instance = this.factory.createComponentByData(name, {
911
+ state,
912
+ slots
913
+ });
914
+ if (instance) {
915
+ instance.slots.toArray().forEach((slot, index) => {
916
+ let sharedSlot = sharedSlots.get(index);
917
+ if (!sharedSlot) {
918
+ sharedSlot = this.createSharedSlotBySlot(slot);
919
+ sharedSlots.push([sharedSlot]);
920
+ }
921
+ this.syncSlotState(sharedSlot, slot);
922
+ this.syncSlotContent(sharedSlot.get('content'), slot);
923
+ });
924
+ return instance;
925
+ }
926
+ return createUnknownComponent(name, canInsertInlineComponent).createInstance(this.starter);
927
+ }
928
+ createSlotBySharedSlot(sharedSlot) {
929
+ const content = sharedSlot.get('content');
930
+ const delta = content.toDelta();
931
+ const slot = this.factory.createSlot({
932
+ schema: sharedSlot.get('schema'),
933
+ state: sharedSlot.get('state'),
934
+ attributes: {},
935
+ formats: {},
936
+ content: []
937
+ });
938
+ const attrs = content.getAttributes();
939
+ Object.keys(attrs).forEach(key => {
940
+ const attribute = this.factory.getAttribute(key);
941
+ if (attribute) {
942
+ slot.setAttribute(attribute, attrs[key]);
943
+ }
944
+ });
945
+ for (const action of delta) {
946
+ if (action.insert) {
947
+ if (typeof action.insert === 'string') {
948
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
949
+ slot.insert(action.insert, formats);
950
+ }
951
+ else {
952
+ const sharedComponent = action.insert;
953
+ const canInsertInlineComponent = slot.schema.includes(core.ContentType.InlineComponent);
954
+ const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent);
955
+ slot.insert(component, remoteFormatsToLocal(this.factory, action.attributes));
956
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
957
+ this.syncComponentState(sharedComponent, component);
958
+ }
959
+ }
960
+ else {
961
+ throw collaborateErrorFn('unexpected delta action.');
962
+ }
963
+ }
964
+ return slot;
965
+ }
966
+ cleanSubscriptionsBySlot(slot) {
967
+ this.contentMap.delete(slot);
968
+ [this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
969
+ if (fn) {
970
+ fn();
971
+ }
972
+ });
973
+ slot.sliceContent().forEach(i => {
974
+ if (typeof i !== 'string') {
975
+ this.cleanSubscriptionsByComponent(i);
976
+ }
977
+ });
978
+ }
979
+ cleanSubscriptionsByComponent(component) {
980
+ [this.slotsSyncCaches.get(component), this.componentStateSyncCaches.get(component)].forEach(fn => {
981
+ if (fn) {
982
+ fn();
983
+ }
984
+ });
985
+ component.slots.toArray().forEach(slot => {
986
+ this.cleanSubscriptionsBySlot(slot);
987
+ });
988
+ }
989
+ };
990
+ exports.Collaborate = __decorate([
991
+ di.Injectable(),
992
+ __param(0, di.Inject(core.HISTORY_STACK_SIZE)),
993
+ __metadata("design:paramtypes", [Number, core.RootComponentRef,
994
+ exports.CollaborateCursor,
995
+ core.Controller,
996
+ core.Scheduler,
997
+ core.Factory,
998
+ core.Selection,
999
+ core.Starter])
1000
+ ], exports.Collaborate);
1001
+ function remoteFormatsToLocal(factory, attrs) {
1002
+ const formats = [];
1003
+ if (attrs) {
1004
+ Object.keys(attrs).forEach(key => {
1005
+ const formatter = factory.getFormatter(key);
1006
+ if (formatter) {
1007
+ formats.push([formatter, attrs[key]]);
1008
+ }
1009
+ });
1010
+ }
1011
+ return formats;
1012
+ }
1013
+
1014
+ const collaborateModule = {
1015
+ providers: [
1016
+ exports.Collaborate,
1017
+ exports.CollaborateCursor,
1018
+ {
1019
+ provide: core.History,
1020
+ useClass: exports.Collaborate
1021
+ }
1022
+ ]
1023
+ };
1024
+
1025
+ exports.CollaborateSelectionAwarenessDelegate = CollaborateSelectionAwarenessDelegate;
1026
+ exports.collaborateModule = collaborateModule;