@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.
@@ -1,42 +1,100 @@
1
- import { Injectable } from '@tanbo/di'
2
- import { merge, microTask, Observable, Subject, Subscription } from '@tanbo/stream'
1
+ import { Inject, Injectable } from '@tanbo/di'
2
+ import { delay, filter, map, Observable, Subject, Subscription } from '@tanbo/stream'
3
3
  import {
4
- RootComponentRef,
5
- Starter,
6
- Translator,
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
- History, Renderer, Slot, ComponentInstance, makeError, Formats
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
- Array as YArray,
25
+ Transaction,
17
26
  UndoManager,
18
- Transaction
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<any>
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!: UndoManager
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 rootComponentRef: RootComponentRef,
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
- setup() {
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.undo()
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.redo()
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 listen2() {
112
- const root = this.yDoc.getText('content')
113
- const rootComponent = this.rootComponentRef.component!
114
- this.manager = new UndoManager(root, {
115
- trackedOrigins: new Set<any>([this.yDoc])
116
- })
117
- this.syncContent(root, rootComponent.slots.get(0)!)
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 component = this.createComponentBySharedComponent(sharedComponent)
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.startSlot && this.selection.startOffset! >= index) {
167
- this.selection.setStart(slot, this.selection.startOffset! + length)
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.endSlot && this.selection.endOffset! >= index) {
170
- this.selection.setEnd(slot, this.selection.endOffset! + length)
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.startSlot && this.selection.startOffset! >= index) {
179
- this.selection.setStart(slot, this.selection.startOffset! - action.delete)
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.endSlot && this.selection.endOffset! >= index) {
182
- this.selection.setEnd(slot, this.selection.endOffset! - action.delete)
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
- Object.assign(draft, action.attributes)
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 component = slot.getContentAtIndex(offset) as ComponentInstance
226
- const sharedComponent = this.createSharedComponentByComponent(component)
379
+ const sharedComponent = this.createSharedComponentByComponent(action.ref as ComponentInstance)
227
380
  content.insertEmbed(offset, sharedComponent)
228
381
  }
229
- if (action.formats) {
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.delete(offset, action.count)
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
- Object.assign(draft, state)
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
- slots.retain(action.retain!)
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(slots.index)
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 slot = slots.get(index)!
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
- Object.assign(draft, state)
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
- fn()
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>): ComponentInstance {
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
- const sharedSlot = sharedSlots.get(index)
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
- throw collaborateErrorFn(`cannot find component factory \`${name}\`.`)
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 component = this.createComponentBySharedComponent(sharedComponent)
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).map(key => {
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
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "declaration": true,
4
- "useDefineForClassFields": true,
4
+ "useDefineForClassFields": false,
5
5
  "emitDecoratorMetadata": true,
6
6
  "experimentalDecorators": true,
7
7
  "allowSyntheticDefaultImports": true,