@textbus/collaborate 2.0.0-beta.9 → 2.0.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 +11 -11
- package/bundles/collaborate-cursor.js +120 -117
- package/bundles/collaborate.d.ts +11 -6
- package/bundles/collaborate.js +242 -216
- package/package.json +7 -7
- package/src/collaborate-cursor.ts +100 -56
- package/src/collaborate.ts +235 -66
- package/tsconfig-build.json +1 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@textbus/collaborate",
|
3
|
-
"version": "2.0.0
|
3
|
+
"version": "2.0.0",
|
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.
|
29
|
-
"@tanbo/stream": "^1.
|
30
|
-
"@textbus/browser": "^2.0.0
|
31
|
-
"@textbus/core": "^2.0.0
|
28
|
+
"@tanbo/di": "^1.1.3",
|
29
|
+
"@tanbo/stream": "^1.1.5",
|
30
|
+
"@textbus/browser": "^2.0.0",
|
31
|
+
"@textbus/core": "^2.0.0",
|
32
32
|
"reflect-metadata": "^0.1.13",
|
33
33
|
"y-protocols": "^1.0.5",
|
34
|
-
"yjs": "^13.5.
|
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": "
|
47
|
+
"gitHead": "df5108327f1edd1bbf662bb8da193c22b659abe9"
|
48
48
|
}
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import { Inject, Injectable, Optional } from '@tanbo/di'
|
2
2
|
import {
|
3
3
|
createElement,
|
4
|
-
|
5
|
-
EDITOR_CONTAINER,
|
4
|
+
VIEW_CONTAINER,
|
6
5
|
getLayoutRectByRange,
|
7
|
-
SelectionBridge
|
6
|
+
SelectionBridge,
|
7
|
+
getBoundingClientRect
|
8
8
|
} from '@textbus/browser'
|
9
|
-
import { Selection, SelectionPaths,
|
10
|
-
import { Subject } from '@tanbo/stream'
|
9
|
+
import { Selection, SelectionPaths, AbstractSelection, Scheduler, Rect } from '@textbus/core'
|
10
|
+
import { fromEvent, Subject, Subscription } from '@tanbo/stream'
|
11
11
|
|
12
12
|
export interface RemoteSelection {
|
13
13
|
color: string
|
@@ -15,13 +15,6 @@ export interface RemoteSelection {
|
|
15
15
|
paths: SelectionPaths
|
16
16
|
}
|
17
17
|
|
18
|
-
export interface Rect {
|
19
|
-
x: number
|
20
|
-
y: number
|
21
|
-
width: number
|
22
|
-
height: number
|
23
|
-
}
|
24
|
-
|
25
18
|
export interface SelectionRect extends Rect {
|
26
19
|
color: string
|
27
20
|
username: string
|
@@ -33,12 +26,33 @@ export interface RemoteSelectionCursor {
|
|
33
26
|
userTip: HTMLElement
|
34
27
|
}
|
35
28
|
|
36
|
-
export abstract class
|
37
|
-
abstract getRects(
|
29
|
+
export abstract class CollaborateSelectionAwarenessDelegate {
|
30
|
+
abstract getRects(abstractSelection: AbstractSelection, nativeRange: Range): false | Rect[]
|
38
31
|
}
|
39
32
|
|
40
33
|
@Injectable()
|
41
34
|
export class CollaborateCursor {
|
35
|
+
private host = createElement('div', {
|
36
|
+
styles: {
|
37
|
+
position: 'absolute',
|
38
|
+
left: 0,
|
39
|
+
top: 0,
|
40
|
+
width: '100%',
|
41
|
+
height: '100%',
|
42
|
+
pointerEvents: 'none',
|
43
|
+
zIndex: 1
|
44
|
+
}
|
45
|
+
})
|
46
|
+
private canvasContainer = createElement('div', {
|
47
|
+
styles: {
|
48
|
+
position: 'absolute',
|
49
|
+
left: 0,
|
50
|
+
top: 0,
|
51
|
+
width: '100%',
|
52
|
+
height: '100%',
|
53
|
+
overflow: 'hidden'
|
54
|
+
}
|
55
|
+
})
|
42
56
|
private canvas = createElement('canvas', {
|
43
57
|
styles: {
|
44
58
|
position: 'absolute',
|
@@ -46,8 +60,8 @@ export class CollaborateCursor {
|
|
46
60
|
left: 0,
|
47
61
|
top: 0,
|
48
62
|
width: '100%',
|
49
|
-
height: '
|
50
|
-
pointerEvents: 'none'
|
63
|
+
height: document.documentElement.clientHeight + 'px',
|
64
|
+
pointerEvents: 'none',
|
51
65
|
}
|
52
66
|
}) as HTMLCanvasElement
|
53
67
|
private context = this.canvas.getContext('2d')!
|
@@ -66,63 +80,88 @@ export class CollaborateCursor {
|
|
66
80
|
|
67
81
|
private onRectsChange = new Subject<SelectionRect[]>()
|
68
82
|
|
69
|
-
|
70
|
-
|
71
|
-
|
83
|
+
private subscription = new Subscription()
|
84
|
+
private currentSelection: RemoteSelection[] = []
|
85
|
+
|
86
|
+
constructor(@Inject(VIEW_CONTAINER) private container: HTMLElement,
|
87
|
+
@Optional() private awarenessDelegate: CollaborateSelectionAwarenessDelegate,
|
72
88
|
private nativeSelection: SelectionBridge,
|
89
|
+
private scheduler: Scheduler,
|
73
90
|
private selection: Selection) {
|
74
|
-
|
75
|
-
this.
|
91
|
+
this.canvasContainer.append(this.canvas)
|
92
|
+
this.host.append(this.canvasContainer, this.tooltips)
|
93
|
+
container.prepend(this.host)
|
94
|
+
this.subscription.add(this.onRectsChange.subscribe(rects => {
|
76
95
|
for (const rect of rects) {
|
77
96
|
this.context.fillStyle = rect.color
|
78
97
|
this.context.beginPath()
|
79
|
-
this.context.rect(rect.
|
98
|
+
this.context.rect(rect.left, rect.top, rect.width, rect.height)
|
80
99
|
this.context.fill()
|
81
100
|
this.context.closePath()
|
82
101
|
}
|
83
|
-
})
|
102
|
+
}), fromEvent(window, 'resize').subscribe(() => {
|
103
|
+
this.canvas.style.height = document.documentElement.clientHeight + 'px'
|
104
|
+
this.refresh()
|
105
|
+
}), this.scheduler.onDocChanged.subscribe(() => {
|
106
|
+
this.refresh()
|
107
|
+
}))
|
108
|
+
}
|
109
|
+
|
110
|
+
refresh() {
|
111
|
+
this.draw(this.currentSelection)
|
112
|
+
}
|
113
|
+
|
114
|
+
destroy() {
|
115
|
+
this.subscription.unsubscribe()
|
84
116
|
}
|
85
117
|
|
86
118
|
draw(paths: RemoteSelection[]) {
|
87
|
-
|
88
|
-
|
89
|
-
this.canvas.
|
119
|
+
this.currentSelection = paths
|
120
|
+
const containerRect = getBoundingClientRect(this.container)
|
121
|
+
this.canvas.style.top = containerRect.top * -1 + 'px'
|
122
|
+
this.canvas.width = this.canvas.offsetWidth
|
123
|
+
this.canvas.height = this.canvas.offsetHeight
|
90
124
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
91
125
|
|
92
126
|
const users: SelectionRect[] = []
|
93
127
|
|
94
|
-
|
95
128
|
paths.filter(i => {
|
96
|
-
return i.paths.
|
129
|
+
return i.paths.anchor.length && i.paths.focus.length
|
97
130
|
}).forEach(item => {
|
98
|
-
const
|
99
|
-
const
|
100
|
-
const
|
101
|
-
const
|
102
|
-
|
131
|
+
const anchorPaths = [...item.paths.anchor]
|
132
|
+
const focusPaths = [...item.paths.focus]
|
133
|
+
const anchorOffset = anchorPaths.pop()!
|
134
|
+
const anchorSlot = this.selection.findSlotByPaths(anchorPaths)
|
135
|
+
const focusOffset = focusPaths.pop()!
|
136
|
+
const focusSlot = this.selection.findSlotByPaths(focusPaths)
|
137
|
+
if (!anchorSlot || !focusSlot) {
|
103
138
|
return
|
104
139
|
}
|
105
140
|
|
106
|
-
const {
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
141
|
+
const { focus, anchor } = this.nativeSelection.getPositionByRange({
|
142
|
+
focusOffset,
|
143
|
+
anchorOffset,
|
144
|
+
focusSlot,
|
145
|
+
anchorSlot
|
111
146
|
})
|
112
|
-
if (!
|
147
|
+
if (!focus || !anchor) {
|
113
148
|
return
|
114
149
|
}
|
115
|
-
const nativeRange =
|
116
|
-
nativeRange.setStart(
|
117
|
-
nativeRange.setEnd(
|
150
|
+
const nativeRange = document.createRange()
|
151
|
+
nativeRange.setStart(anchor.node, anchor.offset)
|
152
|
+
nativeRange.setEnd(focus.node, focus.offset)
|
153
|
+
if ((anchor.node !== focus.node || anchor.offset !== focus.offset) && nativeRange.collapsed) {
|
154
|
+
nativeRange.setStart(focus.node, focus.offset)
|
155
|
+
nativeRange.setEnd(anchor.node, anchor.offset)
|
156
|
+
}
|
118
157
|
|
119
158
|
let rects: Rect[] | DOMRectList | false = false
|
120
159
|
if (this.awarenessDelegate) {
|
121
160
|
rects = this.awarenessDelegate.getRects({
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
161
|
+
focusOffset,
|
162
|
+
anchorOffset,
|
163
|
+
focusSlot,
|
164
|
+
anchorSlot
|
126
165
|
}, nativeRange)
|
127
166
|
}
|
128
167
|
if (!rects) {
|
@@ -134,8 +173,8 @@ export class CollaborateCursor {
|
|
134
173
|
selectionRects.push({
|
135
174
|
color: item.color,
|
136
175
|
username: item.username,
|
137
|
-
|
138
|
-
|
176
|
+
left: rect.left - containerRect.left,
|
177
|
+
top: rect.top,
|
139
178
|
width: rect.width,
|
140
179
|
height: rect.height,
|
141
180
|
})
|
@@ -143,18 +182,23 @@ export class CollaborateCursor {
|
|
143
182
|
this.onRectsChange.next(selectionRects)
|
144
183
|
|
145
184
|
const cursorRange = nativeRange.cloneRange()
|
146
|
-
cursorRange.
|
185
|
+
cursorRange.setStart(focus.node, focus.offset)
|
186
|
+
cursorRange.collapse(true)
|
147
187
|
|
148
188
|
const cursorRect = getLayoutRectByRange(cursorRange)
|
149
189
|
|
150
|
-
|
190
|
+
const rect: SelectionRect = {
|
151
191
|
username: item.username,
|
152
192
|
color: item.color,
|
153
|
-
|
154
|
-
|
193
|
+
left: cursorRect.left - containerRect.left,
|
194
|
+
top: cursorRect.top - containerRect.top,
|
155
195
|
width: 2,
|
156
196
|
height: cursorRect.height
|
157
|
-
}
|
197
|
+
}
|
198
|
+
if (rect.left < 0 || rect.top < 0 || rect.left > containerRect.width) {
|
199
|
+
return
|
200
|
+
}
|
201
|
+
users.push(rect)
|
158
202
|
})
|
159
203
|
this.drawUserCursor(users)
|
160
204
|
}
|
@@ -162,10 +206,10 @@ export class CollaborateCursor {
|
|
162
206
|
private drawUserCursor(rects: SelectionRect[]) {
|
163
207
|
for (let i = 0; i < rects.length; i++) {
|
164
208
|
const rect = rects[i]
|
165
|
-
const {cursor, userTip, anchor} = this.getUserCursor(i)
|
209
|
+
const { cursor, userTip, anchor } = this.getUserCursor(i)
|
166
210
|
Object.assign(cursor.style, {
|
167
|
-
left: rect.
|
168
|
-
top: rect.
|
211
|
+
left: rect.left + 'px',
|
212
|
+
top: rect.top + 'px',
|
169
213
|
width: rect.width + 'px',
|
170
214
|
height: rect.height + 'px',
|
171
215
|
background: rect.color,
|