@textbus/collaborate 2.0.0-beta.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbus/collaborate",
3
- "version": "2.0.0-beta.9",
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.1",
29
- "@tanbo/stream": "^1.0.1",
30
- "@textbus/browser": "^2.0.0-beta.9",
31
- "@textbus/core": "^2.0.0-beta.9",
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.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": "471125b715d9092979574a89d5ffd15ca51c1498"
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
- EDITABLE_DOCUMENT,
5
- EDITOR_CONTAINER,
4
+ VIEW_CONTAINER,
6
5
  getLayoutRectByRange,
7
- SelectionBridge
6
+ SelectionBridge,
7
+ getBoundingClientRect
8
8
  } from '@textbus/browser'
9
- import { Selection, SelectionPaths, Range as TBRange } from '@textbus/core'
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 CollaborateCursorAwarenessDelegate {
37
- abstract getRects(range: TBRange, nativeRange: Range): false | Rect[]
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: '100%',
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
- constructor(@Inject(EDITOR_CONTAINER) private container: HTMLElement,
70
- @Inject(EDITABLE_DOCUMENT) private document: Document,
71
- @Optional() private awarenessDelegate: CollaborateCursorAwarenessDelegate,
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
- container.prepend(this.canvas, this.tooltips)
75
- this.onRectsChange.subscribe(rects => {
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.x, rect.y, rect.width, rect.height)
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
- const containerRect = this.container.getBoundingClientRect()
88
- this.canvas.width = containerRect.width
89
- this.canvas.height = containerRect.height
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.start.length && i.paths.end.length
129
+ return i.paths.anchor.length && i.paths.focus.length
97
130
  }).forEach(item => {
98
- const startOffset = item.paths.start.pop()!
99
- const startSlot = this.selection.findSlotByPaths(item.paths.start)
100
- const endOffset = item.paths.end.pop()!
101
- const endSlot = this.selection.findSlotByPaths(item.paths.end)
102
- if (!startSlot || !endSlot) {
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 {start, end} = this.nativeSelection.getPositionByRange({
107
- startOffset,
108
- endOffset,
109
- startSlot,
110
- endSlot
141
+ const { focus, anchor } = this.nativeSelection.getPositionByRange({
142
+ focusOffset,
143
+ anchorOffset,
144
+ focusSlot,
145
+ anchorSlot
111
146
  })
112
- if (!start || !end) {
147
+ if (!focus || !anchor) {
113
148
  return
114
149
  }
115
- const nativeRange = this.document.createRange()
116
- nativeRange.setStart(start.node, start.offset)
117
- nativeRange.setEnd(end.node, end.offset)
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
- startOffset,
123
- endOffset,
124
- startSlot,
125
- endSlot
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
- x: rect.x - containerRect.x,
138
- y: rect.y - containerRect.y,
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.collapse(!item.paths.focusEnd)
185
+ cursorRange.setStart(focus.node, focus.offset)
186
+ cursorRange.collapse(true)
147
187
 
148
188
  const cursorRect = getLayoutRectByRange(cursorRange)
149
189
 
150
- users.push({
190
+ const rect: SelectionRect = {
151
191
  username: item.username,
152
192
  color: item.color,
153
- x: cursorRect.x - containerRect.x,
154
- y: cursorRect.y - containerRect.y,
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.x + 'px',
168
- top: rect.y + 'px',
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,