@jupyter/collaboration 4.1.0-beta.0 → 4.1.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/lib/sharedlink.js +378 -20
- package/package.json +1 -1
- package/style/base.css +1 -0
- package/style/sharedlink.css +62 -0
package/lib/sharedlink.js
CHANGED
|
@@ -11,22 +11,377 @@ import { Widget } from '@lumino/widgets';
|
|
|
11
11
|
* @returns Dialog result
|
|
12
12
|
*/
|
|
13
13
|
export async function showSharedLinkDialog({ translator }) {
|
|
14
|
-
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('
|
|
14
|
+
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyter_collaboration');
|
|
15
15
|
const token = PageConfig.getToken();
|
|
16
16
|
const url = new URL(URLExt.normalize(PageConfig.getUrl({
|
|
17
17
|
workspace: PageConfig.defaultWorkspace
|
|
18
18
|
})));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
let canCreateShare = false;
|
|
20
|
+
let canListUsers = false;
|
|
21
|
+
let canListGroups = false;
|
|
22
|
+
let canControlServer = false;
|
|
23
|
+
let hubApiUrl = '';
|
|
24
|
+
let serverName = '';
|
|
25
|
+
let serverOwner = '';
|
|
26
|
+
// If hubUser is set, we are behind a JupyterHub
|
|
27
|
+
if (PageConfig.getOption('hubUser') !== '') {
|
|
28
|
+
// Get server name and owner
|
|
29
|
+
serverOwner = PageConfig.getOption('hubServerUser');
|
|
30
|
+
serverName = PageConfig.getOption('hubServerName');
|
|
31
|
+
// Prepare the Hub API URL
|
|
32
|
+
const protocol = window.location.protocol;
|
|
33
|
+
const hostname = PageConfig.getOption('hubHost') || window.location.hostname;
|
|
34
|
+
const port = window.location.port;
|
|
35
|
+
const prefix = PageConfig.getOption('hubPrefix');
|
|
36
|
+
hubApiUrl = `${protocol}//${hostname}:${port}${prefix}api`;
|
|
37
|
+
// Check Hub version for share compatibility (>= 5.0)
|
|
38
|
+
const response = await fetch(hubApiUrl, {
|
|
39
|
+
// A GET request on base API url returns the Hub version
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `token ${token}`
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
const hubVersion = data.version;
|
|
46
|
+
const [major] = hubVersion.split('.').map(Number);
|
|
47
|
+
if (major >= 5) {
|
|
48
|
+
// The Hub version is compatible with the share feature, but we need to check if the user has rights to create a share
|
|
49
|
+
const userResponse = await fetch(`${hubApiUrl}/user`, {
|
|
50
|
+
headers: {
|
|
51
|
+
Authorization: `token ${token}`
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const userData = await userResponse.json();
|
|
55
|
+
// The permissions needed are "read:users:name" (to be able to get a user by his/her name) and "shares!user=owner" (to be able to manage shares)
|
|
56
|
+
if (userData.scopes.includes('read:users:name')) {
|
|
57
|
+
// Check for shares permission (shares!user or shares!user=owner or shares!server=owner/serverName)
|
|
58
|
+
if ((serverOwner === PageConfig.getOption('hubUser') &&
|
|
59
|
+
userData.scopes.includes('shares!user')) ||
|
|
60
|
+
userData.scopes.includes('shares!user=' + serverOwner) ||
|
|
61
|
+
userData.scopes.includes('shares!server=' + serverOwner + '/' + serverName)) {
|
|
62
|
+
// If the user has the required permissions, we can create a share
|
|
63
|
+
canCreateShare = true;
|
|
64
|
+
// Check if the user has the correct scope to list all users (not mandatory, but makes the UI easier to use)
|
|
65
|
+
if (userData.scopes.includes('list:users') ||
|
|
66
|
+
userData.scopes.includes('read:users') ||
|
|
67
|
+
userData.scopes.includes('admin:users')) {
|
|
68
|
+
canListUsers = true;
|
|
69
|
+
}
|
|
70
|
+
// Check if the user has the correct scope to list all groups (not mandatory, but makes the UI easier to use)
|
|
71
|
+
if (userData.scopes.includes('list:groups') ||
|
|
72
|
+
userData.scopes.includes('read:groups') ||
|
|
73
|
+
userData.scopes.includes('admin:groups')) {
|
|
74
|
+
canListGroups = true;
|
|
75
|
+
}
|
|
76
|
+
// Check if the user has the correct scope to control the server (not mandatory, but allows other users to start/stop the server)
|
|
77
|
+
if ((serverOwner === PageConfig.getOption('hubUser') &&
|
|
78
|
+
userData.scopes.includes('servers!user')) ||
|
|
79
|
+
userData.scopes.includes('servers!user=' + serverOwner) ||
|
|
80
|
+
userData.scopes.includes('servers!server=' + serverOwner + '/' + serverName + '/') ||
|
|
81
|
+
userData.scopes.includes('admin:servers')) {
|
|
82
|
+
canControlServer = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// If we can create a share, open the proper UI
|
|
89
|
+
if (canCreateShare) {
|
|
90
|
+
let readableServerName = serverName;
|
|
91
|
+
if (readableServerName === '') {
|
|
92
|
+
readableServerName = 'default';
|
|
93
|
+
}
|
|
94
|
+
return showDialog({
|
|
95
|
+
title: trans.__('Share Jupyter Server %1', readableServerName),
|
|
96
|
+
body: new ManageSharesBody(url.toString(), serverName, canListUsers, canListGroups, canControlServer, serverOwner, hubApiUrl, token, trans),
|
|
97
|
+
buttons: [
|
|
98
|
+
Dialog.cancelButton({
|
|
99
|
+
label: trans.__('Close'),
|
|
100
|
+
caption: trans.__('Close the dialog')
|
|
101
|
+
}),
|
|
102
|
+
Dialog.okButton({
|
|
103
|
+
label: trans.__('Copy Link'),
|
|
104
|
+
caption: trans.__('Copy the link to the Jupyter Server')
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
// If we can't create a real share, we show the legacy dialog that just copies the URL with the server owner's token
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return showDialog({
|
|
112
|
+
title: trans.__('Share Jupyter Server Link'),
|
|
113
|
+
body: new SharedLinkBody(url.toString(), token, PageConfig.getOption('hubUser') !== '', trans),
|
|
114
|
+
buttons: [
|
|
115
|
+
Dialog.cancelButton(),
|
|
116
|
+
Dialog.okButton({
|
|
117
|
+
label: trans.__('Copy Link'),
|
|
118
|
+
caption: trans.__('Copy the link to the Jupyter Server')
|
|
119
|
+
})
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
class ManageSharesBody extends Widget {
|
|
125
|
+
constructor(_url, _serverName, _canListUsers, _canListGroups, _canControlServer, _serverOwner, _hubApiUrl, _token, _trans) {
|
|
126
|
+
super();
|
|
127
|
+
this._url = _url;
|
|
128
|
+
this._serverName = _serverName;
|
|
129
|
+
this._canListUsers = _canListUsers;
|
|
130
|
+
this._canListGroups = _canListGroups;
|
|
131
|
+
this._canControlServer = _canControlServer;
|
|
132
|
+
this._serverOwner = _serverOwner;
|
|
133
|
+
this._hubApiUrl = _hubApiUrl;
|
|
134
|
+
this._token = _token;
|
|
135
|
+
this._trans = _trans;
|
|
136
|
+
this._recipients = [];
|
|
137
|
+
this._shares = [];
|
|
138
|
+
this._searchInput = null;
|
|
139
|
+
this._searchResults = null;
|
|
140
|
+
this._sharesContainer = null;
|
|
141
|
+
this._populateBody(this.node);
|
|
142
|
+
this.addClass('jp-shared-link-body');
|
|
143
|
+
this._loadShares().then(() => {
|
|
144
|
+
this._updateSharesList().then(() => {
|
|
145
|
+
this._loadUsers();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Returns the input value.
|
|
151
|
+
*/
|
|
152
|
+
getValue() {
|
|
153
|
+
return this._url;
|
|
154
|
+
}
|
|
155
|
+
onAfterAttach(msg) {
|
|
156
|
+
super.onAfterAttach(msg);
|
|
157
|
+
}
|
|
158
|
+
onBeforeDetach(msg) {
|
|
159
|
+
super.onBeforeDetach(msg);
|
|
160
|
+
}
|
|
161
|
+
async _loadUsers() {
|
|
162
|
+
// If possible, download the users list for the UI
|
|
163
|
+
if (this._canListUsers) {
|
|
164
|
+
let offset = 0;
|
|
165
|
+
const limit = 200;
|
|
166
|
+
let usersData = [];
|
|
167
|
+
let hasMore = true;
|
|
168
|
+
while (hasMore) {
|
|
169
|
+
const usersResponse = await fetch(`${this._hubApiUrl}/users?limit=${limit}&offset=${offset}`, {
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `token ${this._token}`
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
const data = await usersResponse.json();
|
|
175
|
+
usersData = usersData.concat(data);
|
|
176
|
+
hasMore = data.length === limit;
|
|
177
|
+
offset += limit;
|
|
178
|
+
}
|
|
179
|
+
const sharedUserNames = new Set(this._shares
|
|
180
|
+
.filter(share => share.type === 'user')
|
|
181
|
+
.map(share => share.name));
|
|
182
|
+
// We remove from the list the current user and the users that already have a share
|
|
183
|
+
this._recipients = usersData
|
|
184
|
+
.filter((user) => user.name !== this._serverOwner && !sharedUserNames.has(user.name))
|
|
185
|
+
.map((user) => ({ ...user, type: 'user' }));
|
|
186
|
+
}
|
|
187
|
+
// If possible, download the groups list for the UI and add them to the users list
|
|
188
|
+
if (this._canListGroups) {
|
|
189
|
+
const groupsResponse = await fetch(`${this._hubApiUrl}/groups`, {
|
|
190
|
+
headers: {
|
|
191
|
+
Authorization: `token ${this._token}`
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const groupsData = await groupsResponse.json();
|
|
195
|
+
const sharedGroupNames = new Set(this._shares
|
|
196
|
+
.filter(share => share.type === 'group')
|
|
197
|
+
.map(share => share.name));
|
|
198
|
+
this._recipients = this._recipients.concat(groupsData
|
|
199
|
+
.filter((group) => !sharedGroupNames.has(group.name))
|
|
200
|
+
.map((group) => ({ name: group.name, type: 'group' })));
|
|
201
|
+
}
|
|
202
|
+
// Sort users and groups by name in alphabetical order
|
|
203
|
+
this._recipients.sort((a, b) => a.name.localeCompare(b.name));
|
|
204
|
+
this._updateSearchResults();
|
|
205
|
+
}
|
|
206
|
+
async _loadShares() {
|
|
207
|
+
const sharesResponse = await fetch(`${this._hubApiUrl}/shares/${this._serverOwner}/${this._serverName}`, {
|
|
208
|
+
headers: {
|
|
209
|
+
Authorization: `token ${this._token}`
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
const sharesData = await sharesResponse.json();
|
|
213
|
+
this._shares = sharesData.items.map((item) => {
|
|
214
|
+
var _a, _b;
|
|
215
|
+
return ({
|
|
216
|
+
name: ((_a = item.user) === null || _a === void 0 ? void 0 : _a.name) || ((_b = item.group) === null || _b === void 0 ? void 0 : _b.name),
|
|
217
|
+
createdAt: item.created_at,
|
|
218
|
+
type: item.user ? 'user' : 'group'
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async _createShare(sharewith, type) {
|
|
223
|
+
// If the issuer can control the server, we add the "servers!server" scope to the share to let other users start/stop the server
|
|
224
|
+
const scopes = [
|
|
225
|
+
'access:servers!server=' + this._serverOwner + '/' + this._serverName
|
|
226
|
+
];
|
|
227
|
+
if (this._canControlServer) {
|
|
228
|
+
scopes.push('servers!server=' + this._serverOwner + '/' + this._serverName);
|
|
229
|
+
}
|
|
230
|
+
await fetch(`${this._hubApiUrl}/shares/${this._serverOwner}/${this._serverName}`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers: {
|
|
233
|
+
Authorization: `token ${this._token}`,
|
|
234
|
+
'Content-Type': 'application/json'
|
|
235
|
+
},
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
[type]: sharewith.name,
|
|
238
|
+
scopes
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async _deleteShare(sharewith, type) {
|
|
243
|
+
await fetch(`${this._hubApiUrl}/shares/${this._serverOwner}/${this._serverName}`, {
|
|
244
|
+
method: 'PATCH',
|
|
245
|
+
headers: {
|
|
246
|
+
Authorization: `token ${this._token}`
|
|
247
|
+
},
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
[type]: sharewith.name
|
|
27
250
|
})
|
|
28
|
-
|
|
29
|
-
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
_populateBody(dialogBody) {
|
|
254
|
+
// Add search input
|
|
255
|
+
const searchContainer = document.createElement('div');
|
|
256
|
+
searchContainer.classList.add('jp-ManageSharesBody-search-container');
|
|
257
|
+
this._searchInput = document.createElement('input');
|
|
258
|
+
this._searchInput.type = 'text';
|
|
259
|
+
this._searchInput.classList.add('jp-ManageSharesBody-search-input');
|
|
260
|
+
this._searchInput.placeholder = this._trans.__('Type to search for a user or a group to share your server with...');
|
|
261
|
+
this._searchInput.addEventListener('input', () => {
|
|
262
|
+
this._updateSearchResults();
|
|
263
|
+
});
|
|
264
|
+
searchContainer.appendChild(this._searchInput);
|
|
265
|
+
dialogBody.appendChild(searchContainer);
|
|
266
|
+
// Add search results container
|
|
267
|
+
this._searchResults = document.createElement('div');
|
|
268
|
+
this._searchResults.classList.add('jp-ManageSharesBody-search-results');
|
|
269
|
+
dialogBody.appendChild(this._searchResults);
|
|
270
|
+
// Add selected users container
|
|
271
|
+
this._sharesContainer = document.createElement('div');
|
|
272
|
+
this._sharesContainer.classList.add('jp-ManageSharesBody-selected-users');
|
|
273
|
+
dialogBody.appendChild(this._sharesContainer);
|
|
274
|
+
const input = document.createElement('input');
|
|
275
|
+
input.classList.add('jp-ManageSharesBody-url-input');
|
|
276
|
+
input.readOnly = true;
|
|
277
|
+
input.value = this._url;
|
|
278
|
+
dialogBody.appendChild(input);
|
|
279
|
+
dialogBody.insertAdjacentHTML('beforeend', '<br>');
|
|
280
|
+
dialogBody.insertAdjacentText('beforeend', this._trans.__('Warning: Anyone you share this server with will have access to all your files, not just the currently open file.'));
|
|
281
|
+
dialogBody.insertAdjacentHTML('beforeend', '<br>');
|
|
282
|
+
dialogBody.insertAdjacentText('beforeend', this._trans.__('If your Hub administrator allows it, you can create another server dedicated to sharing a specific project.'));
|
|
283
|
+
}
|
|
284
|
+
_updateSearchResults() {
|
|
285
|
+
var _a;
|
|
286
|
+
if (!this._searchResults) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this._searchResults.innerHTML = '';
|
|
290
|
+
const query = ((_a = this._searchInput) === null || _a === void 0 ? void 0 : _a.value) || '';
|
|
291
|
+
const filteredUsers = this._recipients.filter(user => user.name.toLowerCase().includes(query.toLowerCase()) || query === '');
|
|
292
|
+
filteredUsers.forEach(user => {
|
|
293
|
+
var _a;
|
|
294
|
+
const userElement = document.createElement('div');
|
|
295
|
+
userElement.classList.add('jp-ManageSharesBody-user-item');
|
|
296
|
+
if (user.type === 'group') {
|
|
297
|
+
userElement.textContent = this._trans.__('Group %1', user.name);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
userElement.textContent = user.name;
|
|
301
|
+
}
|
|
302
|
+
userElement.addEventListener('click', async () => {
|
|
303
|
+
await this._createShare(user, user.type);
|
|
304
|
+
await this._loadShares();
|
|
305
|
+
await this._updateSharesList();
|
|
306
|
+
// Removing the new user from the search results
|
|
307
|
+
const sharedUserNames = new Set(this._shares.map(share => share.name));
|
|
308
|
+
this._recipients = this._recipients.filter((user) => user.name !== this._serverOwner && !sharedUserNames.has(user.name));
|
|
309
|
+
this._updateSearchResults();
|
|
310
|
+
});
|
|
311
|
+
(_a = this._searchResults) === null || _a === void 0 ? void 0 : _a.appendChild(userElement);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async _updateSharesList() {
|
|
315
|
+
if (!this._sharesContainer) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this._sharesContainer.innerHTML = '';
|
|
319
|
+
const table = document.createElement('table');
|
|
320
|
+
table.classList.add('jp-ManageSharesBody-shares-table');
|
|
321
|
+
const headerRow = document.createElement('tr');
|
|
322
|
+
const thSharedWith = document.createElement('th');
|
|
323
|
+
thSharedWith.textContent = this._trans.__('Shared with');
|
|
324
|
+
const thSharedSince = document.createElement('th');
|
|
325
|
+
thSharedSince.textContent = this._trans.__('Shared since');
|
|
326
|
+
const thActions = document.createElement('th');
|
|
327
|
+
thActions.textContent = this._trans.__('Actions');
|
|
328
|
+
headerRow.appendChild(thSharedWith);
|
|
329
|
+
headerRow.appendChild(thSharedSince);
|
|
330
|
+
headerRow.appendChild(thActions);
|
|
331
|
+
table.appendChild(headerRow);
|
|
332
|
+
if (this._shares.length === 0) {
|
|
333
|
+
const row = document.createElement('tr');
|
|
334
|
+
const cell = document.createElement('td');
|
|
335
|
+
cell.colSpan = 3;
|
|
336
|
+
cell.textContent = this._trans.__('Your server is not shared to anybody yet. You can search for users and groups above.');
|
|
337
|
+
row.appendChild(cell);
|
|
338
|
+
table.appendChild(row);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
this._shares.forEach(share => {
|
|
342
|
+
const row = document.createElement('tr');
|
|
343
|
+
// Shared with cell
|
|
344
|
+
const sharedWithCell = document.createElement('td');
|
|
345
|
+
if (share.type === 'group') {
|
|
346
|
+
sharedWithCell.textContent = this._trans.__('Group %1', share.name);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
sharedWithCell.textContent = share.name;
|
|
350
|
+
}
|
|
351
|
+
row.appendChild(sharedWithCell);
|
|
352
|
+
// Shared since cell
|
|
353
|
+
const sharedSinceCell = document.createElement('td');
|
|
354
|
+
const formattedDate = new Date(share.createdAt).toLocaleString([], {
|
|
355
|
+
hour: '2-digit',
|
|
356
|
+
minute: '2-digit',
|
|
357
|
+
year: 'numeric',
|
|
358
|
+
month: '2-digit',
|
|
359
|
+
day: '2-digit'
|
|
360
|
+
});
|
|
361
|
+
sharedSinceCell.textContent = formattedDate;
|
|
362
|
+
row.appendChild(sharedSinceCell);
|
|
363
|
+
// Actions cell
|
|
364
|
+
const actionsCell = document.createElement('td');
|
|
365
|
+
const revokeButton = document.createElement('button');
|
|
366
|
+
revokeButton.textContent = this._trans.__('Revoke');
|
|
367
|
+
revokeButton.classList.add('jp-mod-styled');
|
|
368
|
+
revokeButton.addEventListener('click', async () => {
|
|
369
|
+
await this._deleteShare(share, share.type);
|
|
370
|
+
await this._loadShares();
|
|
371
|
+
await this._updateSharesList();
|
|
372
|
+
await this._loadUsers();
|
|
373
|
+
// Removing the new user from the search results
|
|
374
|
+
const sharedUserNames = new Set(this._shares.map(share => share.name));
|
|
375
|
+
this._recipients = this._recipients.filter((user) => user.name !== this._serverOwner && !sharedUserNames.has(user.name));
|
|
376
|
+
this._updateSearchResults();
|
|
377
|
+
});
|
|
378
|
+
actionsCell.appendChild(revokeButton);
|
|
379
|
+
row.appendChild(actionsCell);
|
|
380
|
+
table.appendChild(row);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
this._sharesContainer.appendChild(table);
|
|
384
|
+
}
|
|
30
385
|
}
|
|
31
386
|
class SharedLinkBody extends Widget {
|
|
32
387
|
constructor(_url, _token, _behindHub, _trans) {
|
|
@@ -36,12 +391,12 @@ class SharedLinkBody extends Widget {
|
|
|
36
391
|
this._behindHub = _behindHub;
|
|
37
392
|
this._trans = _trans;
|
|
38
393
|
this._tokenCheckbox = null;
|
|
39
|
-
this.
|
|
394
|
+
this._onTokenChange = (e) => {
|
|
40
395
|
const target = e.target;
|
|
41
|
-
this.
|
|
396
|
+
this._updateContent(target === null || target === void 0 ? void 0 : target.checked);
|
|
42
397
|
};
|
|
43
398
|
this._warning = document.createElement('div');
|
|
44
|
-
this.
|
|
399
|
+
this._populateBody(this.node);
|
|
45
400
|
this.addClass('jp-shared-link-body');
|
|
46
401
|
}
|
|
47
402
|
/**
|
|
@@ -62,14 +417,14 @@ class SharedLinkBody extends Widget {
|
|
|
62
417
|
onAfterAttach(msg) {
|
|
63
418
|
var _a;
|
|
64
419
|
super.onAfterAttach(msg);
|
|
65
|
-
(_a = this._tokenCheckbox) === null || _a === void 0 ? void 0 : _a.addEventListener('change', this.
|
|
420
|
+
(_a = this._tokenCheckbox) === null || _a === void 0 ? void 0 : _a.addEventListener('change', this._onTokenChange);
|
|
66
421
|
}
|
|
67
422
|
onBeforeDetach(msg) {
|
|
68
423
|
var _a;
|
|
69
|
-
(_a = this._tokenCheckbox) === null || _a === void 0 ? void 0 : _a.removeEventListener('change', this.
|
|
424
|
+
(_a = this._tokenCheckbox) === null || _a === void 0 ? void 0 : _a.removeEventListener('change', this._onTokenChange);
|
|
70
425
|
super.onBeforeDetach(msg);
|
|
71
426
|
}
|
|
72
|
-
|
|
427
|
+
_updateContent(withToken) {
|
|
73
428
|
this._warning.innerHTML = '';
|
|
74
429
|
const urlInput = this.node.querySelector('input[readonly]');
|
|
75
430
|
if (withToken) {
|
|
@@ -108,15 +463,18 @@ class SharedLinkBody extends Widget {
|
|
|
108
463
|
}
|
|
109
464
|
}
|
|
110
465
|
}
|
|
111
|
-
|
|
112
|
-
|
|
466
|
+
_populateBody(dialogBody) {
|
|
467
|
+
const input = document.createElement('input');
|
|
468
|
+
input.readOnly = true;
|
|
469
|
+
input.value = this._url;
|
|
470
|
+
dialogBody.appendChild(input);
|
|
113
471
|
if (this._token) {
|
|
114
472
|
const label = dialogBody.appendChild(document.createElement('label'));
|
|
115
473
|
label.insertAdjacentHTML('beforeend', '<input type="checkbox">');
|
|
116
474
|
this._tokenCheckbox = label.firstChild;
|
|
117
475
|
label.insertAdjacentText('beforeend', this._trans.__('Include token in URL'));
|
|
118
476
|
dialogBody.insertAdjacentElement('beforeend', this._warning);
|
|
119
|
-
this.
|
|
477
|
+
this._updateContent(false);
|
|
120
478
|
}
|
|
121
479
|
}
|
|
122
480
|
}
|
package/package.json
CHANGED
package/style/base.css
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* -----------------------------------------------------------------------------
|
|
2
|
+
| Copyright (c) Jupyter Development Team.
|
|
3
|
+
| Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|---------------------------------------------------------------------------- */
|
|
5
|
+
|
|
6
|
+
.jp-shared-link-body {
|
|
7
|
+
user-select: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.jp-ManageSharesBody-search-container {
|
|
11
|
+
margin-bottom: 10px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.jp-ManageSharesBody-search-input {
|
|
15
|
+
width: 100%;
|
|
16
|
+
padding: 5px;
|
|
17
|
+
margin-top: 5px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.jp-ManageSharesBody-search-results {
|
|
21
|
+
height: 10em;
|
|
22
|
+
overflow-y: auto;
|
|
23
|
+
border: 1px solid var(--jp-border-color0);
|
|
24
|
+
padding: 5px;
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.jp-ManageSharesBody-user-item {
|
|
29
|
+
padding: 5px;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.jp-ManageSharesBody-user-item:hover {
|
|
34
|
+
background-color: var(--jp-border-color3);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.jp-ManageSharesBody-selected-users {
|
|
38
|
+
margin-top: 10px;
|
|
39
|
+
height: 10em;
|
|
40
|
+
overflow-y: auto;
|
|
41
|
+
border: 1px solid var(--jp-border-color0);
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.jp-ManageSharesBody-url-input {
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding: 5px;
|
|
48
|
+
margin-top: 10px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.jp-ManageSharesBody-shares-table {
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.jp-ManageSharesBody-shares-table td:nth-child(2),
|
|
56
|
+
.jp-ManageSharesBody-shares-table td:nth-child(3) {
|
|
57
|
+
text-align: center;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.jp-Dialog-content:has(.jp-shared-link-body) {
|
|
61
|
+
max-height: 750px;
|
|
62
|
+
}
|