@jupyter/collaboration 1.0.0-alpha.4 → 1.0.0-alpha.5

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.
@@ -70,8 +70,9 @@ export class CollaboratorsBody extends ReactWidget {
70
70
  let currentFileLocation = '';
71
71
  if (value.current) {
72
72
  canOpenCurrent = true;
73
- currentFileLocation = value.current.split(':')[1];
74
- current = PathExt.basename(currentFileLocation);
73
+ const path = value.current.split(':');
74
+ currentFileLocation = `${path[1]}:${path[2]}`;
75
+ current = PathExt.basename(path[2]);
75
76
  current =
76
77
  current.length > 25 ? current.slice(0, 12).concat('…') : current;
77
78
  separator = '•';
@@ -1,6 +1,6 @@
1
1
  import { User } from '@jupyterlab/services';
2
2
  import * as React from 'react';
3
- declare type Props = {
3
+ type Props = {
4
4
  user: User.IIdentity;
5
5
  };
6
6
  /**
@@ -0,0 +1,23 @@
1
+ import { Extension } from '@codemirror/state';
2
+ import { Awareness } from 'y-protocols/awareness';
3
+ import { Text } from 'yjs';
4
+ /**
5
+ * Yjs document objects
6
+ */
7
+ export type EditorAwareness = {
8
+ /**
9
+ * User related information
10
+ */
11
+ awareness: Awareness;
12
+ /**
13
+ * Shared editor source
14
+ */
15
+ ytext: Text;
16
+ };
17
+ /**
18
+ * CodeMirror extension to display remote users cursors
19
+ *
20
+ * @param config Editor source and awareness
21
+ * @returns CodeMirror extension
22
+ */
23
+ export declare function remoteUserCursors(config: EditorAwareness): Extension;
package/lib/cursors.js ADDED
@@ -0,0 +1,278 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+ import { Annotation, EditorSelection, Facet } from '@codemirror/state';
4
+ import { EditorView, hoverTooltip, layer, RectangleMarker, tooltips, ViewPlugin } from '@codemirror/view';
5
+ import { JSONExt } from '@lumino/coreutils';
6
+ import { createAbsolutePositionFromRelativePosition, createRelativePositionFromJSON, createRelativePositionFromTypeIndex } from 'yjs';
7
+ /**
8
+ * Facet storing the Yjs document objects
9
+ */
10
+ const editorAwarenessFacet = Facet.define({
11
+ combine(configs) {
12
+ return configs[configs.length - 1];
13
+ }
14
+ });
15
+ /**
16
+ * Remote selection theme
17
+ */
18
+ const remoteSelectionTheme = EditorView.baseTheme({
19
+ '.jp-remote-cursor': {
20
+ borderLeft: '1px solid black',
21
+ marginLeft: '-1px'
22
+ },
23
+ '.jp-remote-cursor.jp-mod-primary': {
24
+ borderLeftWidth: '2px'
25
+ },
26
+ '.jp-remote-selection': {
27
+ opacity: 0.5
28
+ },
29
+ '.cm-tooltip': {
30
+ border: 'none'
31
+ },
32
+ '.cm-tooltip .jp-remote-userInfo': {
33
+ color: 'var(--jp-ui-inverse-font-color0)',
34
+ padding: '0px 2px'
35
+ }
36
+ });
37
+ // TODO fix which user needs update
38
+ const remoteSelectionsAnnotation = Annotation.define();
39
+ /**
40
+ * Wrapper around RectangleMarker to be able to set the user color for the remote cursor and selection ranges.
41
+ */
42
+ class RemoteMarker {
43
+ /**
44
+ * Constructor
45
+ *
46
+ * @param style Specific user style to be applied on the marker element
47
+ * @param marker {@link RectangleMarker} to wrap
48
+ */
49
+ constructor(style, marker) {
50
+ this.style = style;
51
+ this.marker = marker;
52
+ }
53
+ draw() {
54
+ const elt = this.marker.draw();
55
+ for (const [key, value] of Object.entries(this.style)) {
56
+ // @ts-expect-error Unknown key
57
+ elt.style[key] = value;
58
+ }
59
+ return elt;
60
+ }
61
+ eq(other) {
62
+ return (this.marker.eq(other.marker) && JSONExt.deepEqual(this.style, other.style));
63
+ }
64
+ update(dom, oldMarker) {
65
+ for (const [key, value] of Object.entries(this.style)) {
66
+ // @ts-expect-error Unknown key
67
+ dom.style[key] = value;
68
+ }
69
+ return this.marker.update(dom, oldMarker.marker);
70
+ }
71
+ }
72
+ /**
73
+ * Extension defining a new editor layer storing the remote user cursors
74
+ */
75
+ const remoteCursorsLayer = layer({
76
+ above: true,
77
+ markers(view) {
78
+ const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
79
+ const ydoc = ytext.doc;
80
+ const cursors = [];
81
+ awareness.getStates().forEach((state, clientID) => {
82
+ var _a, _b, _c;
83
+ if (clientID === awareness.doc.clientID) {
84
+ return;
85
+ }
86
+ const cursors_ = state.cursors;
87
+ for (const cursor of cursors_ !== null && cursors_ !== void 0 ? cursors_ : []) {
88
+ if (!(cursor === null || cursor === void 0 ? void 0 : cursor.anchor) || !(cursor === null || cursor === void 0 ? void 0 : cursor.head)) {
89
+ return;
90
+ }
91
+ const anchor = createAbsolutePositionFromRelativePosition(cursor.anchor, ydoc);
92
+ const head = createAbsolutePositionFromRelativePosition(cursor.head, ydoc);
93
+ if ((anchor === null || anchor === void 0 ? void 0 : anchor.type) !== ytext || (head === null || head === void 0 ? void 0 : head.type) !== ytext) {
94
+ return;
95
+ }
96
+ const className = ((_a = cursor.primary) !== null && _a !== void 0 ? _a : true)
97
+ ? 'jp-remote-cursor jp-mod-primary'
98
+ : 'jp-remote-cursor';
99
+ const cursor_ = EditorSelection.cursor(head.index, head.index > anchor.index ? -1 : 1);
100
+ for (const piece of RectangleMarker.forRange(view, className, cursor_)) {
101
+ // Wrap the rectangle marker to set the user color
102
+ cursors.push(new RemoteMarker({ borderLeftColor: (_c = (_b = state.user) === null || _b === void 0 ? void 0 : _b.color) !== null && _c !== void 0 ? _c : 'black' }, piece));
103
+ }
104
+ }
105
+ });
106
+ return cursors;
107
+ },
108
+ update(update, layer) {
109
+ return !!update.transactions.find(t => t.annotation(remoteSelectionsAnnotation));
110
+ },
111
+ class: 'jp-remote-cursors'
112
+ });
113
+ /**
114
+ * Tooltip extension to display user display name at cursor position
115
+ */
116
+ const userHover = hoverTooltip((view, pos) => {
117
+ var _a;
118
+ const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
119
+ const ydoc = ytext.doc;
120
+ for (const [clientID, state] of awareness.getStates()) {
121
+ if (clientID === awareness.doc.clientID) {
122
+ continue;
123
+ }
124
+ for (const cursor of (_a = state.cursors) !== null && _a !== void 0 ? _a : []) {
125
+ if (!(cursor === null || cursor === void 0 ? void 0 : cursor.head)) {
126
+ continue;
127
+ }
128
+ const head = createAbsolutePositionFromRelativePosition(cursor.head, ydoc);
129
+ if ((head === null || head === void 0 ? void 0 : head.type) !== ytext) {
130
+ continue;
131
+ }
132
+ // Use some margin around the cursor to display the user.
133
+ if (head.index - 1 <= pos && pos <= head.index + 1) {
134
+ return {
135
+ pos: head.index,
136
+ above: true,
137
+ create: () => {
138
+ var _a, _b, _c, _d;
139
+ const dom = document.createElement('div');
140
+ dom.classList.add('jp-remote-userInfo');
141
+ dom.style.backgroundColor = (_b = (_a = state.user) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : 'darkgrey';
142
+ dom.textContent =
143
+ (_d = (_c = state.user) === null || _c === void 0 ? void 0 : _c.display_name) !== null && _d !== void 0 ? _d : 'Anonymous';
144
+ return { dom };
145
+ }
146
+ };
147
+ }
148
+ }
149
+ }
150
+ return null;
151
+ }, {
152
+ hideOn: (tr, tooltip) => !!tr.annotation(remoteSelectionsAnnotation)
153
+ });
154
+ /**
155
+ * Extension defining a new editor layer storing the remote selections
156
+ */
157
+ const remoteSelectionLayer = layer({
158
+ above: false,
159
+ markers(view) {
160
+ const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
161
+ const ydoc = ytext.doc;
162
+ const cursors = [];
163
+ awareness.getStates().forEach((state, clientID) => {
164
+ var _a, _b, _c;
165
+ if (clientID === awareness.doc.clientID) {
166
+ return;
167
+ }
168
+ const cursors_ = state.cursors;
169
+ for (const cursor of cursors_ !== null && cursors_ !== void 0 ? cursors_ : []) {
170
+ if (((_a = cursor.empty) !== null && _a !== void 0 ? _a : true) || !(cursor === null || cursor === void 0 ? void 0 : cursor.anchor) || !(cursor === null || cursor === void 0 ? void 0 : cursor.head)) {
171
+ return;
172
+ }
173
+ const anchor = createAbsolutePositionFromRelativePosition(cursor.anchor, ydoc);
174
+ const head = createAbsolutePositionFromRelativePosition(cursor.head, ydoc);
175
+ if ((anchor === null || anchor === void 0 ? void 0 : anchor.type) !== ytext || (head === null || head === void 0 ? void 0 : head.type) !== ytext) {
176
+ return;
177
+ }
178
+ const className = 'jp-remote-selection';
179
+ for (const piece of RectangleMarker.forRange(view, className, EditorSelection.range(anchor.index, head.index))) {
180
+ // Wrap the rectangle marker to set the user color
181
+ cursors.push(new RemoteMarker({ backgroundColor: (_c = (_b = state.user) === null || _b === void 0 ? void 0 : _b.color) !== null && _c !== void 0 ? _c : 'black' }, piece));
182
+ }
183
+ }
184
+ });
185
+ return cursors;
186
+ },
187
+ update(update, layer) {
188
+ return !!update.transactions.find(t => t.annotation(remoteSelectionsAnnotation));
189
+ },
190
+ class: 'jp-remote-selections'
191
+ });
192
+ /**
193
+ * CodeMirror extension exchanging and displaying remote user selection ranges (including cursors)
194
+ */
195
+ const showCollaborators = ViewPlugin.fromClass(class {
196
+ constructor(view) {
197
+ this.editorAwareness = view.state.facet(editorAwarenessFacet);
198
+ this._listener = ({ added, updated, removed }) => {
199
+ const clients = added.concat(updated).concat(removed);
200
+ if (clients.findIndex(id => id !== this.editorAwareness.awareness.doc.clientID) >= 0) {
201
+ // Trick to get the remoteCursorLayers to be updated
202
+ view.dispatch({ annotations: [remoteSelectionsAnnotation.of([])] });
203
+ }
204
+ };
205
+ this.editorAwareness.awareness.on('change', this._listener);
206
+ }
207
+ destroy() {
208
+ this.editorAwareness.awareness.off('change', this._listener);
209
+ }
210
+ /**
211
+ * Communicate the current user cursor position to all remotes
212
+ */
213
+ update(update) {
214
+ var _a;
215
+ if (!update.docChanged && !update.selectionSet) {
216
+ return;
217
+ }
218
+ const { awareness, ytext } = this.editorAwareness;
219
+ const localAwarenessState = awareness.getLocalState();
220
+ // set local awareness state (update cursors)
221
+ if (localAwarenessState) {
222
+ const hasFocus = update.view.hasFocus && update.view.dom.ownerDocument.hasFocus();
223
+ const selection = update.state.selection;
224
+ const cursors = new Array();
225
+ if (hasFocus && selection) {
226
+ for (const r of selection.ranges) {
227
+ const primary = r === selection.main;
228
+ const anchor = createRelativePositionFromTypeIndex(ytext, r.anchor);
229
+ const head = createRelativePositionFromTypeIndex(ytext, r.head);
230
+ cursors.push({
231
+ anchor,
232
+ head,
233
+ primary,
234
+ empty: r.empty
235
+ });
236
+ }
237
+ if (!localAwarenessState.cursors || cursors.length > 0) {
238
+ const oldCursors = (_a = localAwarenessState.cursors) === null || _a === void 0 ? void 0 : _a.map(cursor => {
239
+ return {
240
+ ...cursor,
241
+ anchor: (cursor === null || cursor === void 0 ? void 0 : cursor.anchor)
242
+ ? createRelativePositionFromJSON(cursor.anchor)
243
+ : null,
244
+ head: (cursor === null || cursor === void 0 ? void 0 : cursor.head)
245
+ ? createRelativePositionFromJSON(cursor.head)
246
+ : null
247
+ };
248
+ });
249
+ if (!JSONExt.deepEqual(cursors, oldCursors)) {
250
+ // Update cursors
251
+ awareness.setLocalStateField('cursors', cursors);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }, {
258
+ provide: () => {
259
+ return [
260
+ remoteSelectionTheme,
261
+ remoteCursorsLayer,
262
+ remoteSelectionLayer,
263
+ userHover,
264
+ // As we use relative positioning of widget, the tooltip must be positioned absolutely
265
+ // And we attach the tooltip to the body to avoid overflow rules
266
+ tooltips({ position: 'absolute', parent: document.body })
267
+ ];
268
+ }
269
+ });
270
+ /**
271
+ * CodeMirror extension to display remote users cursors
272
+ *
273
+ * @param config Editor source and awareness
274
+ * @returns CodeMirror extension
275
+ */
276
+ export function remoteUserCursors(config) {
277
+ return [editorAwarenessFacet.of(config), showCollaborators];
278
+ }
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * @module collaboration
4
4
  */
5
5
  export * from './tokens';
6
+ export * from './cursors';
6
7
  export * from './menu';
7
8
  export * from './userinfopanel';
8
9
  export * from './collaboratorspanel';
package/lib/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module collaboration
6
6
  */
7
7
  export * from './tokens';
8
+ export * from './cursors';
8
9
  export * from './menu';
9
10
  export * from './userinfopanel';
10
11
  export * from './collaboratorspanel';
package/lib/tokens.d.ts CHANGED
@@ -16,7 +16,7 @@ export declare const IGlobalAwareness: Token<Awareness>;
16
16
  /**
17
17
  * The awareness interface.
18
18
  */
19
- export declare type IAwareness = Awareness;
19
+ export type IAwareness = Awareness;
20
20
  /**
21
21
  * An interface describing the user menu.
22
22
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyter/collaboration",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.5",
4
4
  "description": "JupyterLab - Real-Time Collaboration Widgets",
5
5
  "homepage": "https://github.com/jupyterlab/jupyter_collaboration",
6
6
  "bugs": {
@@ -39,25 +39,27 @@
39
39
  "watch": "tsc -b --watch"
40
40
  },
41
41
  "dependencies": {
42
- "@jupyterlab/apputils": "^4.0.0-alpha.19",
43
- "@jupyterlab/coreutils": "^6.0.0-alpha.19",
44
- "@jupyterlab/services": "^7.0.0-alpha.19",
45
- "@jupyterlab/ui-components": "^4.0.0-alpha.33",
46
- "@lumino/coreutils": "^2.0.0-beta.0",
47
- "@lumino/virtualdom": "^2.0.0-beta.0",
48
- "@lumino/widgets": "^2.0.0-beta.1",
42
+ "@codemirror/state": "^6.2.0",
43
+ "@codemirror/view": "^6.7.0",
44
+ "@jupyterlab/apputils": "^4.0.0-alpha.22",
45
+ "@jupyterlab/coreutils": "^6.0.0-alpha.22",
46
+ "@jupyterlab/services": "^7.0.0-alpha.22",
47
+ "@jupyterlab/ui-components": "^4.0.0-alpha.37",
48
+ "@lumino/coreutils": "^2.0.0",
49
+ "@lumino/virtualdom": "^2.0.0",
50
+ "@lumino/widgets": "^2.0.0",
49
51
  "react": "^18.2.0",
50
52
  "y-protocols": "^1.0.5",
51
53
  "yjs": "^13.5.40"
52
54
  },
53
55
  "devDependencies": {
56
+ "@types/react": "^18.0.27",
54
57
  "rimraf": "^4.1.2",
55
- "typescript": "~4.7.3"
58
+ "typescript": "~5.0.2"
56
59
  },
57
60
  "publishConfig": {
58
61
  "access": "public"
59
62
  },
60
- "jupyterlab": {},
61
63
  "typedoc": {
62
64
  "entryPoint": "./src/index.ts",
63
65
  "readmeFile": "./README.md",