@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/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,