@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.
- package/bundles/collaborate-cursor.d.ts +17 -6
- package/bundles/collaborate-cursor.js +141 -119
- package/bundles/collaborate.d.ts +12 -7
- package/bundles/collaborate.js +267 -224
- package/bundles/public-api.d.ts +2 -0
- package/bundles/public-api.js +14 -1
- package/bundles/unknown.component.d.ts +1 -0
- package/bundles/unknown.component.js +22 -0
- package/package.json +7 -7
- package/src/collaborate-cursor.ts +133 -65
- package/src/collaborate.ts +264 -82
- package/src/public-api.ts +16 -0
- package/src/unknown.component.ts +22 -0
- package/tsconfig-build.json +1 -1
package/bundles/public-api.d.ts
CHANGED
package/bundles/public-api.js
CHANGED
@@ -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
|
-
|
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.
|
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.
|
29
|
-
"@tanbo/stream": "^1.
|
30
|
-
"@textbus/browser": "^2.0.0-beta.
|
31
|
-
"@textbus/core": "^2.0.0-beta.
|
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.
|
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": "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
|
-
|
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
|
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: '
|
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
|
-
|
63
|
-
|
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
|
-
|
67
|
-
this.
|
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.
|
81
|
-
this.canvas.
|
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.
|
132
|
+
return i.paths.anchor.length && i.paths.focus.length
|
89
133
|
}).forEach(item => {
|
90
|
-
const
|
91
|
-
const
|
92
|
-
const
|
93
|
-
const
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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',
|