@textbus/collaborate 2.0.0-beta.4 → 2.0.0-beta.40
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/{collab/collaborate-cursor.d.ts → collaborate-cursor.d.ts} +17 -6
- package/bundles/collaborate-cursor.js +327 -0
- package/bundles/collaborate.d.ts +11 -5
- package/bundles/collaborate.js +206 -51
- package/bundles/fixed-caret.plugin.d.ts +9 -0
- package/bundles/fixed-caret.plugin.js +41 -0
- package/bundles/public-api.d.ts +4 -1
- package/bundles/public-api.js +16 -2
- 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 +293 -0
- package/src/collaborate.ts +194 -54
- package/src/fixed-caret.plugin.ts +35 -0
- package/src/public-api.ts +18 -1
- package/src/unknown.component.ts +22 -0
- package/bundles/collab/_api.d.ts +0 -1
- package/bundles/collab/_api.js +0 -2
- package/bundles/collab/collaborate-cursor.js +0 -245
- package/src/collab/_api.ts +0 -1
- package/src/collab/collaborate-cursor.ts +0 -225
package/src/collaborate.ts
CHANGED
|
@@ -1,27 +1,83 @@
|
|
|
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, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex,
|
|
13
21
|
Doc as YDoc,
|
|
14
22
|
Map as YMap,
|
|
23
|
+
RelativePosition,
|
|
15
24
|
Text as YText,
|
|
16
|
-
|
|
17
|
-
UndoManager
|
|
18
|
-
Transaction
|
|
25
|
+
Transaction,
|
|
26
|
+
UndoManager
|
|
19
27
|
} from 'yjs'
|
|
20
28
|
|
|
21
|
-
import { CollaborateCursor, RemoteSelection } from './
|
|
29
|
+
import { CollaborateCursor, RemoteSelection } from './collaborate-cursor'
|
|
30
|
+
import { createUnknownComponent } from './unknown.component'
|
|
22
31
|
|
|
23
32
|
const collaborateErrorFn = makeError('Collaborate')
|
|
24
33
|
|
|
34
|
+
interface CursorPosition {
|
|
35
|
+
anchor: RelativePosition
|
|
36
|
+
focus: RelativePosition
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class ContentMap {
|
|
40
|
+
private slotAndYTextMap = new WeakMap<Slot, YText>()
|
|
41
|
+
private yTextAndSLotMap = new WeakMap<YText, Slot>()
|
|
42
|
+
|
|
43
|
+
set(key: Slot, value: YText): void
|
|
44
|
+
set(key: YText, value: Slot): void
|
|
45
|
+
set(key: any, value: any) {
|
|
46
|
+
if (key instanceof Slot) {
|
|
47
|
+
this.slotAndYTextMap.set(key, value)
|
|
48
|
+
this.yTextAndSLotMap.set(value, key)
|
|
49
|
+
} else {
|
|
50
|
+
this.slotAndYTextMap.set(value, key)
|
|
51
|
+
this.yTextAndSLotMap.set(key, value)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get(key: Slot): YText | null
|
|
56
|
+
get(key: YText): Slot | null
|
|
57
|
+
get(key: any) {
|
|
58
|
+
if (key instanceof Slot) {
|
|
59
|
+
return this.slotAndYTextMap.get(key) || null
|
|
60
|
+
}
|
|
61
|
+
return this.yTextAndSLotMap.get(key) || null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
delete(key: Slot | YText) {
|
|
65
|
+
if (key instanceof Slot) {
|
|
66
|
+
const v = this.slotAndYTextMap.get(key)
|
|
67
|
+
this.slotAndYTextMap.delete(key)
|
|
68
|
+
if (v) {
|
|
69
|
+
this.yTextAndSLotMap.delete(v)
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const v = this.yTextAndSLotMap.get(key)
|
|
73
|
+
this.yTextAndSLotMap.delete(key)
|
|
74
|
+
if (v) {
|
|
75
|
+
this.slotAndYTextMap.delete(v)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
25
81
|
@Injectable()
|
|
26
82
|
export class Collaborate implements History {
|
|
27
83
|
onSelectionChange: Observable<SelectionPaths>
|
|
@@ -32,11 +88,11 @@ export class Collaborate implements History {
|
|
|
32
88
|
onPush: Observable<void>
|
|
33
89
|
|
|
34
90
|
get canBack() {
|
|
35
|
-
return this.manager?.canUndo()
|
|
91
|
+
return this.manager?.canUndo() || false
|
|
36
92
|
}
|
|
37
93
|
|
|
38
94
|
get canForward() {
|
|
39
|
-
return this.manager?.canRedo()
|
|
95
|
+
return this.manager?.canRedo() || false
|
|
40
96
|
}
|
|
41
97
|
|
|
42
98
|
private backEvent = new Subject<void>()
|
|
@@ -44,7 +100,7 @@ export class Collaborate implements History {
|
|
|
44
100
|
private changeEvent = new Subject<void>()
|
|
45
101
|
private pushEvent = new Subject<void>()
|
|
46
102
|
|
|
47
|
-
private manager
|
|
103
|
+
private manager: UndoManager | null = null
|
|
48
104
|
|
|
49
105
|
private subscriptions: Subscription[] = []
|
|
50
106
|
private updateFromRemote = false
|
|
@@ -55,17 +111,20 @@ export class Collaborate implements History {
|
|
|
55
111
|
private componentStateSyncCaches = new WeakMap<ComponentInstance, () => void>()
|
|
56
112
|
|
|
57
113
|
private selectionChangeEvent = new Subject<SelectionPaths>()
|
|
114
|
+
private contentMap = new ContentMap()
|
|
58
115
|
|
|
59
116
|
private updateRemoteActions: Array<() => void> = []
|
|
60
117
|
|
|
61
|
-
constructor(private
|
|
118
|
+
constructor(@Inject(HISTORY_STACK_SIZE) private stackSize: number,
|
|
119
|
+
private rootComponentRef: RootComponentRef,
|
|
62
120
|
private collaborateCursor: CollaborateCursor,
|
|
121
|
+
private controller: Controller,
|
|
122
|
+
private scheduler: Scheduler,
|
|
63
123
|
private translator: Translator,
|
|
64
|
-
private renderer: Renderer,
|
|
65
124
|
private registry: Registry,
|
|
66
125
|
private selection: Selection,
|
|
67
126
|
private starter: Starter) {
|
|
68
|
-
this.onSelectionChange = this.selectionChangeEvent.asObservable()
|
|
127
|
+
this.onSelectionChange = this.selectionChangeEvent.asObservable().pipe(delay())
|
|
69
128
|
this.onBack = this.backEvent.asObservable()
|
|
70
129
|
this.onForward = this.forwardEvent.asObservable()
|
|
71
130
|
this.onChange = this.changeEvent.asObservable()
|
|
@@ -74,14 +133,12 @@ export class Collaborate implements History {
|
|
|
74
133
|
|
|
75
134
|
setup() {
|
|
76
135
|
this.subscriptions.push(
|
|
77
|
-
this.starter.onReady.subscribe(() => {
|
|
78
|
-
this.listen2()
|
|
79
|
-
}),
|
|
80
136
|
this.selection.onChange.subscribe(() => {
|
|
81
137
|
const paths = this.selection.getPaths()
|
|
82
138
|
this.selectionChangeEvent.next(paths)
|
|
83
139
|
})
|
|
84
140
|
)
|
|
141
|
+
this.syncRootComponent()
|
|
85
142
|
}
|
|
86
143
|
|
|
87
144
|
updateRemoteSelection(paths: RemoteSelection[]) {
|
|
@@ -94,34 +151,64 @@ export class Collaborate implements History {
|
|
|
94
151
|
|
|
95
152
|
back() {
|
|
96
153
|
if (this.canBack) {
|
|
97
|
-
this.manager
|
|
154
|
+
this.manager?.undo()
|
|
155
|
+
this.backEvent.next()
|
|
98
156
|
}
|
|
99
157
|
}
|
|
100
158
|
|
|
101
159
|
forward() {
|
|
102
160
|
if (this.canForward) {
|
|
103
|
-
this.manager
|
|
161
|
+
this.manager?.redo()
|
|
162
|
+
this.forwardEvent.next()
|
|
104
163
|
}
|
|
105
164
|
}
|
|
106
165
|
|
|
166
|
+
clear() {
|
|
167
|
+
this.manager?.clear()
|
|
168
|
+
this.changeEvent.next()
|
|
169
|
+
}
|
|
170
|
+
|
|
107
171
|
destroy() {
|
|
108
172
|
this.subscriptions.forEach(i => i.unsubscribe())
|
|
173
|
+
this.collaborateCursor.destroy()
|
|
174
|
+
this.manager?.destroy()
|
|
109
175
|
}
|
|
110
176
|
|
|
111
|
-
private
|
|
177
|
+
private syncRootComponent() {
|
|
112
178
|
const root = this.yDoc.getText('content')
|
|
113
179
|
const rootComponent = this.rootComponentRef.component!
|
|
114
180
|
this.manager = new UndoManager(root, {
|
|
115
181
|
trackedOrigins: new Set<any>([this.yDoc])
|
|
116
182
|
})
|
|
183
|
+
const cursorKey = 'cursor-position'
|
|
184
|
+
this.manager.on('stack-item-added', event => {
|
|
185
|
+
event.stackItem.meta.set(cursorKey, this.getRelativeCursorLocation())
|
|
186
|
+
if (this.manager!.undoStack.length > this.stackSize) {
|
|
187
|
+
this.manager!.undoStack.shift()
|
|
188
|
+
}
|
|
189
|
+
if (event.origin === this.yDoc) {
|
|
190
|
+
this.pushEvent.next()
|
|
191
|
+
}
|
|
192
|
+
this.changeEvent.next()
|
|
193
|
+
})
|
|
194
|
+
this.manager.on('stack-item-popped', event => {
|
|
195
|
+
const position = event.stackItem.meta.get(cursorKey) as CursorPosition
|
|
196
|
+
if (position) {
|
|
197
|
+
this.restoreCursorLocation(position)
|
|
198
|
+
}
|
|
199
|
+
})
|
|
117
200
|
this.syncContent(root, rootComponent.slots.get(0)!)
|
|
118
201
|
|
|
119
202
|
this.subscriptions.push(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
203
|
+
this.scheduler.onDocChange.pipe(
|
|
204
|
+
map(item => {
|
|
205
|
+
return item.filter(i => {
|
|
206
|
+
return i.from === ChangeOrigin.Local
|
|
207
|
+
})
|
|
208
|
+
}),
|
|
209
|
+
filter(item => {
|
|
210
|
+
return item.length
|
|
211
|
+
})
|
|
125
212
|
).subscribe(() => {
|
|
126
213
|
this.yDoc.transact(() => {
|
|
127
214
|
this.updateRemoteActions.forEach(fn => {
|
|
@@ -129,13 +216,45 @@ export class Collaborate implements History {
|
|
|
129
216
|
})
|
|
130
217
|
this.updateRemoteActions = []
|
|
131
218
|
}, this.yDoc)
|
|
132
|
-
this.renderer.render()
|
|
133
|
-
this.selection.restore()
|
|
134
219
|
})
|
|
135
220
|
)
|
|
136
221
|
}
|
|
137
222
|
|
|
223
|
+
private restoreCursorLocation(position: CursorPosition) {
|
|
224
|
+
const anchorPosition = createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc)
|
|
225
|
+
const focusPosition = createAbsolutePositionFromRelativePosition(position.focus, this.yDoc)
|
|
226
|
+
if (anchorPosition && focusPosition) {
|
|
227
|
+
const focusSlot = this.contentMap.get(focusPosition.type as YText)
|
|
228
|
+
const anchorSlot = this.contentMap.get(anchorPosition.type as YText)
|
|
229
|
+
if (focusSlot && anchorSlot) {
|
|
230
|
+
this.selection.setBaseAndExtent(anchorSlot, anchorPosition.index, focusSlot, focusPosition.index)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private getRelativeCursorLocation(): CursorPosition | null {
|
|
236
|
+
const {anchorSlot, anchorOffset, focusSlot, focusOffset} = this.selection
|
|
237
|
+
if (anchorSlot) {
|
|
238
|
+
const anchorYText = this.contentMap.get(anchorSlot)
|
|
239
|
+
if (anchorYText) {
|
|
240
|
+
const anchorPosition = createRelativePositionFromTypeIndex(anchorYText, anchorOffset!)
|
|
241
|
+
if (focusSlot) {
|
|
242
|
+
const focusYText = this.contentMap.get(focusSlot)
|
|
243
|
+
if (focusYText) {
|
|
244
|
+
const focusPosition = createRelativePositionFromTypeIndex(focusYText, focusOffset!)
|
|
245
|
+
return {
|
|
246
|
+
focus: focusPosition,
|
|
247
|
+
anchor: anchorPosition
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return null
|
|
254
|
+
}
|
|
255
|
+
|
|
138
256
|
private syncContent(content: YText, slot: Slot) {
|
|
257
|
+
this.contentMap.set(slot, content)
|
|
139
258
|
const syncRemote = (ev, tr) => {
|
|
140
259
|
this.runRemoteUpdate(tr, () => {
|
|
141
260
|
slot.retain(0)
|
|
@@ -157,17 +276,18 @@ export class Collaborate implements History {
|
|
|
157
276
|
slot.insert(action.insert, makeFormats(this.registry, action.attributes))
|
|
158
277
|
} else {
|
|
159
278
|
const sharedComponent = action.insert as YMap<any>
|
|
160
|
-
const
|
|
279
|
+
const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
|
|
280
|
+
const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
|
|
161
281
|
this.syncSlots(sharedComponent.get('slots'), component)
|
|
162
282
|
this.syncComponent(sharedComponent, component)
|
|
163
283
|
slot.insert(component)
|
|
164
284
|
}
|
|
165
285
|
if (this.selection.isSelected) {
|
|
166
|
-
if (slot === this.selection.
|
|
167
|
-
this.selection.
|
|
286
|
+
if (slot === this.selection.anchorSlot && this.selection.anchorOffset! > index) {
|
|
287
|
+
this.selection.setAnchor(slot, this.selection.anchorOffset! + length)
|
|
168
288
|
}
|
|
169
|
-
if (slot === this.selection.
|
|
170
|
-
this.selection.
|
|
289
|
+
if (slot === this.selection.focusSlot && this.selection.focusOffset! > index) {
|
|
290
|
+
this.selection.setFocus(slot, this.selection.focusOffset! + length)
|
|
171
291
|
}
|
|
172
292
|
}
|
|
173
293
|
} else if (action.delete) {
|
|
@@ -175,11 +295,11 @@ export class Collaborate implements History {
|
|
|
175
295
|
slot.retain(slot.index)
|
|
176
296
|
slot.delete(action.delete)
|
|
177
297
|
if (this.selection.isSelected) {
|
|
178
|
-
if (slot === this.selection.
|
|
179
|
-
this.selection.
|
|
298
|
+
if (slot === this.selection.anchorSlot && this.selection.anchorOffset! >= index) {
|
|
299
|
+
this.selection.setAnchor(slot, this.selection.startOffset! - action.delete)
|
|
180
300
|
}
|
|
181
|
-
if (slot === this.selection.
|
|
182
|
-
this.selection.
|
|
301
|
+
if (slot === this.selection.focusSlot && this.selection.focusOffset! >= index) {
|
|
302
|
+
this.selection.setFocus(slot, this.selection.focusOffset! - action.delete)
|
|
183
303
|
}
|
|
184
304
|
}
|
|
185
305
|
} else if (action.attributes) {
|
|
@@ -219,16 +339,14 @@ export class Collaborate implements History {
|
|
|
219
339
|
const isEmpty = delta.length === 1 && delta[0].insert === Slot.emptyPlaceholder
|
|
220
340
|
if (typeof action.content === 'string') {
|
|
221
341
|
length = action.content.length
|
|
222
|
-
content.insert(offset, action.content)
|
|
342
|
+
content.insert(offset, action.content, action.formats || {})
|
|
223
343
|
} else {
|
|
224
344
|
length = 1
|
|
225
345
|
const component = slot.getContentAtIndex(offset) as ComponentInstance
|
|
226
346
|
const sharedComponent = this.createSharedComponentByComponent(component)
|
|
227
347
|
content.insertEmbed(offset, sharedComponent)
|
|
228
348
|
}
|
|
229
|
-
|
|
230
|
-
content.format(offset, length, action.formats)
|
|
231
|
-
}
|
|
349
|
+
|
|
232
350
|
if (isEmpty && offset === 0) {
|
|
233
351
|
content.delete(content.length - 1, 1)
|
|
234
352
|
}
|
|
@@ -243,6 +361,12 @@ export class Collaborate implements History {
|
|
|
243
361
|
}
|
|
244
362
|
})
|
|
245
363
|
})
|
|
364
|
+
|
|
365
|
+
sub.add(slot.onChildComponentRemove.subscribe(components => {
|
|
366
|
+
components.forEach(c => {
|
|
367
|
+
this.cleanSubscriptionsByComponent(c)
|
|
368
|
+
})
|
|
369
|
+
}))
|
|
246
370
|
this.contentSyncCaches.set(slot, () => {
|
|
247
371
|
content.unobserve(syncRemote)
|
|
248
372
|
sub.unsubscribe()
|
|
@@ -281,18 +405,21 @@ export class Collaborate implements History {
|
|
|
281
405
|
const slots = component.slots
|
|
282
406
|
const syncRemote = (ev, tr) => {
|
|
283
407
|
this.runRemoteUpdate(tr, () => {
|
|
408
|
+
let index = 0
|
|
284
409
|
ev.delta.forEach(action => {
|
|
285
410
|
if (Reflect.has(action, 'retain')) {
|
|
286
|
-
|
|
411
|
+
index += action.retain
|
|
412
|
+
slots.retain(index)
|
|
287
413
|
} else if (action.insert) {
|
|
288
414
|
(action.insert as Array<YMap<any>>).forEach(item => {
|
|
289
415
|
const slot = this.createSlotBySharedSlot(item)
|
|
290
416
|
slots.insert(slot)
|
|
291
417
|
this.syncContent(item.get('content'), slot)
|
|
292
418
|
this.syncSlot(item, slot)
|
|
419
|
+
index++
|
|
293
420
|
})
|
|
294
421
|
} else if (action.delete) {
|
|
295
|
-
slots.retain(
|
|
422
|
+
slots.retain(index)
|
|
296
423
|
slots.delete(action.delete)
|
|
297
424
|
}
|
|
298
425
|
})
|
|
@@ -313,15 +440,18 @@ export class Collaborate implements History {
|
|
|
313
440
|
remoteSlots.insert(index, [sharedSlot])
|
|
314
441
|
index++
|
|
315
442
|
} else if (action.type === 'delete') {
|
|
316
|
-
slots.slice(index, index + action.count).forEach(slot => {
|
|
317
|
-
this.cleanSubscriptionsBySlot(slot)
|
|
318
|
-
})
|
|
319
443
|
remoteSlots.delete(index, action.count)
|
|
320
444
|
}
|
|
321
445
|
})
|
|
322
446
|
})
|
|
323
447
|
})
|
|
324
448
|
|
|
449
|
+
sub.add(slots.onChildSlotRemove.subscribe(slots => {
|
|
450
|
+
slots.forEach(slot => {
|
|
451
|
+
this.cleanSubscriptionsBySlot(slot)
|
|
452
|
+
})
|
|
453
|
+
}))
|
|
454
|
+
|
|
325
455
|
this.slotsSyncCaches.set(component, () => {
|
|
326
456
|
remoteSlots.unobserve(syncRemote)
|
|
327
457
|
sub.unsubscribe()
|
|
@@ -355,7 +485,7 @@ export class Collaborate implements History {
|
|
|
355
485
|
}
|
|
356
486
|
|
|
357
487
|
private runLocalUpdate(fn: () => void) {
|
|
358
|
-
if (this.updateFromRemote) {
|
|
488
|
+
if (this.updateFromRemote || this.controller.readonly) {
|
|
359
489
|
return
|
|
360
490
|
}
|
|
361
491
|
this.updateRemoteActions.push(fn)
|
|
@@ -366,7 +496,11 @@ export class Collaborate implements History {
|
|
|
366
496
|
return
|
|
367
497
|
}
|
|
368
498
|
this.updateFromRemote = true
|
|
369
|
-
|
|
499
|
+
if (tr.origin === this.manager) {
|
|
500
|
+
this.scheduler.historyApplyTransact(fn)
|
|
501
|
+
} else {
|
|
502
|
+
this.scheduler.remoteUpdateTransact(fn)
|
|
503
|
+
}
|
|
370
504
|
this.updateFromRemote = false
|
|
371
505
|
}
|
|
372
506
|
|
|
@@ -414,7 +548,7 @@ export class Collaborate implements History {
|
|
|
414
548
|
return sharedSlot
|
|
415
549
|
}
|
|
416
550
|
|
|
417
|
-
private createComponentBySharedComponent(yMap: YMap<any
|
|
551
|
+
private createComponentBySharedComponent(yMap: YMap<any>, canInsertInlineComponent: boolean): ComponentInstance {
|
|
418
552
|
const sharedSlots = yMap.get('slots') as YArray<YMap<any>>
|
|
419
553
|
const slots: Slot[] = []
|
|
420
554
|
sharedSlots.forEach(sharedSlot => {
|
|
@@ -428,13 +562,17 @@ export class Collaborate implements History {
|
|
|
428
562
|
})
|
|
429
563
|
if (instance) {
|
|
430
564
|
instance.slots.toArray().forEach((slot, index) => {
|
|
431
|
-
|
|
565
|
+
let sharedSlot = sharedSlots.get(index)
|
|
566
|
+
if (!sharedSlot) {
|
|
567
|
+
sharedSlot = this.createSharedSlotBySlot(slot)
|
|
568
|
+
sharedSlots.push([sharedSlot])
|
|
569
|
+
}
|
|
432
570
|
this.syncSlot(sharedSlot, slot)
|
|
433
571
|
this.syncContent(sharedSlot.get('content'), slot)
|
|
434
572
|
})
|
|
435
573
|
return instance
|
|
436
574
|
}
|
|
437
|
-
|
|
575
|
+
return createUnknownComponent(name, canInsertInlineComponent).createInstance(this.starter)
|
|
438
576
|
}
|
|
439
577
|
|
|
440
578
|
private createSlotBySharedSlot(sharedSlot: YMap<any>): Slot {
|
|
@@ -454,7 +592,8 @@ export class Collaborate implements History {
|
|
|
454
592
|
slot.insert(action.insert, makeFormats(this.registry, action.attributes))
|
|
455
593
|
} else {
|
|
456
594
|
const sharedComponent = action.insert as YMap<any>
|
|
457
|
-
const
|
|
595
|
+
const canInsertInlineComponent = slot.schema.includes(ContentType.InlineComponent)
|
|
596
|
+
const component = this.createComponentBySharedComponent(sharedComponent, canInsertInlineComponent)
|
|
458
597
|
slot.insert(component)
|
|
459
598
|
this.syncSlots(sharedComponent.get('slots'), component)
|
|
460
599
|
this.syncComponent(sharedComponent, component)
|
|
@@ -467,6 +606,7 @@ export class Collaborate implements History {
|
|
|
467
606
|
}
|
|
468
607
|
|
|
469
608
|
private cleanSubscriptionsBySlot(slot: Slot) {
|
|
609
|
+
this.contentMap.delete(slot);
|
|
470
610
|
[this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
|
|
471
611
|
if (fn) {
|
|
472
612
|
fn()
|
|
@@ -494,7 +634,7 @@ export class Collaborate implements History {
|
|
|
494
634
|
function makeFormats(registry: Registry, attrs?: any) {
|
|
495
635
|
const formats: Formats = []
|
|
496
636
|
if (attrs) {
|
|
497
|
-
Object.keys(attrs).
|
|
637
|
+
Object.keys(attrs).forEach(key => {
|
|
498
638
|
const formatter = registry.getFormatter(key)
|
|
499
639
|
if (formatter) {
|
|
500
640
|
formats.push([formatter, attrs[key]])
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Plugin, Renderer, Scheduler } from '@textbus/core'
|
|
2
|
+
import { Injector } from '@tanbo/di'
|
|
3
|
+
import { Caret, CaretPosition } from '@textbus/browser'
|
|
4
|
+
import { Subscription } from '@tanbo/stream'
|
|
5
|
+
|
|
6
|
+
export class FixedCaretPlugin implements Plugin {
|
|
7
|
+
private subscriptions = new Subscription()
|
|
8
|
+
|
|
9
|
+
constructor(public scrollContainer: HTMLElement) {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setup(injector: Injector) {
|
|
13
|
+
const scheduler = injector.get(Scheduler)
|
|
14
|
+
const caret = injector.get(Caret)
|
|
15
|
+
const renderer = injector.get(Renderer)
|
|
16
|
+
|
|
17
|
+
let isChanged = false
|
|
18
|
+
let caretPosition: CaretPosition | null = null
|
|
19
|
+
renderer.onViewChecked.subscribe(() => {
|
|
20
|
+
isChanged = true
|
|
21
|
+
})
|
|
22
|
+
this.subscriptions.add(caret.onPositionChange.subscribe(position => {
|
|
23
|
+
if (isChanged && caretPosition && position && !scheduler.hasLocalUpdate) {
|
|
24
|
+
const offset = position.top - caretPosition.top
|
|
25
|
+
this.scrollContainer.scrollTop += offset
|
|
26
|
+
isChanged = false
|
|
27
|
+
}
|
|
28
|
+
caretPosition = position
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onDestroy() {
|
|
33
|
+
this.subscriptions.unsubscribe()
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/public-api.ts
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { History, Module } from '@textbus/core'
|
|
2
|
+
|
|
3
|
+
import { Collaborate } from './collaborate'
|
|
4
|
+
import { CollaborateCursor } from './collaborate-cursor'
|
|
5
|
+
|
|
2
6
|
export * from './collaborate'
|
|
7
|
+
export * from './collaborate-cursor'
|
|
8
|
+
export * from './fixed-caret.plugin'
|
|
9
|
+
|
|
10
|
+
export const collaborateModule: Module = {
|
|
11
|
+
providers: [
|
|
12
|
+
Collaborate,
|
|
13
|
+
CollaborateCursor,
|
|
14
|
+
{
|
|
15
|
+
provide: History,
|
|
16
|
+
useClass: Collaborate
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -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
|
+
}
|
package/bundles/collab/_api.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './collaborate-cursor';
|
package/bundles/collab/_api.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export * from './collaborate-cursor';
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2FwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb2xsYWIvX2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNCQUFzQixDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0ICogZnJvbSAnLi9jb2xsYWJvcmF0ZS1jdXJzb3InXG4iXX0=
|