@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,2 +1,4 @@
1
+ import { Module } from '@textbus/core';
1
2
  export * from './collaborate';
2
3
  export * from './collaborate-cursor';
4
+ export declare const collaborateModule: Module;
@@ -1,3 +1,16 @@
1
+ import { History } from '@textbus/core';
2
+ import { Collaborate } from './collaborate';
3
+ import { CollaborateCursor } from './collaborate-cursor';
1
4
  export * from './collaborate';
2
5
  export * from './collaborate-cursor';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsZUFBZSxDQUFBO0FBQzdCLGNBQWMsc0JBQXNCLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICcuL2NvbGxhYm9yYXRlJ1xuZXhwb3J0ICogZnJvbSAnLi9jb2xsYWJvcmF0ZS1jdXJzb3InXG4iXX0=
6
+ export const collaborateModule = {
7
+ providers: [
8
+ Collaborate,
9
+ CollaborateCursor,
10
+ {
11
+ provide: History,
12
+ useClass: Collaborate
13
+ }
14
+ ]
15
+ };
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxPQUFPLEVBQVUsTUFBTSxlQUFlLENBQUE7QUFFL0MsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGVBQWUsQ0FBQTtBQUMzQyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQTtBQUV4RCxjQUFjLGVBQWUsQ0FBQTtBQUM3QixjQUFjLHNCQUFzQixDQUFBO0FBRXBDLE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFXO0lBQ3ZDLFNBQVMsRUFBRTtRQUNULFdBQVc7UUFDWCxpQkFBaUI7UUFDakI7WUFDRSxPQUFPLEVBQUUsT0FBTztZQUNoQixRQUFRLEVBQUUsV0FBVztTQUN0QjtLQUNGO0NBQ0YsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEhpc3RvcnksIE1vZHVsZSB9IGZyb20gJ0B0ZXh0YnVzL2NvcmUnXG5cbmltcG9ydCB7IENvbGxhYm9yYXRlIH0gZnJvbSAnLi9jb2xsYWJvcmF0ZSdcbmltcG9ydCB7IENvbGxhYm9yYXRlQ3Vyc29yIH0gZnJvbSAnLi9jb2xsYWJvcmF0ZS1jdXJzb3InXG5cbmV4cG9ydCAqIGZyb20gJy4vY29sbGFib3JhdGUnXG5leHBvcnQgKiBmcm9tICcuL2NvbGxhYm9yYXRlLWN1cnNvcidcblxuZXhwb3J0IGNvbnN0IGNvbGxhYm9yYXRlTW9kdWxlOiBNb2R1bGUgPSB7XG4gIHByb3ZpZGVyczogW1xuICAgIENvbGxhYm9yYXRlLFxuICAgIENvbGxhYm9yYXRlQ3Vyc29yLFxuICAgIHtcbiAgICAgIHByb3ZpZGU6IEhpc3RvcnksXG4gICAgICB1c2VDbGFzczogQ29sbGFib3JhdGVcbiAgICB9XG4gIF1cbn1cbiJdfQ==
@@ -0,0 +1 @@
1
+ export declare function createUnknownComponent(factoryName: string, canInsertInlineComponent: boolean): any;
@@ -0,0 +1,22 @@
1
+ import { ContentType, defineComponent, VElement } from '@textbus/core';
2
+ export function createUnknownComponent(factoryName, canInsertInlineComponent) {
3
+ const unknownComponent = defineComponent({
4
+ type: canInsertInlineComponent ? ContentType.InlineComponent : ContentType.BlockComponent,
5
+ name: 'UnknownComponent',
6
+ setup() {
7
+ console.error(`cannot find component factory \`${factoryName}\`.`);
8
+ return {
9
+ render() {
10
+ return VElement.createElement('textbus-unknown-component', {
11
+ style: {
12
+ display: canInsertInlineComponent ? 'inline' : 'block',
13
+ color: '#f00'
14
+ }
15
+ }, unknownComponent.name);
16
+ }
17
+ };
18
+ }
19
+ });
20
+ return unknownComponent;
21
+ }
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW5rbm93bi5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdW5rbm93bi5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBRXRFLE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxXQUFtQixFQUFFLHdCQUFpQztJQUMzRixNQUFNLGdCQUFnQixHQUFHLGVBQWUsQ0FBQztRQUN2QyxJQUFJLEVBQUUsd0JBQXdCLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxjQUFjO1FBQ3pGLElBQUksRUFBRSxrQkFBa0I7UUFDeEIsS0FBSztZQUNILE9BQU8sQ0FBQyxLQUFLLENBQUMsbUNBQW1DLFdBQVcsS0FBSyxDQUFDLENBQUE7WUFDbEUsT0FBTztnQkFDTCxNQUFNO29CQUNKLE9BQU8sUUFBUSxDQUFDLGFBQWEsQ0FBQywyQkFBMkIsRUFBRTt3QkFDekQsS0FBSyxFQUFFOzRCQUNMLE9BQU8sRUFBRSx3QkFBd0IsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxPQUFPOzRCQUN0RCxLQUFLLEVBQUUsTUFBTTt5QkFDZDtxQkFDRixFQUFFLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFBO2dCQUMzQixDQUFDO2FBQ0YsQ0FBQTtRQUNILENBQUM7S0FDRixDQUFDLENBQUE7SUFDRixPQUFPLGdCQUFnQixDQUFBO0FBQ3pCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb250ZW50VHlwZSwgZGVmaW5lQ29tcG9uZW50LCBWRWxlbWVudCB9IGZyb20gJ0B0ZXh0YnVzL2NvcmUnXG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVVbmtub3duQ29tcG9uZW50KGZhY3RvcnlOYW1lOiBzdHJpbmcsIGNhbkluc2VydElubGluZUNvbXBvbmVudDogYm9vbGVhbikge1xuICBjb25zdCB1bmtub3duQ29tcG9uZW50ID0gZGVmaW5lQ29tcG9uZW50KHtcbiAgICB0eXBlOiBjYW5JbnNlcnRJbmxpbmVDb21wb25lbnQgPyBDb250ZW50VHlwZS5JbmxpbmVDb21wb25lbnQgOiBDb250ZW50VHlwZS5CbG9ja0NvbXBvbmVudCxcbiAgICBuYW1lOiAnVW5rbm93bkNvbXBvbmVudCcsXG4gICAgc2V0dXAoKSB7XG4gICAgICBjb25zb2xlLmVycm9yKGBjYW5ub3QgZmluZCBjb21wb25lbnQgZmFjdG9yeSBcXGAke2ZhY3RvcnlOYW1lfVxcYC5gKVxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgcmVuZGVyKCkge1xuICAgICAgICAgIHJldHVybiBWRWxlbWVudC5jcmVhdGVFbGVtZW50KCd0ZXh0YnVzLXVua25vd24tY29tcG9uZW50Jywge1xuICAgICAgICAgICAgc3R5bGU6IHtcbiAgICAgICAgICAgICAgZGlzcGxheTogY2FuSW5zZXJ0SW5saW5lQ29tcG9uZW50ID8gJ2lubGluZScgOiAnYmxvY2snLFxuICAgICAgICAgICAgICBjb2xvcjogJyNmMDAnXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSwgdW5rbm93bkNvbXBvbmVudC5uYW1lKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9KVxuICByZXR1cm4gdW5rbm93bkNvbXBvbmVudFxufVxuIl19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbus/collaborate",
3
- "version": "2.0.0-beta.5",
3
+ "version": "2.0.0-beta.52",
4
4
  "description": "Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.",
5
5
  "main": "./bundles/public-api.js",
6
6
  "module": "./bundles/public-api.js",
@@ -25,13 +25,13 @@
25
25
  "typescript editor"
26
26
  ],
27
27
  "dependencies": {
28
- "@tanbo/di": "^1.1.0",
29
- "@tanbo/stream": "^1.0.0",
30
- "@textbus/browser": "^2.0.0-beta.5",
31
- "@textbus/core": "^2.0.0-beta.4",
28
+ "@tanbo/di": "^1.1.1",
29
+ "@tanbo/stream": "^1.1.3",
30
+ "@textbus/browser": "^2.0.0-beta.52",
31
+ "@textbus/core": "^2.0.0-beta.52",
32
32
  "reflect-metadata": "^0.1.13",
33
33
  "y-protocols": "^1.0.5",
34
- "yjs": "^13.5.27"
34
+ "yjs": "^13.5.39"
35
35
  },
36
36
  "author": {
37
37
  "name": "Tanbo",
@@ -44,5 +44,5 @@
44
44
  "bugs": {
45
45
  "url": "https://github.com/textbus/textbus.git/issues"
46
46
  },
47
- "gitHead": "c2d4326a24e8e2acf969737b315cfef462b16b2f"
47
+ "gitHead": "a87a9a890fba05921aa9872b95944eb3d8b4705f"
48
48
  }
@@ -1,13 +1,12 @@
1
- import { Inject, Injectable } from '@tanbo/di'
1
+ import { Inject, Injectable, Optional } from '@tanbo/di'
2
2
  import {
3
3
  createElement,
4
- EDITABLE_DOCUMENT,
5
- EDITOR_CONTAINER,
4
+ VIEW_CONTAINER,
6
5
  getLayoutRectByRange,
7
6
  SelectionBridge
8
7
  } from '@textbus/browser'
9
- import { Selection, SelectionPaths } from '@textbus/core'
10
- import { Subject } from '@tanbo/stream'
8
+ import { Selection, SelectionPaths, Range as TBRange } from '@textbus/core'
9
+ import { fromEvent, Subject, Subscription } from '@tanbo/stream'
11
10
 
12
11
  export interface RemoteSelection {
13
12
  color: string
@@ -15,23 +14,51 @@ export interface RemoteSelection {
15
14
  paths: SelectionPaths
16
15
  }
17
16
 
18
- export interface SelectionRect {
19
- color: string
20
- username: string
17
+ export interface Rect {
21
18
  x: number
22
19
  y: number
23
20
  width: number
24
21
  height: number
25
22
  }
26
23
 
24
+ export interface SelectionRect extends Rect {
25
+ color: string
26
+ username: string
27
+ }
28
+
27
29
  export interface RemoteSelectionCursor {
28
30
  cursor: HTMLElement
29
31
  anchor: HTMLElement
30
32
  userTip: HTMLElement
31
33
  }
32
34
 
35
+ export abstract class CollaborateCursorAwarenessDelegate {
36
+ abstract getRects(range: TBRange, nativeRange: Range): false | Rect[]
37
+ }
38
+
33
39
  @Injectable()
34
40
  export class CollaborateCursor {
41
+ private host = createElement('div', {
42
+ styles: {
43
+ position: 'absolute',
44
+ left: 0,
45
+ top: 0,
46
+ width: '100%',
47
+ height: '100%',
48
+ pointerEvents: 'none',
49
+ zIndex: 1
50
+ }
51
+ })
52
+ private canvasContainer = createElement('div', {
53
+ styles: {
54
+ position: 'absolute',
55
+ left: 0,
56
+ top: 0,
57
+ width: '100%',
58
+ height: '100%',
59
+ overflow: 'hidden'
60
+ }
61
+ })
35
62
  private canvas = createElement('canvas', {
36
63
  styles: {
37
64
  position: 'absolute',
@@ -39,8 +66,8 @@ export class CollaborateCursor {
39
66
  left: 0,
40
67
  top: 0,
41
68
  width: '100%',
42
- height: '100%',
43
- pointerEvents: 'none'
69
+ height: document.documentElement.clientHeight + 'px',
70
+ pointerEvents: 'none',
44
71
  }
45
72
  }) as HTMLCanvasElement
46
73
  private context = this.canvas.getContext('2d')!
@@ -59,12 +86,17 @@ export class CollaborateCursor {
59
86
 
60
87
  private onRectsChange = new Subject<SelectionRect[]>()
61
88
 
62
- constructor(@Inject(EDITOR_CONTAINER) private container: HTMLElement,
63
- @Inject(EDITABLE_DOCUMENT) private document: Document,
89
+ private subscription = new Subscription()
90
+ private currentSelection: RemoteSelection[] = []
91
+
92
+ constructor(@Inject(VIEW_CONTAINER) private container: HTMLElement,
93
+ @Optional() private awarenessDelegate: CollaborateCursorAwarenessDelegate,
64
94
  private nativeSelection: SelectionBridge,
65
95
  private selection: Selection) {
66
- container.prepend(this.canvas, this.tooltips)
67
- this.onRectsChange.subscribe(rects => {
96
+ this.canvasContainer.append(this.canvas)
97
+ this.host.append(this.canvasContainer, this.tooltips)
98
+ container.prepend(this.host)
99
+ this.subscription.add(this.onRectsChange.subscribe(rects => {
68
100
  for (const rect of rects) {
69
101
  this.context.fillStyle = rect.color
70
102
  this.context.beginPath()
@@ -72,68 +104,104 @@ export class CollaborateCursor {
72
104
  this.context.fill()
73
105
  this.context.closePath()
74
106
  }
75
- })
107
+ }), fromEvent(window, 'resize').subscribe(() => {
108
+ this.canvas.style.height = document.documentElement.clientHeight + 'px'
109
+ this.refresh()
110
+ }))
111
+ }
112
+
113
+ refresh() {
114
+ this.draw(this.currentSelection)
115
+ }
116
+
117
+ destroy() {
118
+ this.subscription.unsubscribe()
76
119
  }
77
120
 
78
121
  draw(paths: RemoteSelection[]) {
122
+ this.currentSelection = paths
79
123
  const containerRect = this.container.getBoundingClientRect()
80
- this.canvas.width = containerRect.width
81
- this.canvas.height = containerRect.height
124
+ this.canvas.style.top = containerRect.y * -1 + 'px'
125
+ this.canvas.width = this.canvas.offsetWidth
126
+ this.canvas.height = this.canvas.offsetHeight
82
127
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
83
128
 
84
129
  const users: SelectionRect[] = []
85
130
 
86
-
87
131
  paths.filter(i => {
88
- return i.paths.start.length && i.paths.end.length
132
+ return i.paths.anchor.length && i.paths.focus.length
89
133
  }).forEach(item => {
90
- const startOffset = item.paths.start.pop()!
91
- const startSlot = this.selection.findSlotByPaths(item.paths.start)
92
- const endOffset = item.paths.end.pop()!
93
- const endSlot = this.selection.findSlotByPaths(item.paths.end)
94
-
95
- if (startSlot && endSlot) {
96
- const position = this.nativeSelection.getPositionByRange({
97
- startOffset,
98
- endOffset,
99
- startSlot,
100
- endSlot
134
+ const anchorPaths = [...item.paths.anchor]
135
+ const focusPaths = [...item.paths.focus]
136
+ const anchorOffset = anchorPaths.pop()!
137
+ const anchorSlot = this.selection.findSlotByPaths(anchorPaths)
138
+ const focusOffset = focusPaths.pop()!
139
+ const focusSlot = this.selection.findSlotByPaths(focusPaths)
140
+ if (!anchorSlot || !focusSlot) {
141
+ return
142
+ }
143
+
144
+ const { focus, anchor } = this.nativeSelection.getPositionByRange({
145
+ focusOffset,
146
+ anchorOffset,
147
+ focusSlot,
148
+ anchorSlot
149
+ })
150
+ if (!focus || !anchor) {
151
+ return
152
+ }
153
+ const nativeRange = document.createRange()
154
+ nativeRange.setStart(anchor.node, anchor.offset)
155
+ nativeRange.setEnd(focus.node, focus.offset)
156
+ if ((anchor.node !== focus.node || anchor.offset !== focus.offset) && nativeRange.collapsed) {
157
+ nativeRange.setStart(focus.node, focus.offset)
158
+ nativeRange.setEnd(anchor.node, anchor.offset)
159
+ }
160
+
161
+ let rects: Rect[] | DOMRectList | false = false
162
+ if (this.awarenessDelegate) {
163
+ rects = this.awarenessDelegate.getRects({
164
+ focusOffset,
165
+ anchorOffset,
166
+ focusSlot,
167
+ anchorSlot
168
+ }, nativeRange)
169
+ }
170
+ if (!rects) {
171
+ rects = nativeRange.getClientRects()
172
+ }
173
+ const selectionRects: SelectionRect[] = []
174
+ for (let i = rects.length - 1; i >= 0; i--) {
175
+ const rect = rects[i]
176
+ selectionRects.push({
177
+ color: item.color,
178
+ username: item.username,
179
+ x: rect.x - containerRect.x,
180
+ y: rect.y,
181
+ width: rect.width,
182
+ height: rect.height,
101
183
  })
102
- if (position.start && position.end) {
103
- const nativeRange = this.document.createRange()
104
- nativeRange.setStart(position.start.node, position.start.offset)
105
- nativeRange.setEnd(position.end.node, position.end.offset)
106
-
107
- const rects = nativeRange.getClientRects()
108
- const selectionRects: SelectionRect[] = []
109
- for (let i = rects.length - 1; i >= 0; i--) {
110
- const rect = rects[i]
111
- selectionRects.push({
112
- color: item.color,
113
- username: item.username,
114
- x: rect.x - containerRect.x,
115
- y: rect.y - containerRect.y,
116
- width: rect.width,
117
- height: rect.height,
118
- })
119
- }
120
- this.onRectsChange.next(selectionRects)
121
-
122
- const cursorRange = nativeRange.cloneRange()
123
- cursorRange.collapse(!item.paths.focusEnd)
124
-
125
- const cursorRect = getLayoutRectByRange(cursorRange)
126
-
127
- users.push({
128
- username: item.username,
129
- color: item.color,
130
- x: cursorRect.x - containerRect.x,
131
- y: cursorRect.y - containerRect.y,
132
- width: 2,
133
- height: cursorRect.height
134
- })
135
- }
136
184
  }
185
+ this.onRectsChange.next(selectionRects)
186
+
187
+ const cursorRange = nativeRange.cloneRange()
188
+ cursorRange.setStart(focus.node, focus.offset)
189
+ cursorRange.collapse(true)
190
+
191
+ const cursorRect = getLayoutRectByRange(cursorRange)
192
+
193
+ const rect: SelectionRect = {
194
+ username: item.username,
195
+ color: item.color,
196
+ x: cursorRect.x - containerRect.x,
197
+ y: cursorRect.y - containerRect.y,
198
+ width: 2,
199
+ height: cursorRect.height
200
+ }
201
+ if (rect.x < 0 || rect.y < 0 || rect.x > containerRect.width) {
202
+ return
203
+ }
204
+ users.push(rect)
137
205
  })
138
206
  this.drawUserCursor(users)
139
207
  }
@@ -141,7 +209,7 @@ export class CollaborateCursor {
141
209
  private drawUserCursor(rects: SelectionRect[]) {
142
210
  for (let i = 0; i < rects.length; i++) {
143
211
  const rect = rects[i]
144
- const {cursor, userTip, anchor} = this.getUserCursor(i)
212
+ const { cursor, userTip, anchor } = this.getUserCursor(i)
145
213
  Object.assign(cursor.style, {
146
214
  left: rect.x + 'px',
147
215
  top: rect.y + 'px',