@textbus/collaborate 2.0.0-beta.5 → 2.0.0-beta.52
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 +17 -6
- package/bundles/collaborate-cursor.js +141 -119
- package/bundles/collaborate.d.ts +12 -7
- package/bundles/collaborate.js +267 -224
- package/bundles/public-api.d.ts +2 -0
- package/bundles/public-api.js +14 -1
- package/bundles/unknown.component.d.ts +1 -0
- package/bundles/unknown.component.js +22 -0
- package/package.json +7 -7
- package/src/collaborate-cursor.ts +133 -65
- package/src/collaborate.ts +264 -82
- package/src/public-api.ts +16 -0
- package/src/unknown.component.ts +22 -0
- package/tsconfig-build.json +1 -1
package/src/collaborate.ts
CHANGED
@@ -1,42 +1,100 @@
|
|
1
|
-
import { Injectable } from '@tanbo/di'
|
2
|
-
import {
|
1
|
+
import { Inject, Injectable } from '@tanbo/di'
|
2
|
+
import { delay, filter, map, Observable, Subject, Subscription } from '@tanbo/stream'
|
3
3
|
import {
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
ChangeOrigin,
|
5
|
+
ComponentInstance,
|
6
|
+
ContentType, Controller,
|
7
|
+
Formats,
|
8
|
+
History, HISTORY_STACK_SIZE,
|
9
|
+
makeError,
|
7
10
|
Registry,
|
11
|
+
RootComponentRef,
|
12
|
+
Scheduler,
|
8
13
|
Selection,
|
9
14
|
SelectionPaths,
|
10
|
-
|
15
|
+
Slot,
|
16
|
+
Starter,
|
17
|
+
Translator
|
11
18
|
} from '@textbus/core'
|
12
19
|
import {
|
20
|
+
Array as YArray,
|
13
21
|
Doc as YDoc,
|
14
22
|
Map as YMap,
|
23
|
+
RelativePosition,
|
15
24
|
Text as YText,
|
16
|
-
|
25
|
+
Transaction,
|
17
26
|
UndoManager,
|
18
|
-
|
27
|
+
createAbsolutePositionFromRelativePosition,
|
28
|
+
createRelativePositionFromTypeIndex
|
19
29
|
} from 'yjs'
|
20
30
|
|
21
31
|
import { CollaborateCursor, RemoteSelection } from './collaborate-cursor'
|
32
|
+
import { createUnknownComponent } from './unknown.component'
|
22
33
|
|
23
34
|
const collaborateErrorFn = makeError('Collaborate')
|
24
35
|
|
36
|
+
interface CursorPosition {
|
37
|
+
anchor: RelativePosition
|
38
|
+
focus: RelativePosition
|
39
|
+
}
|
40
|
+
|
41
|
+
class ContentMap {
|
42
|
+
private slotAndYTextMap = new WeakMap<Slot, YText>()
|
43
|
+
private yTextAndSLotMap = new WeakMap<YText, Slot>()
|
44
|
+
|
45
|
+
set(key: Slot, value: YText): void
|
46
|
+
set(key: YText, value: Slot): void
|
47
|
+
set(key: any, value: any) {
|
48
|
+
if (key instanceof Slot) {
|
49
|
+
this.slotAndYTextMap.set(key, value)
|
50
|
+
this.yTextAndSLotMap.set(value, key)
|
51
|
+
} else {
|
52
|
+
this.slotAndYTextMap.set(value, key)
|
53
|
+
this.yTextAndSLotMap.set(key, value)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
get(key: Slot): YText | null
|
58
|
+
get(key: YText): Slot | null
|
59
|
+
get(key: any) {
|
60
|
+
if (key instanceof Slot) {
|
61
|
+
return this.slotAndYTextMap.get(key) || null
|
62
|
+
}
|
63
|
+
return this.yTextAndSLotMap.get(key) || null
|
64
|
+
}
|
65
|
+
|
66
|
+
delete(key: Slot | YText) {
|
67
|
+
if (key instanceof Slot) {
|
68
|
+
const v = this.slotAndYTextMap.get(key)
|
69
|
+
this.slotAndYTextMap.delete(key)
|
70
|
+
if (v) {
|
71
|
+
this.yTextAndSLotMap.delete(v)
|
72
|
+
}
|
73
|
+
} else {
|
74
|
+
const v = this.yTextAndSLotMap.get(key)
|
75
|
+
this.yTextAndSLotMap.delete(key)
|
76
|
+
if (v) {
|
77
|
+
this.slotAndYTextMap.delete(v)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
25
83
|
@Injectable()
|
26
84
|
export class Collaborate implements History {
|
27
85
|
onSelectionChange: Observable<SelectionPaths>
|
28
86
|
yDoc = new YDoc()
|
29
87
|
onBack: Observable<void>
|
30
88
|
onForward: Observable<void>
|
31
|
-
onChange: Observable<
|
89
|
+
onChange: Observable<void>
|
32
90
|
onPush: Observable<void>
|
33
91
|
|
34
92
|
get canBack() {
|
35
|
-
return this.manager?.canUndo()
|
93
|
+
return this.manager?.canUndo() || false
|
36
94
|
}
|
37
95
|
|
38
96
|
get canForward() {
|
39
|
-
return this.manager?.canRedo()
|
97
|
+
return this.manager?.canRedo() || false
|
40
98
|
}
|
41
99
|
|
42
100
|
private backEvent = new Subject<void>()
|
@@ -44,7 +102,7 @@ export class Collaborate implements History {
|
|
44
102
|
private changeEvent = new Subject<void>()
|
45
103
|
private pushEvent = new Subject<void>()
|
46
104
|
|
47
|
-
private manager
|
105
|
+
private manager: UndoManager | null = null
|
48
106
|
|
49
107
|
private subscriptions: Subscription[] = []
|
50
108
|
private updateFromRemote = false
|
@@ -55,87 +113,177 @@ export class Collaborate implements History {
|
|
55
113
|
private componentStateSyncCaches = new WeakMap<ComponentInstance, () => void>()
|
56
114
|
|
57
115
|
private selectionChangeEvent = new Subject<SelectionPaths>()
|
116
|
+
private contentMap = new ContentMap()
|
58
117
|
|
59
118
|
private updateRemoteActions: Array<() => void> = []
|
60
119
|
|
61
|
-
constructor(private
|
120
|
+
constructor(@Inject(HISTORY_STACK_SIZE) private stackSize: number,
|
121
|
+
private rootComponentRef: RootComponentRef,
|
62
122
|
private collaborateCursor: CollaborateCursor,
|
123
|
+
private controller: Controller,
|
124
|
+
private scheduler: Scheduler,
|
63
125
|
private translator: Translator,
|
64
|
-
private renderer: Renderer,
|
65
126
|
private registry: Registry,
|
66
127
|
private selection: Selection,
|
67
128
|
private starter: Starter) {
|
68
|
-
this.onSelectionChange = this.selectionChangeEvent.asObservable()
|
129
|
+
this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(delay())
|
69
130
|
this.onBack = this.backEvent.asObservable()
|
70
131
|
this.onForward = this.forwardEvent.asObservable()
|
71
132
|
this.onChange = this.changeEvent.asObservable()
|
72
133
|
this.onPush = this.pushEvent.asObservable()
|
73
134
|
}
|
74
135
|
|
75
|
-
|
136
|
+
listen() {
|
137
|
+
const root = this.yDoc.getMap('RootComponent')
|
138
|
+
const rootComponent = this.rootComponentRef.component!
|
139
|
+
this.manager = new UndoManager(root, {
|
140
|
+
trackedOrigins: new Set<any>([this.yDoc])
|
141
|
+
})
|
142
|
+
const cursorKey = 'cursor-position'
|
143
|
+
this.manager.on('stack-item-added', event => {
|
144
|
+
event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation())
|
145
|
+
if (this.manager!.undoStack.length > this.stackSize) {
|
146
|
+
this.manager!.undoStack.shift()
|
147
|
+
}
|
148
|
+
if (event.origin === this.yDoc) {
|
149
|
+
this.pushEvent.next()
|
150
|
+
}
|
151
|
+
this.changeEvent.next()
|
152
|
+
})
|
153
|
+
this.manager.on('stack-item-popped', event => {
|
154
|
+
const position = event.stackItem.meta.get(cursorKey) as CursorPosition
|
155
|
+
if (position) {
|
156
|
+
this.restoreCursorLocation(position)
|
157
|
+
}
|
158
|
+
})
|
76
159
|
this.subscriptions.push(
|
77
|
-
this.starter.onReady.subscribe(() => {
|
78
|
-
this.listen2()
|
79
|
-
}),
|
80
160
|
this.selection.onChange.subscribe(() => {
|
81
161
|
const paths = this.selection.getPaths()
|
82
162
|
this.selectionChangeEvent.next(paths)
|
163
|
+
}),
|
164
|
+
this.scheduler.onDocChanged.pipe(
|
165
|
+
map(item => {
|
166
|
+
return item.filter(i => {
|
167
|
+
return i.from !== ChangeOrigin.Remote
|
168
|
+
})
|
169
|
+
}),
|
170
|
+
filter(item => {
|
171
|
+
return item.length
|
172
|
+
})
|
173
|
+
).subscribe(() => {
|
174
|
+
this.yDoc.transact(() => {
|
175
|
+
this.updateRemoteActions.forEach(fn => {
|
176
|
+
fn()
|
177
|
+
})
|
178
|
+
this.updateRemoteActions = []
|
179
|
+
}, this.yDoc)
|
83
180
|
})
|
84
181
|
)
|
182
|
+
this.syncRootComponent(root, rootComponent)
|
85
183
|
}
|
86
184
|
|
87
185
|
updateRemoteSelection(paths: RemoteSelection[]) {
|
88
186
|
this.collaborateCursor.draw(paths)
|
89
187
|
}
|
90
188
|
|
91
|
-
listen() {
|
92
|
-
//
|
93
|
-
}
|
94
|
-
|
95
189
|
back() {
|
96
190
|
if (this.canBack) {
|
97
|
-
this.manager
|
191
|
+
this.manager?.undo()
|
192
|
+
this.backEvent.next()
|
98
193
|
}
|
99
194
|
}
|
100
195
|
|
101
196
|
forward() {
|
102
197
|
if (this.canForward) {
|
103
|
-
this.manager
|
198
|
+
this.manager?.redo()
|
199
|
+
this.forwardEvent.next()
|
104
200
|
}
|
105
201
|
}
|
106
202
|
|
203
|
+
clear() {
|
204
|
+
this.manager?.clear()
|
205
|
+
this.changeEvent.next()
|
206
|
+
}
|
207
|
+
|
107
208
|
destroy() {
|
108
209
|
this.subscriptions.forEach(i => i.unsubscribe())
|
210
|
+
this.collaborateCursor.destroy()
|
211
|
+
this.manager?.destroy()
|
109
212
|
}
|
110
213
|
|
111
|
-
private
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
this.subscriptions.push(
|
120
|
-
merge(
|
121
|
-
rootComponent.changeMarker.onForceChange,
|
122
|
-
rootComponent.changeMarker.onChange
|
123
|
-
).pipe(
|
124
|
-
microTask()
|
125
|
-
).subscribe(() => {
|
126
|
-
this.yDoc.transact(() => {
|
127
|
-
this.updateRemoteActions.forEach(fn => {
|
128
|
-
fn()
|
129
|
-
})
|
130
|
-
this.updateRemoteActions = []
|
131
|
-
}, this.yDoc)
|
132
|
-
this.renderer.render()
|
133
|
-
this.selection.restore()
|
214
|
+
private syncRootComponent(root: YMap<any>, rootComponent: ComponentInstance) {
|
215
|
+
let slots = root.get('slots') as YArray<YMap<any>>
|
216
|
+
if (!slots) {
|
217
|
+
slots = new YArray()
|
218
|
+
rootComponent.slots.toArray().forEach(i => {
|
219
|
+
const sharedSlot = this.createSharedSlotBySlot(i)
|
220
|
+
slots.push([sharedSlot])
|
134
221
|
})
|
135
|
-
|
222
|
+
this.yDoc.transact(() => {
|
223
|
+
root.set('state', rootComponent.state)
|
224
|
+
root.set('slots', slots)
|
225
|
+
})
|
226
|
+
} else if (slots.length === 0) {
|
227
|
+
rootComponent.updateState(() => {
|
228
|
+
return root.get('state')
|
229
|
+
})
|
230
|
+
this.yDoc.transact(() => {
|
231
|
+
rootComponent.slots.toArray().forEach(i => {
|
232
|
+
const sharedSlot = this.createSharedSlotBySlot(i)
|
233
|
+
slots.push([sharedSlot])
|
234
|
+
})
|
235
|
+
})
|
236
|
+
} else {
|
237
|
+
rootComponent.updateState(() => {
|
238
|
+
return root.get('state')
|
239
|
+
})
|
240
|
+
rootComponent.slots.clean()
|
241
|
+
slots.forEach(sharedSlot => {
|
242
|
+
const slot = this.createSlotBySharedSlot(sharedSlot)
|
243
|
+
this.syncContent(sharedSlot.get('content'), slot)
|
244
|
+
this.syncSlot(sharedSlot, slot)
|
245
|
+
rootComponent.slots.insert(slot)
|
246
|
+
})
|
247
|
+
}
|
248
|
+
this.syncComponent(root, rootComponent)
|
249
|
+
this.syncSlots(slots, rootComponent)
|
250
|
+
}
|
251
|
+
|
252
|
+
private restoreCursorLocation(position: CursorPosition) {
|
253
|
+
const anchorPosition = createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc)
|
254
|
+
const focusPosition = createAbsolutePositionFromRelativePosition(position.focus, this.yDoc)
|
255
|
+
if (anchorPosition && focusPosition) {
|
256
|
+
const focusSlot = this.contentMap.get(focusPosition.type as YText)
|
257
|
+
const anchorSlot = this.contentMap.get(anchorPosition.type as YText)
|
258
|
+
if (focusSlot && anchorSlot) {
|
259
|
+
this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index)
|
260
|
+
}
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
private getRelativeCursorLocation(): CursorPosition | null {
|
265
|
+
const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection
|
266
|
+
if (anchorSlot) {
|
267
|
+
const anchorYText = this.contentMap.get(anchorSlot)
|
268
|
+
if (anchorYText) {
|
269
|
+
const anchorPosition = createRelativePositionFromTypeIndex(anchorYText, anchorOffset!)
|
270
|
+
if (focusSlot) {
|
271
|
+
const focusYText = this.contentMap.get(focusSlot)
|
272
|
+
if (focusYText) {
|
273
|
+
const focusPosition = createRelativePositionFromTypeIndex(focusYText, focusOffset!)
|
274
|
+
return {
|
275
|
+
focus: focusPosition,
|
276
|
+
anchor: anchorPosition
|
277
|
+
}
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
return null
|
136
283
|
}
|
137
284
|
|
138
285
|
private syncContent(content: YText, slot: Slot) {
|
286
|
+
this.contentMap.set(slot, content)
|
139
287
|
const syncRemote = (ev, tr) => {
|
140
288
|
this.runRemoteUpdate(tr, () => {
|
141
289
|
slot.retain(0)
|
@@ -146,6 +294,7 @@ export class Collaborate implements History {
|
|
146
294
|
if (formats.length) {
|
147
295
|
slot.retain(action.retain!, formats)
|
148
296
|
}
|
297
|
+
slot.retain(slot.index + action.retain)
|
149
298
|
} else {
|
150
299
|
slot.retain(action.retain)
|
151
300
|
}
|
@@ -157,17 +306,18 @@ export class Collaborate implements History {
|
|
157
306
|
slot.insert(action.insert, makeFormats(this.registry, action.attributes))
|
158
307
|
} else {
|
159
308
|
const sharedComponent = action.insert as YMap<any>
|
160
|
-
const
|
309
|
+
const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
|
310
|
+
const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
|
161
311
|
this.syncSlots(sharedComponent.get('slots'), component)
|
162
312
|
this.syncComponent(sharedComponent, component)
|
163
313
|
slot.insert(component)
|
164
314
|
}
|
165
315
|
if (this.selection.isSelected) {
|
166
|
-
if (slot === this.selection.
|
167
|
-
this.selection.
|
316
|
+
if (slot === this.selection.anchorSlot && this.selection.anchorOffset! > index) {
|
317
|
+
this.selection.setAnchor(slot, this.selection.anchorOffset! + length)
|
168
318
|
}
|
169
|
-
if (slot === this.selection.
|
170
|
-
this.selection.
|
319
|
+
if (slot === this.selection.focusSlot && this.selection.focusOffset! > index) {
|
320
|
+
this.selection.setFocus(slot, this.selection.focusOffset! + length)
|
171
321
|
}
|
172
322
|
}
|
173
323
|
} else if (action.delete) {
|
@@ -175,16 +325,20 @@ export class Collaborate implements History {
|
|
175
325
|
slot.retain(slot.index)
|
176
326
|
slot.delete(action.delete)
|
177
327
|
if (this.selection.isSelected) {
|
178
|
-
if (slot === this.selection.
|
179
|
-
this.selection.
|
328
|
+
if (slot === this.selection.anchorSlot && this.selection.anchorOffset! >= index) {
|
329
|
+
this.selection.setAnchor(slot, this.selection.startOffset! - action.delete)
|
180
330
|
}
|
181
|
-
if (slot === this.selection.
|
182
|
-
this.selection.
|
331
|
+
if (slot === this.selection.focusSlot && this.selection.focusOffset! >= index) {
|
332
|
+
this.selection.setFocus(slot, this.selection.focusOffset! - action.delete)
|
183
333
|
}
|
184
334
|
}
|
185
335
|
} else if (action.attributes) {
|
186
336
|
slot.updateState(draft => {
|
187
|
-
|
337
|
+
if (typeof draft === 'object' && draft !== null) {
|
338
|
+
Object.assign(draft, action.attributes)
|
339
|
+
} else {
|
340
|
+
return action.attributes
|
341
|
+
}
|
188
342
|
})
|
189
343
|
}
|
190
344
|
})
|
@@ -219,23 +373,22 @@ export class Collaborate implements History {
|
|
219
373
|
const isEmpty = delta.length === 1 && delta[0].insert === Slot.emptyPlaceholder
|
220
374
|
if (typeof action.content === 'string') {
|
221
375
|
length = action.content.length
|
222
|
-
content.insert(offset, action.content)
|
376
|
+
content.insert(offset, action.content, action.formats || {})
|
223
377
|
} else {
|
224
378
|
length = 1
|
225
|
-
const
|
226
|
-
const sharedComponent = this.createSharedComponentByComponent(component)
|
379
|
+
const sharedComponent = this.createSharedComponentByComponent(action.ref as ComponentInstance)
|
227
380
|
content.insertEmbed(offset, sharedComponent)
|
228
381
|
}
|
229
|
-
|
230
|
-
content.format(offset, length, action.formats)
|
231
|
-
}
|
382
|
+
|
232
383
|
if (isEmpty && offset === 0) {
|
233
384
|
content.delete(content.length - 1, 1)
|
234
385
|
}
|
235
386
|
offset += length
|
236
387
|
} else if (action.type === 'delete') {
|
237
388
|
const delta = content.toDelta()
|
238
|
-
content.
|
389
|
+
if (content.length) {
|
390
|
+
content.delete(offset, action.count)
|
391
|
+
}
|
239
392
|
if (content.length === 0) {
|
240
393
|
content.insert(0, '\n', delta[0]?.attributes)
|
241
394
|
}
|
@@ -243,6 +396,12 @@ export class Collaborate implements History {
|
|
243
396
|
}
|
244
397
|
})
|
245
398
|
})
|
399
|
+
|
400
|
+
sub.add(slot.onChildComponentRemove.subscribe(components => {
|
401
|
+
components.forEach(c => {
|
402
|
+
this.cleanSubscriptionsByComponent(c)
|
403
|
+
})
|
404
|
+
}))
|
246
405
|
this.contentSyncCaches.set(slot, () => {
|
247
406
|
content.unobserve(syncRemote)
|
248
407
|
sub.unsubscribe()
|
@@ -256,7 +415,11 @@ export class Collaborate implements History {
|
|
256
415
|
if (key === 'state') {
|
257
416
|
const state = (ev.target as YMap<any>).get('state')
|
258
417
|
slot.updateState(draft => {
|
259
|
-
|
418
|
+
if (typeof draft === 'object' && draft !== null) {
|
419
|
+
Object.assign(draft, state)
|
420
|
+
} else {
|
421
|
+
return state
|
422
|
+
}
|
260
423
|
})
|
261
424
|
}
|
262
425
|
})
|
@@ -281,18 +444,21 @@ export class Collaborate implements History {
|
|
281
444
|
const slots = component.slots
|
282
445
|
const syncRemote = (ev, tr) => {
|
283
446
|
this.runRemoteUpdate(tr, () => {
|
447
|
+
let index = 0
|
284
448
|
ev.delta.forEach(action => {
|
285
449
|
if (Reflect.has(action, 'retain')) {
|
286
|
-
|
450
|
+
index += action.retain
|
451
|
+
slots.retain(index)
|
287
452
|
} else if (action.insert) {
|
288
453
|
(action.insert as Array<YMap<any>>).forEach(item => {
|
289
454
|
const slot = this.createSlotBySharedSlot(item)
|
290
455
|
slots.insert(slot)
|
291
456
|
this.syncContent(item.get('content'), slot)
|
292
457
|
this.syncSlot(item, slot)
|
458
|
+
index++
|
293
459
|
})
|
294
460
|
} else if (action.delete) {
|
295
|
-
slots.retain(
|
461
|
+
slots.retain(index)
|
296
462
|
slots.delete(action.delete)
|
297
463
|
}
|
298
464
|
})
|
@@ -308,20 +474,22 @@ export class Collaborate implements History {
|
|
308
474
|
if (action.type === 'retain') {
|
309
475
|
index = action.offset
|
310
476
|
} else if (action.type === 'insertSlot') {
|
311
|
-
const
|
312
|
-
const sharedSlot = this.createSharedSlotBySlot(slot)
|
477
|
+
const sharedSlot = this.createSharedSlotBySlot(action.ref)
|
313
478
|
remoteSlots.insert(index, [sharedSlot])
|
314
479
|
index++
|
315
480
|
} else if (action.type === 'delete') {
|
316
|
-
slots.slice(index, index + action.count).forEach(slot => {
|
317
|
-
this.cleanSubscriptionsBySlot(slot)
|
318
|
-
})
|
319
481
|
remoteSlots.delete(index, action.count)
|
320
482
|
}
|
321
483
|
})
|
322
484
|
})
|
323
485
|
})
|
324
486
|
|
487
|
+
sub.add(slots.onChildSlotRemove.subscribe(slots => {
|
488
|
+
slots.forEach(slot => {
|
489
|
+
this.cleanSubscriptionsBySlot(slot)
|
490
|
+
})
|
491
|
+
}))
|
492
|
+
|
325
493
|
this.slotsSyncCaches.set(component, () => {
|
326
494
|
remoteSlots.unobserve(syncRemote)
|
327
495
|
sub.unsubscribe()
|
@@ -335,7 +503,11 @@ export class Collaborate implements History {
|
|
335
503
|
if (key === 'state') {
|
336
504
|
const state = (ev.target as YMap<any>).get('state')
|
337
505
|
component.updateState(draft => {
|
338
|
-
|
506
|
+
if (typeof draft === 'object' && draft !== null) {
|
507
|
+
Object.assign(draft, state)
|
508
|
+
} else {
|
509
|
+
return state
|
510
|
+
}
|
339
511
|
})
|
340
512
|
}
|
341
513
|
})
|
@@ -355,7 +527,7 @@ export class Collaborate implements History {
|
|
355
527
|
}
|
356
528
|
|
357
529
|
private runLocalUpdate(fn: () => void) {
|
358
|
-
if (this.updateFromRemote) {
|
530
|
+
if (this.updateFromRemote || this.controller.readonly) {
|
359
531
|
return
|
360
532
|
}
|
361
533
|
this.updateRemoteActions.push(fn)
|
@@ -366,7 +538,11 @@ export class Collaborate implements History {
|
|
366
538
|
return
|
367
539
|
}
|
368
540
|
this.updateFromRemote = true
|
369
|
-
|
541
|
+
if (tr.origin === this.manager) {
|
542
|
+
this.scheduler.historyApplyTransact(fn)
|
543
|
+
} else {
|
544
|
+
this.scheduler.remoteUpdateTransact(fn)
|
545
|
+
}
|
370
546
|
this.updateFromRemote = false
|
371
547
|
}
|
372
548
|
|
@@ -414,7 +590,7 @@ export class Collaborate implements History {
|
|
414
590
|
return sharedSlot
|
415
591
|
}
|
416
592
|
|
417
|
-
private createComponentBySharedComponent(yMap: YMap<any
|
593
|
+
private createComponentBySharedComponent(yMap: YMap<any>, canInsertInlineComponent: boolean): ComponentInstance {
|
418
594
|
const sharedSlots = yMap.get('slots') as YArray<YMap<any>>
|
419
595
|
const slots: Slot[] = []
|
420
596
|
sharedSlots.forEach(sharedSlot => {
|
@@ -428,13 +604,17 @@ export class Collaborate implements History {
|
|
428
604
|
})
|
429
605
|
if (instance) {
|
430
606
|
instance.slots.toArray().forEach((slot, index) => {
|
431
|
-
|
607
|
+
let sharedSlot = sharedSlots.get(index)
|
608
|
+
if (!sharedSlot) {
|
609
|
+
sharedSlot = this.createSharedSlotBySlot(slot)
|
610
|
+
sharedSlots.push([sharedSlot])
|
611
|
+
}
|
432
612
|
this.syncSlot(sharedSlot, slot)
|
433
613
|
this.syncContent(sharedSlot.get('content'), slot)
|
434
614
|
})
|
435
615
|
return instance
|
436
616
|
}
|
437
|
-
|
617
|
+
return createUnknownComponent(name, canInsertInlineComponent).createInstance(this.starter)
|
438
618
|
}
|
439
619
|
|
440
620
|
private createSlotBySharedSlot(sharedSlot: YMap<any>): Slot {
|
@@ -454,7 +634,8 @@ export class Collaborate implements History {
|
|
454
634
|
slot.insert(action.insert, makeFormats(this.registry, action.attributes))
|
455
635
|
} else {
|
456
636
|
const sharedComponent = action.insert as YMap<any>
|
457
|
-
const
|
637
|
+
const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
|
638
|
+
const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
|
458
639
|
slot.insert(component)
|
459
640
|
this.syncSlots(sharedComponent.get('slots'), component)
|
460
641
|
this.syncComponent(sharedComponent, component)
|
@@ -467,6 +648,7 @@ export class Collaborate implements History {
|
|
467
648
|
}
|
468
649
|
|
469
650
|
private cleanSubscriptionsBySlot(slot: Slot) {
|
651
|
+
this.contentMap.delete(slot);
|
470
652
|
[this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
|
471
653
|
if (fn) {
|
472
654
|
fn()
|
@@ -494,7 +676,7 @@ export class Collaborate implements History {
|
|
494
676
|
function makeFormats(registry: Registry, attrs?: any) {
|
495
677
|
const formats: Formats = []
|
496
678
|
if (attrs) {
|
497
|
-
Object.keys(attrs).
|
679
|
+
Object.keys(attrs).forEach(key => {
|
498
680
|
const formatter = registry.getFormatter(key)
|
499
681
|
if (formatter) {
|
500
682
|
formats.push([formatter, attrs[key]])
|
package/src/public-api.ts
CHANGED
@@ -1,2 +1,18 @@
|
|
1
|
+
import { History, Module } from '@textbus/core'
|
2
|
+
|
3
|
+
import { Collaborate } from './collaborate'
|
4
|
+
import { CollaborateCursor } from './collaborate-cursor'
|
5
|
+
|
1
6
|
export * from './collaborate'
|
2
7
|
export * from './collaborate-cursor'
|
8
|
+
|
9
|
+
export const collaborateModule: Module = {
|
10
|
+
providers: [
|
11
|
+
Collaborate,
|
12
|
+
CollaborateCursor,
|
13
|
+
{
|
14
|
+
provide: History,
|
15
|
+
useClass: Collaborate
|
16
|
+
}
|
17
|
+
]
|
18
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { ContentType, defineComponent, VElement } from '@textbus/core'
|
2
|
+
|
3
|
+
export function createUnknownComponent(factoryName: string, canInsertInlineComponent: boolean) {
|
4
|
+
const unknownComponent = defineComponent({
|
5
|
+
type: canInsertInlineComponent ? ContentType.InlineComponent : ContentType.BlockComponent,
|
6
|
+
name: 'UnknownComponent',
|
7
|
+
setup() {
|
8
|
+
console.error(`cannot find component factory \`${factoryName}\`.`)
|
9
|
+
return {
|
10
|
+
render() {
|
11
|
+
return VElement.createElement('textbus-unknown-component', {
|
12
|
+
style: {
|
13
|
+
display: canInsertInlineComponent ? 'inline' : 'block',
|
14
|
+
color: '#f00'
|
15
|
+
}
|
16
|
+
}, unknownComponent.name)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
})
|
21
|
+
return unknownComponent
|
22
|
+
}
|