@jupyterlab/apputils-extension 4.0.0-alpha.9 → 4.0.0-beta.1

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.
Files changed (48) hide show
  1. package/lib/announcements.d.ts +2 -0
  2. package/lib/announcements.js +227 -0
  3. package/lib/announcements.js.map +1 -0
  4. package/lib/index.js +66 -12
  5. package/lib/index.js.map +1 -1
  6. package/lib/notificationplugin.d.ts +5 -0
  7. package/lib/notificationplugin.js +504 -0
  8. package/lib/notificationplugin.js.map +1 -0
  9. package/lib/palette.js +3 -3
  10. package/lib/palette.js.map +1 -1
  11. package/lib/settingconnector.d.ts +4 -1
  12. package/lib/settingconnector.js +8 -1
  13. package/lib/settingconnector.js.map +1 -1
  14. package/lib/settingsplugin.js +6 -5
  15. package/lib/settingsplugin.js.map +1 -1
  16. package/lib/shortcuts.d.ts +21 -0
  17. package/lib/shortcuts.js +137 -0
  18. package/lib/shortcuts.js.map +1 -0
  19. package/lib/statusbarplugin.js +10 -9
  20. package/lib/statusbarplugin.js.map +1 -1
  21. package/lib/themesplugins.js +24 -0
  22. package/lib/themesplugins.js.map +1 -1
  23. package/lib/toolbarregistryplugin.js +4 -0
  24. package/lib/toolbarregistryplugin.js.map +1 -1
  25. package/lib/workspacesplugin.js +6 -6
  26. package/lib/workspacesplugin.js.map +1 -1
  27. package/package.json +28 -23
  28. package/schema/notification.json +48 -0
  29. package/schema/palette.json +2 -0
  30. package/schema/sanitizer.json +25 -0
  31. package/schema/utilityCommands.json +35 -0
  32. package/src/announcements.ts +297 -0
  33. package/src/index.ts +721 -0
  34. package/src/notificationplugin.tsx +902 -0
  35. package/src/palette.ts +213 -0
  36. package/src/settingconnector.ts +73 -0
  37. package/src/settingsplugin.ts +66 -0
  38. package/src/shortcuts.tsx +191 -0
  39. package/src/statusbarplugin.ts +145 -0
  40. package/src/themesplugins.ts +290 -0
  41. package/src/toolbarregistryplugin.ts +29 -0
  42. package/src/typings.d.ts +9 -0
  43. package/src/workspacesplugin.ts +306 -0
  44. package/style/base.css +2 -1
  45. package/style/contextualhelp.css +42 -0
  46. package/style/notification.css +227 -0
  47. package/style/scrollbar.raw.css +64 -0
  48. package/style/redirect.css +0 -15
package/src/index.ts ADDED
@@ -0,0 +1,721 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+ /**
6
+ * @packageDocumentation
7
+ * @module apputils-extension
8
+ */
9
+
10
+ import {
11
+ ILayoutRestorer,
12
+ IRouter,
13
+ JupyterFrontEnd,
14
+ JupyterFrontEndPlugin
15
+ } from '@jupyterlab/application';
16
+ import {
17
+ Dialog,
18
+ ICommandPalette,
19
+ ISanitizer,
20
+ ISessionContextDialogs,
21
+ ISplashScreen,
22
+ IWindowResolver,
23
+ MainAreaWidget,
24
+ Printing,
25
+ Sanitizer,
26
+ SessionContextDialogs,
27
+ WindowResolver
28
+ } from '@jupyterlab/apputils';
29
+ import { PageConfig, PathExt, URLExt } from '@jupyterlab/coreutils';
30
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
31
+ import { IStateDB, StateDB } from '@jupyterlab/statedb';
32
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
33
+ import { jupyterFaviconIcon } from '@jupyterlab/ui-components';
34
+ import { PromiseDelegate } from '@lumino/coreutils';
35
+ import { DisposableDelegate } from '@lumino/disposable';
36
+ import { Debouncer, Throttler } from '@lumino/polling';
37
+ import { announcements } from './announcements';
38
+ import { notificationPlugin } from './notificationplugin';
39
+ import { Palette } from './palette';
40
+ import { settingsPlugin } from './settingsplugin';
41
+ import { kernelStatus, runningSessionsStatus } from './statusbarplugin';
42
+ import { themesPaletteMenuPlugin, themesPlugin } from './themesplugins';
43
+ import { toolbarRegistry } from './toolbarregistryplugin';
44
+ import { workspacesPlugin } from './workspacesplugin';
45
+ import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
46
+ import { displayShortcuts } from './shortcuts';
47
+
48
+ /**
49
+ * The interval in milliseconds before recover options appear during splash.
50
+ */
51
+ const SPLASH_RECOVER_TIMEOUT = 12000;
52
+
53
+ /**
54
+ * The command IDs used by the apputils plugin.
55
+ */
56
+ namespace CommandIDs {
57
+ export const loadState = 'apputils:load-statedb';
58
+
59
+ export const print = 'apputils:print';
60
+
61
+ export const reset = 'apputils:reset';
62
+
63
+ export const resetOnLoad = 'apputils:reset-on-load';
64
+
65
+ export const runFirstEnabled = 'apputils:run-first-enabled';
66
+
67
+ export const runAllEnabled = 'apputils:run-all-enabled';
68
+
69
+ export const toggleHeader = 'apputils:toggle-header';
70
+
71
+ export const displayShortcuts = 'apputils:display-shortcuts';
72
+ }
73
+
74
+ /**
75
+ * The default command palette extension.
76
+ */
77
+ const palette: JupyterFrontEndPlugin<ICommandPalette> = {
78
+ id: '@jupyterlab/apputils-extension:palette',
79
+ autoStart: true,
80
+ requires: [ITranslator],
81
+ provides: ICommandPalette,
82
+ optional: [ISettingRegistry],
83
+ activate: (
84
+ app: JupyterFrontEnd,
85
+ translator: ITranslator,
86
+ settingRegistry: ISettingRegistry | null
87
+ ) => {
88
+ return Palette.activate(app, translator, settingRegistry);
89
+ }
90
+ };
91
+
92
+ /**
93
+ * The default command palette's restoration extension.
94
+ *
95
+ * #### Notes
96
+ * The command palette's restoration logic is handled separately from the
97
+ * command palette provider extension because the layout restorer dependency
98
+ * causes the command palette to be unavailable to other extensions earlier
99
+ * in the application load cycle.
100
+ */
101
+ const paletteRestorer: JupyterFrontEndPlugin<void> = {
102
+ id: '@jupyterlab/apputils-extension:palette-restorer',
103
+ autoStart: true,
104
+ requires: [ILayoutRestorer, ITranslator],
105
+ activate: (
106
+ app: JupyterFrontEnd,
107
+ restorer: ILayoutRestorer,
108
+ translator: ITranslator
109
+ ) => {
110
+ Palette.restore(app, restorer, translator);
111
+ }
112
+ };
113
+
114
+ /**
115
+ * The default window name resolver provider.
116
+ */
117
+ const resolver: JupyterFrontEndPlugin<IWindowResolver> = {
118
+ id: '@jupyterlab/apputils-extension:resolver',
119
+ autoStart: true,
120
+ provides: IWindowResolver,
121
+ requires: [JupyterFrontEnd.IPaths, IRouter],
122
+ activate: async (
123
+ app: JupyterFrontEnd,
124
+ paths: JupyterFrontEnd.IPaths,
125
+ router: IRouter
126
+ ) => {
127
+ const { hash, search } = router.current;
128
+ const query = URLExt.queryStringToObject(search || '');
129
+ const solver = new WindowResolver();
130
+ const workspace = PageConfig.getOption('workspace');
131
+ const treePath = PageConfig.getOption('treePath');
132
+ const mode =
133
+ PageConfig.getOption('mode') === 'multiple-document' ? 'lab' : 'doc';
134
+ // This is used as a key in local storage to refer to workspaces, either the name
135
+ // of the workspace or the string PageConfig.defaultWorkspace. Both lab and doc modes share the same workspace.
136
+ const candidate = workspace ? workspace : PageConfig.defaultWorkspace;
137
+ const rest = treePath ? URLExt.join('tree', treePath) : '';
138
+ try {
139
+ await solver.resolve(candidate);
140
+ return solver;
141
+ } catch (error) {
142
+ // Window resolution has failed so the URL must change. Return a promise
143
+ // that never resolves to prevent the application from loading plugins
144
+ // that rely on `IWindowResolver`.
145
+ return new Promise<IWindowResolver>(() => {
146
+ const { base } = paths.urls;
147
+ const pool =
148
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
149
+ const random = pool[Math.floor(Math.random() * pool.length)];
150
+ let path = URLExt.join(base, mode, 'workspaces', `auto-${random}`);
151
+ path = rest ? URLExt.join(path, URLExt.encodeParts(rest)) : path;
152
+
153
+ // Reset the workspace on load.
154
+ query['reset'] = '';
155
+
156
+ const url = path + URLExt.objectToQueryString(query) + (hash || '');
157
+ router.navigate(url, { hard: true });
158
+ });
159
+ }
160
+ }
161
+ };
162
+
163
+ /**
164
+ * The default splash screen provider.
165
+ */
166
+ const splash: JupyterFrontEndPlugin<ISplashScreen> = {
167
+ id: '@jupyterlab/apputils-extension:splash',
168
+ autoStart: true,
169
+ requires: [ITranslator],
170
+ provides: ISplashScreen,
171
+ activate: (app: JupyterFrontEnd, translator: ITranslator) => {
172
+ const trans = translator.load('jupyterlab');
173
+ const { commands, restored } = app;
174
+
175
+ // Create splash element and populate it.
176
+ const splash = document.createElement('div');
177
+ const galaxy = document.createElement('div');
178
+ const logo = document.createElement('div');
179
+
180
+ splash.id = 'jupyterlab-splash';
181
+ galaxy.id = 'galaxy';
182
+ logo.id = 'main-logo';
183
+
184
+ jupyterFaviconIcon.element({
185
+ container: logo,
186
+
187
+ stylesheet: 'splash'
188
+ });
189
+
190
+ galaxy.appendChild(logo);
191
+ ['1', '2', '3'].forEach(id => {
192
+ const moon = document.createElement('div');
193
+ const planet = document.createElement('div');
194
+
195
+ moon.id = `moon${id}`;
196
+ moon.className = 'moon orbit';
197
+ planet.id = `planet${id}`;
198
+ planet.className = 'planet';
199
+
200
+ moon.appendChild(planet);
201
+ galaxy.appendChild(moon);
202
+ });
203
+
204
+ splash.appendChild(galaxy);
205
+
206
+ // Create debounced recovery dialog function.
207
+ let dialog: Dialog<unknown> | null;
208
+ const recovery = new Throttler(
209
+ async () => {
210
+ if (dialog) {
211
+ return;
212
+ }
213
+
214
+ dialog = new Dialog({
215
+ title: trans.__('Loading…'),
216
+ body: trans.__(`The loading screen is taking a long time.
217
+ Would you like to clear the workspace or keep waiting?`),
218
+ buttons: [
219
+ Dialog.cancelButton({ label: trans.__('Keep Waiting') }),
220
+ Dialog.warnButton({ label: trans.__('Clear Workspace') })
221
+ ]
222
+ });
223
+
224
+ try {
225
+ const result = await dialog.launch();
226
+ dialog.dispose();
227
+ dialog = null;
228
+ if (result.button.accept && commands.hasCommand(CommandIDs.reset)) {
229
+ return commands.execute(CommandIDs.reset);
230
+ }
231
+
232
+ // Re-invoke the recovery timer in the next frame.
233
+ requestAnimationFrame(() => {
234
+ // Because recovery can be stopped, handle invocation rejection.
235
+ void recovery.invoke().catch(_ => undefined);
236
+ });
237
+ } catch (error) {
238
+ /* no-op */
239
+ }
240
+ },
241
+ { limit: SPLASH_RECOVER_TIMEOUT, edge: 'trailing' }
242
+ );
243
+
244
+ // Return ISplashScreen.
245
+ let splashCount = 0;
246
+ return {
247
+ show: (light = true) => {
248
+ splash.classList.remove('splash-fade');
249
+ splash.classList.toggle('light', light);
250
+ splash.classList.toggle('dark', !light);
251
+ splashCount++;
252
+ document.body.appendChild(splash);
253
+
254
+ // Because recovery can be stopped, handle invocation rejection.
255
+ void recovery.invoke().catch(_ => undefined);
256
+
257
+ return new DisposableDelegate(async () => {
258
+ await restored;
259
+ if (--splashCount === 0) {
260
+ void recovery.stop();
261
+
262
+ if (dialog) {
263
+ dialog.dispose();
264
+ dialog = null;
265
+ }
266
+
267
+ splash.classList.add('splash-fade');
268
+ window.setTimeout(() => {
269
+ document.body.removeChild(splash);
270
+ }, 200);
271
+ }
272
+ });
273
+ }
274
+ };
275
+ }
276
+ };
277
+
278
+ const print: JupyterFrontEndPlugin<void> = {
279
+ id: '@jupyterlab/apputils-extension:print',
280
+ autoStart: true,
281
+ requires: [ITranslator],
282
+ activate: (app: JupyterFrontEnd, translator: ITranslator) => {
283
+ const trans = translator.load('jupyterlab');
284
+ app.commands.addCommand(CommandIDs.print, {
285
+ label: trans.__('Print…'),
286
+ isEnabled: () => {
287
+ const widget = app.shell.currentWidget;
288
+ return Printing.getPrintFunction(widget) !== null;
289
+ },
290
+ execute: async () => {
291
+ const widget = app.shell.currentWidget;
292
+ const printFunction = Printing.getPrintFunction(widget);
293
+ if (printFunction) {
294
+ await printFunction();
295
+ }
296
+ }
297
+ });
298
+ }
299
+ };
300
+
301
+ export const toggleHeader: JupyterFrontEndPlugin<void> = {
302
+ id: '@jupyterlab/apputils-extension:toggle-header',
303
+ autoStart: true,
304
+ requires: [ITranslator],
305
+ optional: [ICommandPalette],
306
+ activate: (
307
+ app: JupyterFrontEnd,
308
+ translator: ITranslator,
309
+ palette: ICommandPalette | null
310
+ ) => {
311
+ const trans = translator.load('jupyterlab');
312
+
313
+ const category: string = trans.__('Main Area');
314
+ app.commands.addCommand(CommandIDs.toggleHeader, {
315
+ label: trans.__('Show Header Above Content'),
316
+ isEnabled: () =>
317
+ app.shell.currentWidget instanceof MainAreaWidget &&
318
+ !app.shell.currentWidget.contentHeader.isDisposed &&
319
+ app.shell.currentWidget.contentHeader.widgets.length > 0,
320
+ isToggled: () => {
321
+ const widget = app.shell.currentWidget;
322
+ return widget instanceof MainAreaWidget
323
+ ? !widget.contentHeader.isHidden
324
+ : false;
325
+ },
326
+ execute: async () => {
327
+ const widget = app.shell.currentWidget;
328
+ if (widget instanceof MainAreaWidget) {
329
+ widget.contentHeader.setHidden(!widget.contentHeader.isHidden);
330
+ }
331
+ }
332
+ });
333
+ if (palette) {
334
+ palette.addItem({ command: CommandIDs.toggleHeader, category });
335
+ }
336
+ }
337
+ };
338
+
339
+ /**
340
+ * Update the browser title based on the workspace and the current
341
+ * active item.
342
+ */
343
+ async function updateTabTitle(workspace: string, db: IStateDB, name: string) {
344
+ const data: any = await db.toJSON();
345
+ let current: string = data['layout-restorer:data']?.main?.current;
346
+ if (current === undefined) {
347
+ document.title = `${PageConfig.getOption('appName') || 'JupyterLab'}${
348
+ workspace.startsWith('auto-') ? ` (${workspace})` : ``
349
+ }`;
350
+ } else {
351
+ // File name from current path
352
+ let currentFile: string = PathExt.basename(
353
+ decodeURIComponent(window.location.href)
354
+ );
355
+ // Truncate to first 12 characters of current document name + ... if length > 15
356
+ currentFile =
357
+ currentFile.length > 15
358
+ ? currentFile.slice(0, 12).concat(`…`)
359
+ : currentFile;
360
+ // Number of restorable items that are either notebooks or editors
361
+ const count: number = Object.keys(data).filter(
362
+ item => item.startsWith('notebook') || item.startsWith('editor')
363
+ ).length;
364
+
365
+ if (workspace.startsWith('auto-')) {
366
+ document.title = `${currentFile} (${workspace}${
367
+ count > 1 ? ` : ${count}` : ``
368
+ }) - ${name}`;
369
+ } else {
370
+ document.title = `${currentFile}${
371
+ count > 1 ? ` (${count})` : ``
372
+ } - ${name}`;
373
+ }
374
+ }
375
+ }
376
+
377
+ /**
378
+ * The default state database for storing application state.
379
+ *
380
+ * #### Notes
381
+ * If this extension is loaded with a window resolver, it will automatically add
382
+ * state management commands, URL support for `clone` and `reset`, and workspace
383
+ * auto-saving. Otherwise, it will return a simple in-memory state database.
384
+ */
385
+ const state: JupyterFrontEndPlugin<IStateDB> = {
386
+ id: '@jupyterlab/apputils-extension:state',
387
+ autoStart: true,
388
+ provides: IStateDB,
389
+ requires: [JupyterFrontEnd.IPaths, IRouter, ITranslator],
390
+ optional: [IWindowResolver],
391
+ activate: (
392
+ app: JupyterFrontEnd,
393
+ paths: JupyterFrontEnd.IPaths,
394
+ router: IRouter,
395
+ translator: ITranslator,
396
+ resolver: IWindowResolver | null
397
+ ) => {
398
+ const trans = translator.load('jupyterlab');
399
+
400
+ if (resolver === null) {
401
+ return new StateDB();
402
+ }
403
+
404
+ let resolved = false;
405
+ const { commands, name, serviceManager } = app;
406
+ const { workspaces } = serviceManager;
407
+ const workspace = resolver.name;
408
+ const transform = new PromiseDelegate<StateDB.DataTransform>();
409
+ const db = new StateDB({ transform: transform.promise });
410
+ const save = new Debouncer(async () => {
411
+ const id = workspace;
412
+ const metadata = { id };
413
+ const data = await db.toJSON();
414
+ await workspaces.save(id, { data, metadata });
415
+ });
416
+
417
+ // Any time the local state database changes, save the workspace.
418
+ db.changed.connect(() => void save.invoke(), db);
419
+ db.changed.connect(() => updateTabTitle(workspace, db, name));
420
+
421
+ commands.addCommand(CommandIDs.loadState, {
422
+ label: trans.__('Load state for the current workspace.'),
423
+ execute: async (args: IRouter.ILocation) => {
424
+ // Since the command can be executed an arbitrary number of times, make
425
+ // sure it is safe to call multiple times.
426
+ if (resolved) {
427
+ return;
428
+ }
429
+
430
+ const { hash, path, search } = args;
431
+ const query = URLExt.queryStringToObject(search || '');
432
+ const clone =
433
+ typeof query['clone'] === 'string'
434
+ ? query['clone'] === ''
435
+ ? PageConfig.defaultWorkspace
436
+ : query['clone']
437
+ : null;
438
+ const source = clone || workspace || null;
439
+
440
+ if (source === null) {
441
+ console.error(`${CommandIDs.loadState} cannot load null workspace.`);
442
+ return;
443
+ }
444
+
445
+ try {
446
+ const saved = await workspaces.fetch(source);
447
+
448
+ // If this command is called after a reset, the state database
449
+ // will already be resolved.
450
+ if (!resolved) {
451
+ resolved = true;
452
+ transform.resolve({ type: 'overwrite', contents: saved.data });
453
+ }
454
+ } catch ({ message }) {
455
+ console.warn(`Fetching workspace "${workspace}" failed.`, message);
456
+
457
+ // If the workspace does not exist, cancel the data transformation
458
+ // and save a workspace with the current user state data.
459
+ if (!resolved) {
460
+ resolved = true;
461
+ transform.resolve({ type: 'cancel', contents: null });
462
+ }
463
+ }
464
+
465
+ if (source === clone) {
466
+ // Maintain the query string parameters but remove `clone`.
467
+ delete query['clone'];
468
+
469
+ const url = path + URLExt.objectToQueryString(query) + hash;
470
+ const cloned = save.invoke().then(() => router.stop);
471
+
472
+ // After the state has been cloned, navigate to the URL.
473
+ void cloned.then(() => {
474
+ router.navigate(url);
475
+ });
476
+
477
+ return cloned;
478
+ }
479
+
480
+ // After the state database has finished loading, save it.
481
+ await save.invoke();
482
+ }
483
+ });
484
+
485
+ commands.addCommand(CommandIDs.reset, {
486
+ label: trans.__('Reset Application State'),
487
+ execute: async ({ reload }: { reload: boolean }) => {
488
+ await db.clear();
489
+ await save.invoke();
490
+ if (reload) {
491
+ router.reload();
492
+ }
493
+ }
494
+ });
495
+
496
+ commands.addCommand(CommandIDs.resetOnLoad, {
497
+ label: trans.__('Reset state when loading for the workspace.'),
498
+ execute: (args: IRouter.ILocation) => {
499
+ const { hash, path, search } = args;
500
+ const query = URLExt.queryStringToObject(search || '');
501
+ const reset = 'reset' in query;
502
+ const clone = 'clone' in query;
503
+
504
+ if (!reset) {
505
+ return;
506
+ }
507
+
508
+ // If the state database has already been resolved, resetting is
509
+ // impossible without reloading.
510
+ if (resolved) {
511
+ return router.reload();
512
+ }
513
+
514
+ // Empty the state database.
515
+ resolved = true;
516
+ transform.resolve({ type: 'clear', contents: null });
517
+
518
+ // Maintain the query string parameters but remove `reset`.
519
+ delete query['reset'];
520
+
521
+ const url = path + URLExt.objectToQueryString(query) + hash;
522
+ const cleared = db.clear().then(() => save.invoke());
523
+
524
+ // After the state has been reset, navigate to the URL.
525
+ if (clone) {
526
+ void cleared.then(() => {
527
+ router.navigate(url, { hard: true });
528
+ });
529
+ } else {
530
+ void cleared.then(() => {
531
+ router.navigate(url);
532
+ });
533
+ }
534
+
535
+ return cleared;
536
+ }
537
+ });
538
+
539
+ router.register({
540
+ command: CommandIDs.loadState,
541
+ pattern: /.?/,
542
+ rank: 30 // High priority: 30:100.
543
+ });
544
+
545
+ router.register({
546
+ command: CommandIDs.resetOnLoad,
547
+ pattern: /(\?reset|\&reset)($|&)/,
548
+ rank: 20 // High priority: 20:100.
549
+ });
550
+
551
+ return db;
552
+ }
553
+ };
554
+
555
+ /**
556
+ * The default session context dialogs extension.
557
+ */
558
+ const sessionDialogs: JupyterFrontEndPlugin<ISessionContextDialogs> = {
559
+ id: '@jupyterlab/apputils-extension:sessionDialogs',
560
+ provides: ISessionContextDialogs,
561
+ optional: [ITranslator],
562
+ autoStart: true,
563
+ activate: async (app: JupyterFrontEnd, translator: ITranslator | null) => {
564
+ return new SessionContextDialogs({
565
+ translator: translator ?? nullTranslator
566
+ });
567
+ }
568
+ };
569
+
570
+ /**
571
+ * Utility commands
572
+ */
573
+ const utilityCommands: JupyterFrontEndPlugin<void> = {
574
+ id: '@jupyterlab/apputils-extension:utilityCommands',
575
+ requires: [ITranslator],
576
+ optional: [ICommandPalette],
577
+ autoStart: true,
578
+ activate: (
579
+ app: JupyterFrontEnd,
580
+ translator: ITranslator,
581
+ palette: ICommandPalette | null
582
+ ) => {
583
+ const trans = translator.load('jupyterlab');
584
+ const { commands } = app;
585
+ commands.addCommand(CommandIDs.runFirstEnabled, {
586
+ label: trans.__('Run First Enabled Command'),
587
+ execute: args => {
588
+ const commands: string[] = args.commands as string[];
589
+ const commandArgs: any = args.args;
590
+ const argList = Array.isArray(args);
591
+ for (let i = 0; i < commands.length; i++) {
592
+ const cmd = commands[i];
593
+ const arg = argList ? commandArgs[i] : commandArgs;
594
+ if (app.commands.isEnabled(cmd, arg)) {
595
+ return app.commands.execute(cmd, arg);
596
+ }
597
+ }
598
+ }
599
+ });
600
+
601
+ // Add a command for taking lists of commands and command arguments
602
+ // and running all the enabled commands.
603
+ commands.addCommand(CommandIDs.runAllEnabled, {
604
+ label: trans.__('Run All Enabled Commands Passed as Args'),
605
+ execute: async args => {
606
+ const commands: string[] = args.commands as string[];
607
+ const commandArgs: any = args.args;
608
+ const argList = Array.isArray(args);
609
+ const errorIfNotEnabled: boolean = args.errorIfNotEnabled as boolean;
610
+ for (let i = 0; i < commands.length; i++) {
611
+ const cmd = commands[i];
612
+ const arg = argList ? commandArgs[i] : commandArgs;
613
+ if (app.commands.isEnabled(cmd, arg)) {
614
+ await app.commands.execute(cmd, arg);
615
+ } else {
616
+ if (errorIfNotEnabled) {
617
+ console.error(`${cmd} is not enabled.`);
618
+ }
619
+ }
620
+ }
621
+ }
622
+ });
623
+
624
+ commands.addCommand(CommandIDs.displayShortcuts, {
625
+ label: trans.__('Show Keyboard Shortcuts'),
626
+ caption: trans.__(
627
+ 'Show relevant keyboard shortcuts for the current active widget'
628
+ ),
629
+ execute: args => {
630
+ const included = app.shell.currentWidget?.node.contains(
631
+ document.activeElement
632
+ );
633
+
634
+ if (!included) {
635
+ const currentNode =
636
+ (app.shell.currentWidget as MainAreaWidget)?.content.node ??
637
+ app.shell.currentWidget?.node;
638
+ currentNode?.focus();
639
+ }
640
+ const options = { commands, trans };
641
+ return displayShortcuts(options);
642
+ }
643
+ });
644
+
645
+ if (palette) {
646
+ const category: string = trans.__('Help');
647
+ palette.addItem({ command: CommandIDs.displayShortcuts, category });
648
+ }
649
+ }
650
+ };
651
+
652
+ /**
653
+ * The default HTML sanitizer.
654
+ */
655
+ const sanitizer: JupyterFrontEndPlugin<IRenderMime.ISanitizer> = {
656
+ id: '@jupyterlab/apputils-extension:sanitizer',
657
+ autoStart: true,
658
+ provides: ISanitizer,
659
+ requires: [ISettingRegistry],
660
+ activate: (
661
+ app: JupyterFrontEnd,
662
+ settings: ISettingRegistry
663
+ ): IRenderMime.ISanitizer => {
664
+ const sanitizer = new Sanitizer();
665
+ const loadSetting = (setting: ISettingRegistry.ISettings): void => {
666
+ const allowedSchemes = setting.get('allowedSchemes')
667
+ .composite as Array<string>;
668
+
669
+ const autolink = setting.get('autolink').composite as boolean;
670
+
671
+ if (allowedSchemes) {
672
+ sanitizer.setAllowedSchemes(allowedSchemes);
673
+ }
674
+
675
+ sanitizer.setAutolink(autolink);
676
+ };
677
+
678
+ // Wait for the application to be restored and
679
+ // for the settings for this plugin to be loaded
680
+ settings
681
+ .load('@jupyterlab/apputils-extension:sanitizer')
682
+ .then(setting => {
683
+ // Read the settings
684
+ loadSetting(setting);
685
+
686
+ // Listen for your plugin setting changes using Signal
687
+ setting.changed.connect(loadSetting);
688
+ })
689
+ .catch(reason => {
690
+ console.error(`Failed to load sanitizer settings:`, reason);
691
+ });
692
+
693
+ return sanitizer;
694
+ }
695
+ };
696
+
697
+ /**
698
+ * Export the plugins as default.
699
+ */
700
+ const plugins: JupyterFrontEndPlugin<any>[] = [
701
+ announcements,
702
+ kernelStatus,
703
+ notificationPlugin,
704
+ palette,
705
+ paletteRestorer,
706
+ print,
707
+ resolver,
708
+ runningSessionsStatus,
709
+ sanitizer,
710
+ settingsPlugin,
711
+ state,
712
+ splash,
713
+ sessionDialogs,
714
+ themesPlugin,
715
+ themesPaletteMenuPlugin,
716
+ toggleHeader,
717
+ toolbarRegistry,
718
+ utilityCommands,
719
+ workspacesPlugin
720
+ ];
721
+ export default plugins;