@jupyterlab/notebook-extension 4.7.0-alpha.1 → 4.7.0-alpha.2
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/index.js +101 -34
- package/lib/index.js.map +1 -1
- package/lib/nboutput.js +7 -2
- package/lib/nboutput.js.map +1 -1
- package/lib/tool-widgets/activeCellToolWidget.d.ts +58 -2
- package/lib/tool-widgets/activeCellToolWidget.js +236 -29
- package/lib/tool-widgets/activeCellToolWidget.js.map +1 -1
- package/lib/tool-widgets/metadataEditorFields.js +6 -0
- package/lib/tool-widgets/metadataEditorFields.js.map +1 -1
- package/package.json +32 -32
- package/schema/tools.json +8 -0
- package/src/index.ts +114 -33
- package/src/nboutput.ts +9 -2
- package/src/tool-widgets/activeCellToolWidget.tsx +326 -36
- package/src/tool-widgets/metadataEditorFields.tsx +6 -0
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module notebook-extension
|
|
7
7
|
*/
|
|
8
8
|
import { ILabShell, ILayoutRestorer, IRouter } from '@jupyterlab/application';
|
|
9
|
-
import { Clipboard, createToolbarFactory, Dialog, ICommandPalette, IKernelStatusModel, InputDialog, ISanitizer, ISessionContextDialogs, IToolbarWidgetRegistry, MainAreaWidget, Sanitizer, SemanticCommand, SessionContextDialogs, showDialog, Toolbar, WidgetTracker } from '@jupyterlab/apputils';
|
|
9
|
+
import { Clipboard, CommandLinker, createToolbarFactory, Dialog, ICommandPalette, IKernelStatusModel, InputDialog, ISanitizer, ISessionContextDialogs, IToolbarWidgetRegistry, MainAreaWidget, Sanitizer, SemanticCommand, SessionContextDialogs, showDialog, Toolbar, WidgetTracker } from '@jupyterlab/apputils';
|
|
10
10
|
import { MarkdownCell } from '@jupyterlab/cells';
|
|
11
11
|
import { IEditorServices, IPositionModel } from '@jupyterlab/codeeditor';
|
|
12
12
|
import { compareVersions, PageConfig } from '@jupyterlab/coreutils';
|
|
@@ -38,7 +38,7 @@ import { Panel } from '@lumino/widgets';
|
|
|
38
38
|
import { CellBarExtension } from '@jupyterlab/cell-toolbar';
|
|
39
39
|
import { cellExecutor } from './cellexecutor';
|
|
40
40
|
import { logNotebookOutput } from './nboutput';
|
|
41
|
-
import { ActiveCellTool } from './tool-widgets/activeCellToolWidget';
|
|
41
|
+
import { ActiveCellTool, CellIdField } from './tool-widgets/activeCellToolWidget';
|
|
42
42
|
import { CellMetadataField, NotebookMetadataField } from './tool-widgets/metadataEditorFields';
|
|
43
43
|
/**
|
|
44
44
|
* The command IDs used by the notebook plugin.
|
|
@@ -554,6 +554,9 @@ export const exportPlugin = {
|
|
|
554
554
|
.filter(key => !FORMAT_EXCLUDE.includes(key))
|
|
555
555
|
.map(key => {
|
|
556
556
|
const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
|
|
557
|
+
// Fallback for export formats the server offers but `formatLabels`
|
|
558
|
+
// does not know about, so there is no literal to extract.
|
|
559
|
+
// eslint-disable-next-line jupyter/no-dynamic-translation
|
|
557
560
|
const capCaseKey = trans.__(formattedKey);
|
|
558
561
|
const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
|
|
559
562
|
return {
|
|
@@ -864,6 +867,9 @@ const updateRawMimetype = {
|
|
|
864
867
|
const mimetypeExists = ((_a = properties.oneOf) === null || _a === void 0 ? void 0 : _a.filter(value => value.const === key).length) > 0;
|
|
865
868
|
if (!mimetypeExists) {
|
|
866
869
|
const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
|
|
870
|
+
// Fallback for export formats the server offers but `formatLabels`
|
|
871
|
+
// does not know about, so there is no literal to extract.
|
|
872
|
+
// eslint-disable-next-line jupyter/no-dynamic-translation
|
|
867
873
|
const altOption = trans.__(formattedKey);
|
|
868
874
|
const option = formatLabels[key] ? formatLabels[key] : altOption;
|
|
869
875
|
const mimeTypeValue = response[key].output_mimetype;
|
|
@@ -891,26 +897,32 @@ const customMetadataEditorFields = {
|
|
|
891
897
|
optional: [ITranslator],
|
|
892
898
|
activate: (app, tracker, editorServices, formRegistry, translator) => {
|
|
893
899
|
const editorFactory = options => editorServices.factoryService.newInlineEditor(options);
|
|
894
|
-
// Register the custom fields.
|
|
900
|
+
// Register the custom fields. As with the active cell tool below, the
|
|
901
|
+
// field renderers are used by rjsf as React components, so they run on
|
|
902
|
+
// every rebuild of the metadata form. Each field is created once and
|
|
903
|
+
// reused: constructing one per render abandons an editor, its host node
|
|
904
|
+
// and its listeners on every keystroke that rebuilds the form.
|
|
905
|
+
const cellMetadataField = new CellMetadataField({
|
|
906
|
+
editorFactory,
|
|
907
|
+
tracker,
|
|
908
|
+
label: 'Cell metadata',
|
|
909
|
+
translator: translator
|
|
910
|
+
});
|
|
895
911
|
const cellComponent = {
|
|
896
912
|
fieldRenderer: (props) => {
|
|
897
|
-
return
|
|
898
|
-
editorFactory,
|
|
899
|
-
tracker,
|
|
900
|
-
label: 'Cell metadata',
|
|
901
|
-
translator: translator
|
|
902
|
-
}).render(props);
|
|
913
|
+
return cellMetadataField.render(props);
|
|
903
914
|
}
|
|
904
915
|
};
|
|
905
916
|
formRegistry.addRenderer('@jupyterlab/notebook-extension:metadata-editor.cell-metadata', cellComponent);
|
|
917
|
+
const notebookMetadataField = new NotebookMetadataField({
|
|
918
|
+
editorFactory,
|
|
919
|
+
tracker,
|
|
920
|
+
label: 'Notebook metadata',
|
|
921
|
+
translator: translator
|
|
922
|
+
});
|
|
906
923
|
const notebookComponent = {
|
|
907
924
|
fieldRenderer: (props) => {
|
|
908
|
-
return
|
|
909
|
-
editorFactory,
|
|
910
|
-
tracker,
|
|
911
|
-
label: 'Notebook metadata',
|
|
912
|
-
translator: translator
|
|
913
|
-
}).render(props);
|
|
925
|
+
return notebookMetadataField.render(props);
|
|
914
926
|
}
|
|
915
927
|
};
|
|
916
928
|
formRegistry.addRenderer('@jupyterlab/notebook-extension:metadata-editor.notebook-metadata', notebookComponent);
|
|
@@ -921,21 +933,38 @@ const customMetadataEditorFields = {
|
|
|
921
933
|
*/
|
|
922
934
|
const activeCellTool = {
|
|
923
935
|
id: '@jupyterlab/notebook-extension:active-cell-tool',
|
|
924
|
-
description: 'Adds active cell
|
|
936
|
+
description: 'Adds active cell fields in the metadata editor tab.',
|
|
925
937
|
autoStart: true,
|
|
926
938
|
requires: [INotebookTracker, IFormRendererRegistry, IEditorLanguageRegistry],
|
|
939
|
+
optional: [ITranslator],
|
|
927
940
|
activate: (
|
|
928
941
|
// Register the custom field.
|
|
929
|
-
app, tracker, formRegistry, languages) => {
|
|
942
|
+
app, tracker, formRegistry, languages, translator) => {
|
|
943
|
+
// The field renderer is used by rjsf as a React component, so it runs on
|
|
944
|
+
// every rebuild of the metadata form. The tool is created once and reused:
|
|
945
|
+
// constructing one per render would rebuild the prompt and the preview from
|
|
946
|
+
// scratch (showing them empty until the next update) and would leave a
|
|
947
|
+
// connection to the cell model behind for every abandoned instance.
|
|
948
|
+
const tool = new ActiveCellTool({
|
|
949
|
+
tracker,
|
|
950
|
+
languages
|
|
951
|
+
});
|
|
930
952
|
const component = {
|
|
931
953
|
fieldRenderer: (props) => {
|
|
932
|
-
return
|
|
933
|
-
tracker,
|
|
934
|
-
languages
|
|
935
|
-
}).render(props);
|
|
954
|
+
return tool.render(props);
|
|
936
955
|
}
|
|
937
956
|
};
|
|
938
957
|
formRegistry.addRenderer('@jupyterlab/notebook-extension:active-cell-tool.renderer', component);
|
|
958
|
+
const cellIdComponent = {
|
|
959
|
+
fieldRenderer: (props) => {
|
|
960
|
+
return CellIdField({
|
|
961
|
+
...props,
|
|
962
|
+
tracker,
|
|
963
|
+
translator
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
formRegistry.addRenderer('@jupyterlab/notebook-extension:active-cell-tool.cell-id', cellIdComponent);
|
|
939
968
|
}
|
|
940
969
|
};
|
|
941
970
|
/**
|
|
@@ -1097,7 +1126,8 @@ function activatePageHandler(app, rendermime, settingRegistry, translator_) {
|
|
|
1097
1126
|
}
|
|
1098
1127
|
const mimeData = data;
|
|
1099
1128
|
const metadata = ((_a = payload['metadata']) !== null && _a !== void 0 ? _a : {});
|
|
1100
|
-
const
|
|
1129
|
+
const trusted = false;
|
|
1130
|
+
const mimeType = rendermime.preferredMimeType(mimeData, 'ensure');
|
|
1101
1131
|
if (!mimeType) {
|
|
1102
1132
|
return false;
|
|
1103
1133
|
}
|
|
@@ -1113,7 +1143,7 @@ function activatePageHandler(app, rendermime, settingRegistry, translator_) {
|
|
|
1113
1143
|
const content = helpWidget.content;
|
|
1114
1144
|
content.widgets.forEach(widget => widget.dispose());
|
|
1115
1145
|
const renderer = rendermime.createRenderer(mimeType);
|
|
1116
|
-
void renderer.renderModel(new MimeModel({ data: mimeData, metadata, trusted
|
|
1146
|
+
void renderer.renderModel(new MimeModel({ data: mimeData, metadata, trusted }));
|
|
1117
1147
|
content.addWidget(renderer);
|
|
1118
1148
|
if (!helpWidget.isAttached) {
|
|
1119
1149
|
app.shell.add(helpWidget, 'down');
|
|
@@ -1612,8 +1642,10 @@ function activateNotebookHandler(app, factory, extensions, executor, palette, de
|
|
|
1612
1642
|
value: settings.get('sideBySideOutputRatio').composite
|
|
1613
1643
|
})
|
|
1614
1644
|
.then(result => {
|
|
1615
|
-
|
|
1616
|
-
|
|
1645
|
+
if (result.value !== null &&
|
|
1646
|
+
Number.isFinite(result.value) &&
|
|
1647
|
+
result.value >= 0) {
|
|
1648
|
+
setSideBySideOutputRatio(result.value);
|
|
1617
1649
|
void settings.set('sideBySideOutputRatio', result.value);
|
|
1618
1650
|
}
|
|
1619
1651
|
})
|
|
@@ -1676,10 +1708,13 @@ function activateNotebookHandler(app, factory, extensions, executor, palette, de
|
|
|
1676
1708
|
widget.title.iconClass = (_a = ft === null || ft === void 0 ? void 0 : ft.iconClass) !== null && _a !== void 0 ? _a : '';
|
|
1677
1709
|
widget.title.iconLabel = (_b = ft === null || ft === void 0 ? void 0 : ft.iconLabel) !== null && _b !== void 0 ? _b : '';
|
|
1678
1710
|
widget.content.scrollbar = (_c = factory.notebookConfig.showMinimap) !== null && _c !== void 0 ? _c : false;
|
|
1679
|
-
// Notify the widget tracker if restore data needs to update.
|
|
1711
|
+
// Notify the widget tracker if restore data needs to update. The context
|
|
1712
|
+
// outlives this panel when other views of the document stay open, so the
|
|
1713
|
+
// connection is made with the panel as receiver: `Widget.dispose()` calls
|
|
1714
|
+
// `Signal.clearData(this)`, which removes it when the panel is closed.
|
|
1680
1715
|
widget.context.pathChanged.connect(() => {
|
|
1681
1716
|
void tracker.save(widget);
|
|
1682
|
-
});
|
|
1717
|
+
}, widget);
|
|
1683
1718
|
// Add the notebook panel to the tracker.
|
|
1684
1719
|
void tracker.add(widget);
|
|
1685
1720
|
});
|
|
@@ -1932,6 +1967,7 @@ function activateNotebookCompleterService(app, notebooks, manager, translator, a
|
|
|
1932
1967
|
keys: ['Enter'],
|
|
1933
1968
|
selector: '.jp-Notebook .jp-mod-completer-active'
|
|
1934
1969
|
});
|
|
1970
|
+
const completerConnected = new WeakSet();
|
|
1935
1971
|
const updateCompleter = async (_, notebook) => {
|
|
1936
1972
|
var _a, _b;
|
|
1937
1973
|
const completerContext = {
|
|
@@ -1941,6 +1977,15 @@ function activateNotebookCompleterService(app, notebooks, manager, translator, a
|
|
|
1941
1977
|
sanitizer: sanitizer
|
|
1942
1978
|
};
|
|
1943
1979
|
await manager.updateCompleter(completerContext);
|
|
1980
|
+
// `updateCompleter` also runs for every panel on `activeProvidersChanged`
|
|
1981
|
+
// below; connect the listeners only on the first call for a panel so
|
|
1982
|
+
// they do not accumulate per settings change. The disposal check covers
|
|
1983
|
+
// a panel closed while `updateCompleter` was pending: connections made
|
|
1984
|
+
// then would outlive the disposal cleanup that has already run.
|
|
1985
|
+
if (notebook.isDisposed || completerConnected.has(notebook)) {
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
completerConnected.add(notebook);
|
|
1944
1989
|
notebook.content.activeCellChanged.connect((_, cell) => {
|
|
1945
1990
|
// Ensure the editor will exist on the cell before adding the completer
|
|
1946
1991
|
cell === null || cell === void 0 ? void 0 : cell.ready.then(() => {
|
|
@@ -1953,6 +1998,10 @@ function activateNotebookCompleterService(app, notebooks, manager, translator, a
|
|
|
1953
1998
|
return manager.updateCompleter(newCompleterContext);
|
|
1954
1999
|
}).catch(console.error);
|
|
1955
2000
|
});
|
|
2001
|
+
// The session context outlives this panel when other views of the
|
|
2002
|
+
// document stay open, so the connection is made with the panel as
|
|
2003
|
+
// receiver: `Widget.dispose()` calls `Signal.clearData(this)`, which
|
|
2004
|
+
// removes it when the panel is closed.
|
|
1956
2005
|
notebook.sessionContext.sessionChanged.connect(() => {
|
|
1957
2006
|
var _a;
|
|
1958
2007
|
// Ensure the editor will exist on the cell before adding the completer
|
|
@@ -1965,7 +2014,7 @@ function activateNotebookCompleterService(app, notebooks, manager, translator, a
|
|
|
1965
2014
|
};
|
|
1966
2015
|
return manager.updateCompleter(newCompleterContext);
|
|
1967
2016
|
}).catch(console.error);
|
|
1968
|
-
});
|
|
2017
|
+
}, notebook);
|
|
1969
2018
|
};
|
|
1970
2019
|
notebooks.widgetAdded.connect(updateCompleter);
|
|
1971
2020
|
manager.activeProvidersChanged.connect(() => {
|
|
@@ -2073,17 +2122,26 @@ function addCommands(app, tracker, translator, sessionDialogs, settings, isEnabl
|
|
|
2073
2122
|
NotebookActions.paste(notebook, mode, { stripOutputs });
|
|
2074
2123
|
}
|
|
2075
2124
|
};
|
|
2076
|
-
// Set up signal handler to keep the collapse state consistent
|
|
2125
|
+
// Set up signal handler to keep the collapse state consistent. The
|
|
2126
|
+
// connections are made once per panel: `currentChanged` fires repeatedly
|
|
2127
|
+
// for the same panel, and reconnecting each time would pile up duplicate
|
|
2128
|
+
// handlers for as long as the panel lives.
|
|
2129
|
+
const collapseSynchronized = new WeakSet();
|
|
2077
2130
|
tracker.currentChanged.connect((sender, panel) => {
|
|
2078
2131
|
var _a, _b;
|
|
2079
|
-
if (!((_b = (_a = panel === null || panel === void 0 ? void 0 : panel.content) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.cells)) {
|
|
2132
|
+
if (!((_b = (_a = panel === null || panel === void 0 ? void 0 : panel.content) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.cells) || collapseSynchronized.has(panel)) {
|
|
2080
2133
|
return;
|
|
2081
2134
|
}
|
|
2135
|
+
collapseSynchronized.add(panel);
|
|
2136
|
+
// The cell list belongs to the model, which outlives this view when
|
|
2137
|
+
// other views of the document stay open, so the connection is made
|
|
2138
|
+
// with the notebook as receiver: `Widget.dispose()` calls
|
|
2139
|
+
// `Signal.clearData(this)`, which removes it when the view is closed.
|
|
2082
2140
|
panel.content.model.cells.changed.connect((list, args) => {
|
|
2083
2141
|
// Might be overkill to refresh this every time, but
|
|
2084
2142
|
// it helps to keep the collapse state consistent.
|
|
2085
2143
|
refreshCellCollapsed(panel.content);
|
|
2086
|
-
});
|
|
2144
|
+
}, panel.content);
|
|
2087
2145
|
panel.content.activeCellChanged.connect((notebook, cell) => {
|
|
2088
2146
|
NotebookActions.expandParent(cell, notebook);
|
|
2089
2147
|
});
|
|
@@ -2363,8 +2421,12 @@ function addCommands(app, tracker, translator, sessionDialogs, settings, isEnabl
|
|
|
2363
2421
|
commands.addCommand(CommandIDs.trust, {
|
|
2364
2422
|
label: () => trans.__('Trust Notebook'),
|
|
2365
2423
|
execute: async (args) => {
|
|
2366
|
-
|
|
2367
|
-
|
|
2424
|
+
var _a;
|
|
2425
|
+
const trustBoundaryId = args[CommandLinker.TRUST_BOUNDARY_ID_ARG];
|
|
2426
|
+
const current = typeof trustBoundaryId === 'string'
|
|
2427
|
+
? ((_a = tracker.find(widget => widget.content.node.id === trustBoundaryId)) !== null && _a !== void 0 ? _a : null)
|
|
2428
|
+
: getCurrent(tracker, shell, args);
|
|
2429
|
+
if (current && !current.isDisposed) {
|
|
2368
2430
|
const { context, content } = current;
|
|
2369
2431
|
const trustResult = await NotebookActions.trust(content);
|
|
2370
2432
|
if (trustResult.trusted) {
|
|
@@ -2378,7 +2440,12 @@ function addCommands(app, tracker, translator, sessionDialogs, settings, isEnabl
|
|
|
2378
2440
|
describedBy: {
|
|
2379
2441
|
args: {
|
|
2380
2442
|
type: 'object',
|
|
2381
|
-
properties: {
|
|
2443
|
+
properties: {
|
|
2444
|
+
[CommandLinker.TRUST_BOUNDARY_ID_ARG]: {
|
|
2445
|
+
type: 'string',
|
|
2446
|
+
description: trans.__('The notebook trust boundary ID')
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2382
2449
|
}
|
|
2383
2450
|
}
|
|
2384
2451
|
});
|