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