@textbus/collaborate 3.0.0-alpha.16 → 3.0.0-alpha.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1023 @@
1
+ import { Scheduler, Selection, defineComponent, ContentType, VElement, makeError, HISTORY_STACK_SIZE, ChangeOrigin, Slot, RootComponentRef, Controller, Factory, 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, factory, selection, starter) {
355
+ this.stackSize = stackSize;
356
+ this.rootComponentRef = rootComponentRef;
357
+ this.collaborateCursor = collaborateCursor;
358
+ this.controller = controller;
359
+ this.scheduler = scheduler;
360
+ this.factory = factory;
361
+ this.selection = selection;
362
+ this.starter = starter;
363
+ this.yDoc = new Doc();
364
+ this.backEvent = new Subject();
365
+ this.forwardEvent = new Subject();
366
+ this.changeEvent = new Subject();
367
+ this.pushEvent = new Subject();
368
+ this.manager = null;
369
+ this.subscriptions = [];
370
+ this.updateFromRemote = false;
371
+ this.contentSyncCaches = new WeakMap();
372
+ this.slotStateSyncCaches = new WeakMap();
373
+ this.slotsSyncCaches = new WeakMap();
374
+ this.componentStateSyncCaches = new WeakMap();
375
+ this.selectionChangeEvent = new Subject();
376
+ this.contentMap = new ContentMap();
377
+ this.updateRemoteActions = [];
378
+ this.noRecord = {};
379
+ this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(delay());
380
+ this.onBack = this.backEvent.asObservable();
381
+ this.onForward = this.forwardEvent.asObservable();
382
+ this.onChange = this.changeEvent.asObservable();
383
+ this.onPush = this.pushEvent.asObservable();
384
+ }
385
+ get canBack() {
386
+ var _a;
387
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canUndo()) || false;
388
+ }
389
+ get canForward() {
390
+ var _a;
391
+ return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canRedo()) || false;
392
+ }
393
+ listen() {
394
+ const root = this.yDoc.getMap('RootComponent');
395
+ const rootComponent = this.rootComponentRef.component;
396
+ this.manager = new UndoManager(root, {
397
+ trackedOrigins: new Set([this.yDoc])
398
+ });
399
+ const cursorKey = 'cursor-position';
400
+ this.manager.on('stack-item-added', event => {
401
+ event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation());
402
+ if (this.manager.undoStack.length > this.stackSize) {
403
+ this.manager.undoStack.shift();
404
+ }
405
+ if (event.origin === this.yDoc) {
406
+ this.pushEvent.next();
407
+ }
408
+ this.changeEvent.next();
409
+ });
410
+ this.manager.on('stack-item-popped', event => {
411
+ const position = event.stackItem.meta.get(cursorKey);
412
+ if (position) {
413
+ this.restoreCursorLocation(position);
414
+ }
415
+ });
416
+ this.subscriptions.push(this.selection.onChange.subscribe(() => {
417
+ const paths = this.selection.getPaths();
418
+ this.selectionChangeEvent.next(paths);
419
+ }), this.scheduler.onDocChanged.pipe(map(item => {
420
+ return item.filter(i => {
421
+ return i.from !== ChangeOrigin.Remote;
422
+ });
423
+ }), filter(item => {
424
+ return item.length;
425
+ })).subscribe(() => {
426
+ const updates = [];
427
+ let update = null;
428
+ for (const item of this.updateRemoteActions) {
429
+ if (!update) {
430
+ update = {
431
+ record: item.record,
432
+ actions: []
433
+ };
434
+ updates.push(update);
435
+ }
436
+ if (update.record === item.record) {
437
+ update.actions.push(item.action);
438
+ }
439
+ else {
440
+ update = {
441
+ record: item.record,
442
+ actions: [item.action]
443
+ };
444
+ updates.push(update);
445
+ }
446
+ }
447
+ this.updateRemoteActions = [];
448
+ for (const item of updates) {
449
+ this.yDoc.transact(() => {
450
+ item.actions.forEach(fn => {
451
+ fn();
452
+ });
453
+ }, item.record ? this.yDoc : this.noRecord);
454
+ }
455
+ }));
456
+ this.syncRootComponent(root, rootComponent);
457
+ }
458
+ updateRemoteSelection(paths) {
459
+ this.collaborateCursor.draw(paths);
460
+ }
461
+ back() {
462
+ var _a;
463
+ if (this.canBack) {
464
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.undo();
465
+ this.backEvent.next();
466
+ }
467
+ }
468
+ forward() {
469
+ var _a;
470
+ if (this.canForward) {
471
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.redo();
472
+ this.forwardEvent.next();
473
+ }
474
+ }
475
+ clear() {
476
+ var _a;
477
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.clear();
478
+ this.changeEvent.next();
479
+ }
480
+ destroy() {
481
+ var _a;
482
+ this.subscriptions.forEach(i => i.unsubscribe());
483
+ this.collaborateCursor.destroy();
484
+ (_a = this.manager) === null || _a === void 0 ? void 0 : _a.destroy();
485
+ }
486
+ syncRootComponent(root, rootComponent) {
487
+ let slots = root.get('slots');
488
+ if (!slots) {
489
+ slots = new Array();
490
+ rootComponent.slots.toArray().forEach(i => {
491
+ const sharedSlot = this.createSharedSlotBySlot(i);
492
+ slots.push([sharedSlot]);
493
+ });
494
+ this.yDoc.transact(() => {
495
+ root.set('state', rootComponent.state);
496
+ root.set('slots', slots);
497
+ });
498
+ }
499
+ else if (slots.length === 0) {
500
+ rootComponent.updateState(() => {
501
+ return root.get('state');
502
+ });
503
+ this.yDoc.transact(() => {
504
+ rootComponent.slots.toArray().forEach(i => {
505
+ const sharedSlot = this.createSharedSlotBySlot(i);
506
+ slots.push([sharedSlot]);
507
+ });
508
+ });
509
+ }
510
+ else {
511
+ rootComponent.updateState(() => {
512
+ return root.get('state');
513
+ });
514
+ rootComponent.slots.clean();
515
+ slots.forEach(sharedSlot => {
516
+ const slot = this.createSlotBySharedSlot(sharedSlot);
517
+ this.syncSlotContent(sharedSlot.get('content'), slot);
518
+ this.syncSlotState(sharedSlot, slot);
519
+ rootComponent.slots.insert(slot);
520
+ });
521
+ }
522
+ this.syncComponentState(root, rootComponent);
523
+ this.syncComponentSlots(slots, rootComponent);
524
+ }
525
+ restoreCursorLocation(position) {
526
+ const anchorPosition = createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc);
527
+ const focusPosition = createAbsolutePositionFromRelativePosition(position.focus, this.yDoc);
528
+ if (anchorPosition && focusPosition) {
529
+ const focusSlot = this.contentMap.get(focusPosition.type);
530
+ const anchorSlot = this.contentMap.get(anchorPosition.type);
531
+ if (focusSlot && anchorSlot) {
532
+ this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index);
533
+ return;
534
+ }
535
+ }
536
+ this.selection.unSelect();
537
+ }
538
+ getRelativeCursorLocation() {
539
+ const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection;
540
+ if (anchorSlot) {
541
+ const anchorYText = this.contentMap.get(anchorSlot);
542
+ if (anchorYText) {
543
+ const anchorPosition = createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
544
+ if (focusSlot) {
545
+ const focusYText = this.contentMap.get(focusSlot);
546
+ if (focusYText) {
547
+ const focusPosition = createRelativePositionFromTypeIndex(focusYText, focusOffset);
548
+ return {
549
+ focus: focusPosition,
550
+ anchor: anchorPosition
551
+ };
552
+ }
553
+ }
554
+ }
555
+ }
556
+ return null;
557
+ }
558
+ syncSlotContent(content, slot) {
559
+ this.contentMap.set(slot, content);
560
+ const syncRemote = (ev, tr) => {
561
+ this.runRemoteUpdate(tr, () => {
562
+ slot.retain(0);
563
+ ev.keysChanged.forEach(key => {
564
+ const change = ev.keys.get(key);
565
+ if (!change) {
566
+ return;
567
+ }
568
+ const updateType = change.action;
569
+ if (updateType === 'update' || updateType === 'add') {
570
+ const attribute = this.factory.getAttribute(key);
571
+ if (attribute) {
572
+ slot.setAttribute(attribute, content.getAttribute(key));
573
+ }
574
+ }
575
+ else if (updateType === 'delete') {
576
+ const attribute = this.factory.getAttribute(key);
577
+ if (attribute) {
578
+ slot.removeAttribute(attribute);
579
+ }
580
+ }
581
+ });
582
+ ev.delta.forEach(action => {
583
+ if (Reflect.has(action, 'retain')) {
584
+ if (action.attributes) {
585
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
586
+ if (formats.length) {
587
+ slot.retain(action.retain, formats);
588
+ }
589
+ slot.retain(slot.index + action.retain);
590
+ }
591
+ else {
592
+ slot.retain(action.retain);
593
+ }
594
+ }
595
+ else if (action.insert) {
596
+ const index = slot.index;
597
+ let length = 1;
598
+ if (typeof action.insert === 'string') {
599
+ length = action.insert.length;
600
+ slot.insert(action.insert, remoteFormatsToLocal(this.factory, action.attributes));
601
+ }
602
+ else {
603
+ const sharedComponent = action.insert;
604
+ const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent);
605
+ const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent);
606
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
607
+ this.syncComponentState(sharedComponent, component);
608
+ slot.insert(component);
609
+ }
610
+ if (this.selection.isSelected) {
611
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
612
+ this.selection.setAnchor(slot, this.selection.anchorOffset + length);
613
+ }
614
+ if (slot === this.selection.focusSlot && this.selection.focusOffset > index) {
615
+ this.selection.setFocus(slot, this.selection.focusOffset + length);
616
+ }
617
+ }
618
+ }
619
+ else if (action.delete) {
620
+ const index = slot.index;
621
+ slot.retain(slot.index);
622
+ slot.delete(action.delete);
623
+ if (this.selection.isSelected) {
624
+ if (slot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
625
+ this.selection.setAnchor(slot, this.selection.startOffset - action.delete);
626
+ }
627
+ if (slot === this.selection.focusSlot && this.selection.focusOffset >= index) {
628
+ this.selection.setFocus(slot, this.selection.focusOffset - action.delete);
629
+ }
630
+ }
631
+ }
632
+ });
633
+ });
634
+ };
635
+ content.observe(syncRemote);
636
+ const sub = slot.onContentChange.pipe(filter(() => {
637
+ return !this.scheduler.ignoreChanges;
638
+ })).subscribe(actions => {
639
+ this.runLocalUpdate(() => {
640
+ var _a;
641
+ let offset = 0;
642
+ let length = 0;
643
+ for (const action of actions) {
644
+ if (action.type === 'retain') {
645
+ const formats = action.formats;
646
+ if (formats) {
647
+ const keys = Object.keys(formats);
648
+ let length = keys.length;
649
+ keys.forEach(key => {
650
+ const formatter = this.factory.getFormatter(key);
651
+ if (!formatter) {
652
+ length--;
653
+ Reflect.deleteProperty(formats, key);
654
+ }
655
+ });
656
+ if (length) {
657
+ content.format(offset, action.offset, formats);
658
+ }
659
+ }
660
+ else {
661
+ offset = action.offset;
662
+ }
663
+ }
664
+ else if (action.type === 'insert') {
665
+ const delta = content.toDelta();
666
+ const isEmpty = delta.length === 1 && delta[0].insert === Slot.emptyPlaceholder;
667
+ if (typeof action.content === 'string') {
668
+ length = action.content.length;
669
+ content.insert(offset, action.content, action.formats || {});
670
+ }
671
+ else {
672
+ length = 1;
673
+ const sharedComponent = this.createSharedComponentByComponent(action.ref);
674
+ content.insertEmbed(offset, sharedComponent, action.formats || {});
675
+ }
676
+ if (isEmpty && offset === 0) {
677
+ content.delete(content.length - 1, 1);
678
+ }
679
+ offset += length;
680
+ }
681
+ else if (action.type === 'delete') {
682
+ const delta = content.toDelta();
683
+ if (content.length) {
684
+ content.delete(offset, action.count);
685
+ }
686
+ if (content.length === 0) {
687
+ content.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
688
+ }
689
+ }
690
+ else if (action.type === 'attrSet') {
691
+ content.setAttribute(action.name, action.value);
692
+ }
693
+ else if (action.type === 'attrRemove') {
694
+ content.removeAttribute(action.name);
695
+ }
696
+ }
697
+ });
698
+ });
699
+ sub.add(slot.onChildComponentRemove.subscribe(components => {
700
+ components.forEach(c => {
701
+ this.cleanSubscriptionsByComponent(c);
702
+ });
703
+ }));
704
+ this.contentSyncCaches.set(slot, () => {
705
+ content.unobserve(syncRemote);
706
+ sub.unsubscribe();
707
+ });
708
+ }
709
+ syncSlotState(remoteSlot, slot) {
710
+ const syncRemote = (ev, tr) => {
711
+ this.runRemoteUpdate(tr, () => {
712
+ ev.keysChanged.forEach(key => {
713
+ if (key === 'state') {
714
+ const state = ev.target.get('state');
715
+ slot.updateState(draft => {
716
+ if (typeof draft === 'object' && draft !== null) {
717
+ Object.assign(draft, state);
718
+ }
719
+ else {
720
+ return state;
721
+ }
722
+ });
723
+ }
724
+ });
725
+ });
726
+ };
727
+ remoteSlot.observe(syncRemote);
728
+ const sub = slot.onStateChange.pipe(filter(() => {
729
+ return !this.scheduler.ignoreChanges;
730
+ })).subscribe(change => {
731
+ this.runLocalUpdate(() => {
732
+ remoteSlot.set('state', change.newState);
733
+ }, change.record);
734
+ });
735
+ this.slotStateSyncCaches.set(slot, () => {
736
+ remoteSlot.unobserve(syncRemote);
737
+ sub.unsubscribe();
738
+ });
739
+ }
740
+ syncComponentSlots(remoteSlots, component) {
741
+ const slots = component.slots;
742
+ const syncRemote = (ev, tr) => {
743
+ this.runRemoteUpdate(tr, () => {
744
+ let index = 0;
745
+ slots.retain(index);
746
+ ev.delta.forEach(action => {
747
+ if (Reflect.has(action, 'retain')) {
748
+ index += action.retain;
749
+ slots.retain(index);
750
+ }
751
+ else if (action.insert) {
752
+ action.insert.forEach(item => {
753
+ const slot = this.createSlotBySharedSlot(item);
754
+ slots.insert(slot);
755
+ this.syncSlotContent(item.get('content'), slot);
756
+ this.syncSlotState(item, slot);
757
+ index++;
758
+ });
759
+ }
760
+ else if (action.delete) {
761
+ slots.retain(index);
762
+ slots.delete(action.delete);
763
+ }
764
+ });
765
+ });
766
+ };
767
+ remoteSlots.observe(syncRemote);
768
+ const sub = slots.onChange.pipe(filter(() => {
769
+ return !this.scheduler.ignoreChanges;
770
+ })).subscribe(operations => {
771
+ this.runLocalUpdate(() => {
772
+ const applyActions = operations.apply;
773
+ let index;
774
+ applyActions.forEach(action => {
775
+ if (action.type === 'retain') {
776
+ index = action.offset;
777
+ }
778
+ else if (action.type === 'insertSlot') {
779
+ const sharedSlot = this.createSharedSlotBySlot(action.ref);
780
+ remoteSlots.insert(index, [sharedSlot]);
781
+ index++;
782
+ }
783
+ else if (action.type === 'delete') {
784
+ remoteSlots.delete(index, action.count);
785
+ }
786
+ });
787
+ });
788
+ });
789
+ sub.add(slots.onChildSlotRemove.subscribe(slots => {
790
+ slots.forEach(slot => {
791
+ this.cleanSubscriptionsBySlot(slot);
792
+ });
793
+ }));
794
+ this.slotsSyncCaches.set(component, () => {
795
+ remoteSlots.unobserve(syncRemote);
796
+ sub.unsubscribe();
797
+ });
798
+ }
799
+ syncComponentState(remoteComponent, component) {
800
+ const syncRemote = (ev, tr) => {
801
+ this.runRemoteUpdate(tr, () => {
802
+ ev.keysChanged.forEach(key => {
803
+ if (key === 'state') {
804
+ const state = ev.target.get('state');
805
+ component.updateState(draft => {
806
+ if (typeof draft === 'object' && draft !== null) {
807
+ Object.assign(draft, state);
808
+ }
809
+ else {
810
+ return state;
811
+ }
812
+ });
813
+ }
814
+ });
815
+ });
816
+ };
817
+ remoteComponent.observe(syncRemote);
818
+ const sub = component.onStateChange.pipe(filter(() => {
819
+ return !this.scheduler.ignoreChanges;
820
+ })).subscribe(change => {
821
+ this.runLocalUpdate(() => {
822
+ remoteComponent.set('state', change.newState);
823
+ }, change.record);
824
+ });
825
+ this.componentStateSyncCaches.set(component, () => {
826
+ remoteComponent.unobserve(syncRemote);
827
+ sub.unsubscribe();
828
+ });
829
+ }
830
+ runLocalUpdate(fn, record = true) {
831
+ if (this.updateFromRemote || this.controller.readonly) {
832
+ return;
833
+ }
834
+ this.updateRemoteActions.push({
835
+ record,
836
+ action: fn
837
+ });
838
+ }
839
+ runRemoteUpdate(tr, fn) {
840
+ if (tr.origin === this.yDoc) {
841
+ return;
842
+ }
843
+ this.updateFromRemote = true;
844
+ if (tr.origin === this.manager) {
845
+ this.scheduler.historyApplyTransact(fn);
846
+ }
847
+ else {
848
+ this.scheduler.remoteUpdateTransact(fn);
849
+ }
850
+ this.updateFromRemote = false;
851
+ }
852
+ createSharedComponentByComponent(component) {
853
+ const sharedComponent = new Map();
854
+ sharedComponent.set('state', component.state);
855
+ sharedComponent.set('name', component.name);
856
+ const sharedSlots = new Array();
857
+ sharedComponent.set('slots', sharedSlots);
858
+ component.slots.toArray().forEach(slot => {
859
+ const sharedSlot = this.createSharedSlotBySlot(slot);
860
+ sharedSlots.push([sharedSlot]);
861
+ });
862
+ this.syncComponentSlots(sharedSlots, component);
863
+ this.syncComponentState(sharedComponent, component);
864
+ return sharedComponent;
865
+ }
866
+ createSharedSlotBySlot(slot) {
867
+ const sharedSlot = new Map();
868
+ sharedSlot.set('schema', slot.schema);
869
+ sharedSlot.set('state', slot.state);
870
+ const sharedContent = new Text();
871
+ sharedSlot.set('content', sharedContent);
872
+ let offset = 0;
873
+ slot.toDelta().forEach(i => {
874
+ let formats = {};
875
+ if (i.formats) {
876
+ i.formats.forEach(item => {
877
+ formats[item[0].name] = item[1];
878
+ });
879
+ }
880
+ else {
881
+ formats = null;
882
+ }
883
+ if (typeof i.insert === 'string') {
884
+ sharedContent.insert(offset, i.insert, formats);
885
+ }
886
+ else {
887
+ const sharedComponent = this.createSharedComponentByComponent(i.insert);
888
+ sharedContent.insertEmbed(offset, sharedComponent, formats);
889
+ }
890
+ offset += i.insert.length;
891
+ });
892
+ slot.getAttributes().forEach(item => {
893
+ sharedContent.setAttribute(item[0].name, item[1]);
894
+ });
895
+ this.syncSlotContent(sharedContent, slot);
896
+ this.syncSlotState(sharedSlot, slot);
897
+ return sharedSlot;
898
+ }
899
+ createComponentBySharedComponent(yMap, canInsertInlineComponent) {
900
+ const sharedSlots = yMap.get('slots');
901
+ const slots = [];
902
+ sharedSlots.forEach(sharedSlot => {
903
+ const slot = this.createSlotBySharedSlot(sharedSlot);
904
+ slots.push(slot);
905
+ });
906
+ const name = yMap.get('name');
907
+ const state = yMap.get('state');
908
+ const instance = this.factory.createComponentByData(name, {
909
+ state,
910
+ slots
911
+ });
912
+ if (instance) {
913
+ instance.slots.toArray().forEach((slot, index) => {
914
+ let sharedSlot = sharedSlots.get(index);
915
+ if (!sharedSlot) {
916
+ sharedSlot = this.createSharedSlotBySlot(slot);
917
+ sharedSlots.push([sharedSlot]);
918
+ }
919
+ this.syncSlotState(sharedSlot, slot);
920
+ this.syncSlotContent(sharedSlot.get('content'), slot);
921
+ });
922
+ return instance;
923
+ }
924
+ return createUnknownComponent(name, canInsertInlineComponent).createInstance(this.starter);
925
+ }
926
+ createSlotBySharedSlot(sharedSlot) {
927
+ const content = sharedSlot.get('content');
928
+ const delta = content.toDelta();
929
+ const slot = this.factory.createSlot({
930
+ schema: sharedSlot.get('schema'),
931
+ state: sharedSlot.get('state'),
932
+ attributes: {},
933
+ formats: {},
934
+ content: []
935
+ });
936
+ const attrs = content.getAttributes();
937
+ Object.keys(attrs).forEach(key => {
938
+ const attribute = this.factory.getAttribute(key);
939
+ if (attribute) {
940
+ slot.setAttribute(attribute, attrs[key]);
941
+ }
942
+ });
943
+ for (const action of delta) {
944
+ if (action.insert) {
945
+ if (typeof action.insert === 'string') {
946
+ const formats = remoteFormatsToLocal(this.factory, action.attributes);
947
+ slot.insert(action.insert, formats);
948
+ }
949
+ else {
950
+ const sharedComponent = action.insert;
951
+ const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent);
952
+ const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent);
953
+ slot.insert(component, remoteFormatsToLocal(this.factory, action.attributes));
954
+ this.syncComponentSlots(sharedComponent.get('slots'), component);
955
+ this.syncComponentState(sharedComponent, component);
956
+ }
957
+ }
958
+ else {
959
+ throw collaborateErrorFn('unexpected delta action.');
960
+ }
961
+ }
962
+ return slot;
963
+ }
964
+ cleanSubscriptionsBySlot(slot) {
965
+ this.contentMap.delete(slot);
966
+ [this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
967
+ if (fn) {
968
+ fn();
969
+ }
970
+ });
971
+ slot.sliceContent().forEach(i => {
972
+ if (typeof i !== 'string') {
973
+ this.cleanSubscriptionsByComponent(i);
974
+ }
975
+ });
976
+ }
977
+ cleanSubscriptionsByComponent(component) {
978
+ [this.slotsSyncCaches.get(component), this.componentStateSyncCaches.get(component)].forEach(fn => {
979
+ if (fn) {
980
+ fn();
981
+ }
982
+ });
983
+ component.slots.toArray().forEach(slot => {
984
+ this.cleanSubscriptionsBySlot(slot);
985
+ });
986
+ }
987
+ };
988
+ Collaborate = __decorate([
989
+ Injectable(),
990
+ __param(0, Inject(HISTORY_STACK_SIZE)),
991
+ __metadata("design:paramtypes", [Number, RootComponentRef,
992
+ CollaborateCursor,
993
+ Controller,
994
+ Scheduler,
995
+ Factory,
996
+ Selection,
997
+ Starter])
998
+ ], Collaborate);
999
+ function remoteFormatsToLocal(factory, attrs) {
1000
+ const formats = [];
1001
+ if (attrs) {
1002
+ Object.keys(attrs).forEach(key => {
1003
+ const formatter = factory.getFormatter(key);
1004
+ if (formatter) {
1005
+ formats.push([formatter, attrs[key]]);
1006
+ }
1007
+ });
1008
+ }
1009
+ return formats;
1010
+ }
1011
+
1012
+ const collaborateModule = {
1013
+ providers: [
1014
+ Collaborate,
1015
+ CollaborateCursor,
1016
+ {
1017
+ provide: History,
1018
+ useClass: Collaborate
1019
+ }
1020
+ ]
1021
+ };
1022
+
1023
+ export { Collaborate, CollaborateCursor, CollaborateSelectionAwarenessDelegate, collaborateModule };