@textbus/collaborate 2.5.2 → 2.5.4

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