@jupyterlab/notebook-extension 4.7.0-alpha.0 → 4.7.0-alpha.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlab/notebook-extension",
3
- "version": "4.7.0-alpha.0",
3
+ "version": "4.7.0-alpha.1",
4
4
  "description": "JupyterLab - Notebook Extension",
5
5
  "homepage": "https://github.com/jupyterlab/jupyterlab",
6
6
  "bugs": {
@@ -37,44 +37,44 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@jupyter/ydoc": "^4.0.0",
40
- "@jupyterlab/application": "^4.7.0-alpha.0",
41
- "@jupyterlab/apputils": "^4.8.0-alpha.0",
42
- "@jupyterlab/cell-toolbar": "^4.7.0-alpha.0",
43
- "@jupyterlab/cells": "^4.7.0-alpha.0",
44
- "@jupyterlab/codeeditor": "^4.7.0-alpha.0",
45
- "@jupyterlab/codemirror": "^4.7.0-alpha.0",
46
- "@jupyterlab/completer": "^4.7.0-alpha.0",
47
- "@jupyterlab/coreutils": "^6.7.0-alpha.0",
48
- "@jupyterlab/docmanager": "^4.7.0-alpha.0",
49
- "@jupyterlab/docmanager-extension": "^4.7.0-alpha.0",
50
- "@jupyterlab/docregistry": "^4.7.0-alpha.0",
51
- "@jupyterlab/documentsearch": "^4.7.0-alpha.0",
52
- "@jupyterlab/filebrowser": "^4.7.0-alpha.0",
53
- "@jupyterlab/launcher": "^4.7.0-alpha.0",
54
- "@jupyterlab/logconsole": "^4.7.0-alpha.0",
55
- "@jupyterlab/lsp": "^4.7.0-alpha.0",
56
- "@jupyterlab/mainmenu": "^4.7.0-alpha.0",
57
- "@jupyterlab/metadataform": "^4.7.0-alpha.0",
58
- "@jupyterlab/nbformat": "^4.7.0-alpha.0",
59
- "@jupyterlab/notebook": "^4.7.0-alpha.0",
60
- "@jupyterlab/observables": "^5.7.0-alpha.0",
61
- "@jupyterlab/outputarea": "^4.7.0-alpha.0",
62
- "@jupyterlab/property-inspector": "^4.7.0-alpha.0",
63
- "@jupyterlab/rendermime": "^4.7.0-alpha.0",
64
- "@jupyterlab/services": "^7.7.0-alpha.0",
65
- "@jupyterlab/settingregistry": "^4.7.0-alpha.0",
66
- "@jupyterlab/statedb": "^4.7.0-alpha.0",
67
- "@jupyterlab/statusbar": "^4.7.0-alpha.0",
68
- "@jupyterlab/toc": "^6.7.0-alpha.0",
69
- "@jupyterlab/translation": "^4.7.0-alpha.0",
70
- "@jupyterlab/ui-components": "^4.7.0-alpha.0",
71
- "@lumino/algorithm": "^2.0.4",
72
- "@lumino/commands": "^2.3.3",
73
- "@lumino/coreutils": "^2.2.2",
74
- "@lumino/disposable": "^2.1.5",
75
- "@lumino/messaging": "^2.0.4",
76
- "@lumino/polling": "^2.1.5",
77
- "@lumino/widgets": "^2.8.0",
40
+ "@jupyterlab/application": "^4.7.0-alpha.1",
41
+ "@jupyterlab/apputils": "^4.8.0-alpha.1",
42
+ "@jupyterlab/cell-toolbar": "^4.7.0-alpha.1",
43
+ "@jupyterlab/cells": "^4.7.0-alpha.1",
44
+ "@jupyterlab/codeeditor": "^4.7.0-alpha.1",
45
+ "@jupyterlab/codemirror": "^4.7.0-alpha.1",
46
+ "@jupyterlab/completer": "^4.7.0-alpha.1",
47
+ "@jupyterlab/coreutils": "^6.7.0-alpha.1",
48
+ "@jupyterlab/docmanager": "^4.7.0-alpha.1",
49
+ "@jupyterlab/docmanager-extension": "^4.7.0-alpha.1",
50
+ "@jupyterlab/docregistry": "^4.7.0-alpha.1",
51
+ "@jupyterlab/documentsearch": "^4.7.0-alpha.1",
52
+ "@jupyterlab/filebrowser": "^4.7.0-alpha.1",
53
+ "@jupyterlab/launcher": "^4.7.0-alpha.1",
54
+ "@jupyterlab/logconsole": "^4.7.0-alpha.1",
55
+ "@jupyterlab/lsp": "^4.7.0-alpha.1",
56
+ "@jupyterlab/mainmenu": "^4.7.0-alpha.1",
57
+ "@jupyterlab/metadataform": "^4.7.0-alpha.1",
58
+ "@jupyterlab/nbformat": "^4.7.0-alpha.1",
59
+ "@jupyterlab/notebook": "^4.7.0-alpha.1",
60
+ "@jupyterlab/observables": "^5.7.0-alpha.1",
61
+ "@jupyterlab/outputarea": "^4.7.0-alpha.1",
62
+ "@jupyterlab/property-inspector": "^4.7.0-alpha.1",
63
+ "@jupyterlab/rendermime": "^4.7.0-alpha.1",
64
+ "@jupyterlab/services": "^7.7.0-alpha.1",
65
+ "@jupyterlab/settingregistry": "^4.7.0-alpha.1",
66
+ "@jupyterlab/statedb": "^4.7.0-alpha.1",
67
+ "@jupyterlab/statusbar": "^4.7.0-alpha.1",
68
+ "@jupyterlab/toc": "^6.7.0-alpha.1",
69
+ "@jupyterlab/translation": "^4.7.0-alpha.1",
70
+ "@jupyterlab/ui-components": "^4.7.0-alpha.1",
71
+ "@lumino/algorithm": "^2.0.5",
72
+ "@lumino/commands": "^2.3.4",
73
+ "@lumino/coreutils": "^2.2.3",
74
+ "@lumino/disposable": "^2.1.6",
75
+ "@lumino/messaging": "^2.0.5",
76
+ "@lumino/polling": "^2.1.6",
77
+ "@lumino/widgets": "^2.9.0",
78
78
  "@rjsf/utils": "^5.13.4",
79
79
  "react": "^18.2.0"
80
80
  },
@@ -841,12 +841,14 @@
841
841
  "title": "Side-by-side left margin override",
842
842
  "description": "Side-by-side left margin override.",
843
843
  "type": "string",
844
+ "pattern": "^-?(?:0|(?:[0-9]+|[0-9]*\\.[0-9]+)(?:px|em|rem|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc))$",
844
845
  "default": "10px"
845
846
  },
846
847
  "sideBySideRightMarginOverride": {
847
848
  "title": "Side-by-side right margin override",
848
849
  "description": "Side-by-side right margin override.",
849
850
  "type": "string",
851
+ "pattern": "^-?(?:0|(?:[0-9]+|[0-9]*\\.[0-9]+)(?:px|em|rem|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc))$",
850
852
  "default": "10px"
851
853
  },
852
854
  "sideBySideOutputRatio": {
package/src/index.ts CHANGED
@@ -135,7 +135,7 @@ import type {
135
135
  ReadonlyJSONValue,
136
136
  ReadonlyPartialJSONObject
137
137
  } from '@lumino/coreutils';
138
- import { JSONExt, UUID } from '@lumino/coreutils';
138
+ import { JSONExt, Token, UUID } from '@lumino/coreutils';
139
139
  import type { IDisposable } from '@lumino/disposable';
140
140
  import { DisposableSet } from '@lumino/disposable';
141
141
  import type { Message } from '@lumino/messaging';
@@ -189,6 +189,8 @@ namespace CommandIDs {
189
189
 
190
190
  export const exportToFormat = 'notebook:export-to-format';
191
191
 
192
+ export const showExportGuidance = 'notebook:show-export-guidance';
193
+
192
194
  export const run = 'notebook:run-cell';
193
195
 
194
196
  export const runAndAdvance = 'notebook:run-cell-and-select-next';
@@ -372,6 +374,30 @@ const FACTORY = 'Notebook';
372
374
  */
373
375
  const FORMAT_EXCLUDE = ['notebook', 'python', 'custom'];
374
376
 
377
+ /**
378
+ * Documentation page describing notebook export support.
379
+ */
380
+ const NOTEBOOK_EXPORT_DOCS_URL =
381
+ 'https://jupyterlab.readthedocs.io/en/stable/user/export.html';
382
+
383
+ /**
384
+ * An interface describing how export guidance is presented to users.
385
+ */
386
+ export interface INotebookExportGuidance {
387
+ /**
388
+ * Show guidance for enabling notebook exports.
389
+ */
390
+ showExportHelp(): Promise<void>;
391
+ }
392
+
393
+ /**
394
+ * A token describing a service for showing notebook export guidance.
395
+ */
396
+ export const INotebookExportGuidance = new Token<INotebookExportGuidance>(
397
+ '@jupyterlab/notebook-extension:INotebookExportGuidance',
398
+ 'A service for showing notebook export guidance.'
399
+ );
400
+
375
401
  /**
376
402
  * Setting Id storing the customized toolbar definition.
377
403
  */
@@ -648,13 +674,14 @@ export const exportPlugin: JupyterFrontEndPlugin<void> = {
648
674
  description: 'Adds the export notebook commands.',
649
675
  autoStart: true,
650
676
  requires: [ITranslator, INotebookTracker],
651
- optional: [IMainMenu, ICommandPalette],
677
+ optional: [IMainMenu, ICommandPalette, INotebookExportGuidance],
652
678
  activate: (
653
679
  app: JupyterFrontEnd,
654
680
  translator: ITranslator,
655
681
  tracker: INotebookTracker,
656
682
  mainMenu: IMainMenu | null,
657
- palette: ICommandPalette | null
683
+ palette: ICommandPalette | null,
684
+ exportGuidance: INotebookExportGuidance | null
658
685
  ) => {
659
686
  const trans = translator.load('jupyterlab');
660
687
  const { commands, shell } = app;
@@ -745,21 +772,91 @@ export const exportPlugin: JupyterFrontEndPlugin<void> = {
745
772
  }
746
773
  });
747
774
 
775
+ commands.addCommand(CommandIDs.showExportGuidance, {
776
+ label: trans.__('Enable notebook exports'),
777
+ execute: () => {
778
+ if (exportGuidance) {
779
+ return exportGuidance.showExportHelp();
780
+ }
781
+ window.open(NOTEBOOK_EXPORT_DOCS_URL, '_blank', 'noopener,noreferrer');
782
+ return undefined;
783
+ },
784
+ describedBy: {
785
+ args: {
786
+ type: 'object',
787
+ properties: {}
788
+ }
789
+ }
790
+ });
791
+
748
792
  // Add a notebook group to the File menu.
749
- let exportTo: Menu | null | undefined;
750
- if (mainMenu) {
751
- exportTo = mainMenu.fileMenu.items.find(
752
- item =>
753
- item.type === 'submenu' &&
754
- item.submenu?.id === 'jp-mainmenu-file-notebookexport'
755
- )?.submenu;
756
- }
793
+ const fileMenu = mainMenu?.fileMenu as Menu | undefined;
794
+
795
+ const getExportMenu = (): Menu | undefined => {
796
+ return (
797
+ fileMenu?.items.find(
798
+ item =>
799
+ item.type === 'submenu' &&
800
+ item.submenu?.id === 'jp-mainmenu-file-notebookexport'
801
+ )?.submenu ?? undefined
802
+ );
803
+ };
757
804
 
758
805
  let formatsInitialized = false;
806
+ let paletteInitialized = false;
807
+ let exportFormats: Array<{ format: string; label: string }> | null = null;
808
+
809
+ const updateExportMenu = () => {
810
+ const exportTo = getExportMenu();
811
+ if (!exportTo || !exportFormats) {
812
+ return;
813
+ }
814
+
815
+ exportTo.clearItems();
816
+
817
+ if (exportFormats.length === 0) {
818
+ exportTo.addItem({
819
+ command: CommandIDs.showExportGuidance
820
+ });
821
+ return;
822
+ }
823
+
824
+ exportFormats.forEach(({ format, label }) => {
825
+ exportTo.addItem({
826
+ command: CommandIDs.exportToFormat,
827
+ args: {
828
+ format,
829
+ label,
830
+ isPalette: false
831
+ }
832
+ });
833
+ });
834
+
835
+ if (palette && !paletteInitialized) {
836
+ const category = trans.__('Notebook Operations');
837
+ exportFormats.forEach(({ format, label }) => {
838
+ palette.addItem({
839
+ command: CommandIDs.exportToFormat,
840
+ category,
841
+ args: {
842
+ format,
843
+ label,
844
+ isPalette: true
845
+ }
846
+ });
847
+ });
848
+ paletteInitialized = true;
849
+ }
850
+ };
759
851
 
760
852
  /** Request formats only when a notebook might use them. */
761
853
  const maybeInitializeFormats = async () => {
762
854
  if (formatsInitialized) {
855
+ updateExportMenu();
856
+ return;
857
+ }
858
+
859
+ if (tracker.size === 0) {
763
860
  return;
764
861
  }
765
862
 
@@ -767,53 +864,96 @@ export const exportPlugin: JupyterFrontEndPlugin<void> = {
767
864
 
768
865
  formatsInitialized = true;
769
866
 
770
- const response = await services.nbconvert.getExportFormats(false);
771
-
772
- if (!response) {
773
- return;
867
+ let response: NbConvert.IExportFormats | null = null;
868
+ try {
869
+ response = await services.nbconvert.getExportFormats(false);
870
+ } catch {
871
+ // Ignore fetch errors and fallback to export guidance.
774
872
  }
775
873
 
776
- const formatLabels: any = Private.getFormatLabels(translator);
874
+ const formatLabels = Private.getFormatLabels(translator);
777
875
 
778
- // Convert export list to palette and menu items.
779
- const formatList = Object.keys(response);
780
- formatList.forEach(function (key) {
781
- const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
782
- const capCaseKey = trans.__(formattedKey);
783
- const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
784
- let args = {
785
- format: key,
786
- label: labelStr,
787
- isPalette: false
788
- };
789
- if (FORMAT_EXCLUDE.indexOf(key) === -1) {
790
- if (exportTo) {
791
- exportTo.addItem({
792
- command: CommandIDs.exportToFormat,
793
- args: args
794
- });
795
- }
796
- if (palette) {
797
- args = {
798
- format: key,
799
- label: labelStr,
800
- isPalette: true
801
- };
802
- const category = trans.__('Notebook Operations');
803
- palette.addItem({
804
- command: CommandIDs.exportToFormat,
805
- category,
806
- args
807
- });
808
- }
809
- }
810
- });
876
+ // Convert export list to menu and palette items.
877
+ exportFormats = Object.keys(response ?? {})
878
+ .filter(key => !FORMAT_EXCLUDE.includes(key))
879
+ .map(key => {
880
+ const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
881
+ const capCaseKey = trans.__(formattedKey);
882
+ const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
883
+ return {
884
+ format: key,
885
+ label: labelStr
886
+ };
887
+ });
888
+
889
+ updateExportMenu();
811
890
  };
812
891
 
813
892
  tracker.widgetAdded.connect(maybeInitializeFormats);
893
+ if (tracker.size > 0) {
894
+ void maybeInitializeFormats();
895
+ }
896
+ void app.restored.then(() => {
897
+ void maybeInitializeFormats();
898
+ });
814
899
  }
815
900
  };
816
901
 
902
+ /**
903
+ * A plugin providing the default notebook export guidance service.
904
+ */
905
+ export const exportGuidanceDialogPlugin: JupyterFrontEndPlugin<INotebookExportGuidance> =
906
+ {
907
+ id: '@jupyterlab/notebook-extension:export-guidance-dialog',
908
+ description:
909
+ 'Provides the default notebook export guidance shown when no exporters are available.',
910
+ provides: INotebookExportGuidance,
911
+ autoStart: true,
912
+ requires: [ITranslator],
913
+ activate: (
914
+ app: JupyterFrontEnd,
915
+ translator: ITranslator
916
+ ): INotebookExportGuidance => {
917
+ const trans = translator.load('jupyterlab');
918
+ const { commands } = app;
919
+
920
+ const openExportDocs = (url: string, docsLabel: string) => {
921
+ if (commands.hasCommand('help:open')) {
922
+ return commands.execute('help:open', {
923
+ url,
924
+ text: docsLabel,
925
+ newBrowserTab: true
926
+ });
927
+ }
928
+
929
+ window.open(url, '_blank', 'noopener,noreferrer');
930
+ return undefined;
931
+ };
932
+
933
+ return {
934
+ showExportHelp: async () => {
935
+ const result = await showDialog({
936
+ title: trans.__('Notebook exports are unavailable'),
937
+ body: trans.__(
938
+ 'No notebook export formats are currently available. To enable exports, install nbconvert in the server environment or use another exporter supported by your deployment.'
939
+ ),
940
+ buttons: [
941
+ Dialog.cancelButton({ label: trans.__('Close') }),
942
+ Dialog.okButton({ label: trans.__('Open Documentation') })
943
+ ]
944
+ });
945
+
946
+ if (result.button.accept) {
947
+ void openExportDocs(
948
+ NOTEBOOK_EXPORT_DOCS_URL,
949
+ trans.__('Notebook Export Documentation')
950
+ );
951
+ }
952
+ }
953
+ };
954
+ }
955
+ };
956
+
817
957
  /**
818
958
  * A plugin that adds a notebook trust status item to the status bar.
819
959
  */
@@ -1315,6 +1455,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
1315
1455
  trackerPlugin,
1316
1456
  pageHandlerPlugin,
1317
1457
  executionIndicator,
1458
+ exportGuidanceDialogPlugin,
1318
1459
  exportPlugin,
1319
1460
  tools,
1320
1461
  cellCounterItem,
@@ -2306,15 +2447,16 @@ function activateNotebookHandler(
2306
2447
  setSideBySideOutputRatio(factory.notebookConfig.sideBySideOutputRatio);
2307
2448
  const sideBySideMarginStyle = `.jp-mod-sideBySide.jp-Notebook .jp-Notebook-cell {
2308
2449
  margin-left: ${factory.notebookConfig.sideBySideLeftMarginOverride} !important;
2309
- margin-right: ${factory.notebookConfig.sideBySideRightMarginOverride} !important;`;
2450
+ margin-right: ${factory.notebookConfig.sideBySideRightMarginOverride} !important;
2451
+ }`;
2310
2452
  const sideBySideMarginTag = document.getElementById(SIDE_BY_SIDE_STYLE_ID);
2311
2453
  if (sideBySideMarginTag) {
2312
- sideBySideMarginTag.innerText = sideBySideMarginStyle;
2454
+ sideBySideMarginTag.textContent = sideBySideMarginStyle;
2313
2455
  } else {
2314
- document.head.insertAdjacentHTML(
2315
- 'beforeend',
2316
- `<style id="${SIDE_BY_SIDE_STYLE_ID}">${sideBySideMarginStyle}}</style>`
2317
- );
2456
+ const style = document.createElement('style');
2457
+ style.id = SIDE_BY_SIDE_STYLE_ID;
2458
+ style.textContent = sideBySideMarginStyle;
2459
+ document.head.appendChild(style);
2318
2460
  }
2319
2461
  factory.autoStartDefault = settings.get('autoStartDefaultKernel')
2320
2462
  .composite as boolean;