@textbus/collaborate 2.0.0-beta.5 → 2.0.0-beta.52

Sign up to get free protection for your applications and to get access to all the features.
@@ -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',