@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/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ILabShell, ILayoutRestorer, IRouter } from '@jupyterlab/application';
|
|
|
16
16
|
import type { ISessionContext } from '@jupyterlab/apputils';
|
|
17
17
|
import {
|
|
18
18
|
Clipboard,
|
|
19
|
+
CommandLinker,
|
|
19
20
|
createToolbarFactory,
|
|
20
21
|
Dialog,
|
|
21
22
|
ICommandPalette,
|
|
@@ -145,7 +146,10 @@ import { Panel } from '@lumino/widgets';
|
|
|
145
146
|
import { CellBarExtension } from '@jupyterlab/cell-toolbar';
|
|
146
147
|
import { cellExecutor } from './cellexecutor';
|
|
147
148
|
import { logNotebookOutput } from './nboutput';
|
|
148
|
-
import {
|
|
149
|
+
import {
|
|
150
|
+
ActiveCellTool,
|
|
151
|
+
CellIdField
|
|
152
|
+
} from './tool-widgets/activeCellToolWidget';
|
|
149
153
|
import {
|
|
150
154
|
CellMetadataField,
|
|
151
155
|
NotebookMetadataField
|
|
@@ -878,6 +882,9 @@ export const exportPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
878
882
|
.filter(key => !FORMAT_EXCLUDE.includes(key))
|
|
879
883
|
.map(key => {
|
|
880
884
|
const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
|
|
885
|
+
// Fallback for export formats the server offers but `formatLabels`
|
|
886
|
+
// does not know about, so there is no literal to extract.
|
|
887
|
+
// eslint-disable-next-line jupyter/no-dynamic-translation
|
|
881
888
|
const capCaseKey = trans.__(formattedKey);
|
|
882
889
|
const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
|
|
883
890
|
return {
|
|
@@ -1279,6 +1286,9 @@ const updateRawMimetype: JupyterFrontEndPlugin<void> = {
|
|
|
1279
1286
|
).length > 0;
|
|
1280
1287
|
if (!mimetypeExists) {
|
|
1281
1288
|
const formattedKey = key[0].toLocaleUpperCase() + key.slice(1);
|
|
1289
|
+
// Fallback for export formats the server offers but `formatLabels`
|
|
1290
|
+
// does not know about, so there is no literal to extract.
|
|
1291
|
+
// eslint-disable-next-line jupyter/no-dynamic-translation
|
|
1282
1292
|
const altOption = trans.__(formattedKey);
|
|
1283
1293
|
const option = formatLabels[key] ? formatLabels[key] : altOption;
|
|
1284
1294
|
const mimeTypeValue = response[key].output_mimetype;
|
|
@@ -1316,15 +1326,20 @@ const customMetadataEditorFields: JupyterFrontEndPlugin<void> = {
|
|
|
1316
1326
|
) => {
|
|
1317
1327
|
const editorFactory: CodeEditor.Factory = options =>
|
|
1318
1328
|
editorServices.factoryService.newInlineEditor(options);
|
|
1319
|
-
// Register the custom fields.
|
|
1329
|
+
// Register the custom fields. As with the active cell tool below, the
|
|
1330
|
+
// field renderers are used by rjsf as React components, so they run on
|
|
1331
|
+
// every rebuild of the metadata form. Each field is created once and
|
|
1332
|
+
// reused: constructing one per render abandons an editor, its host node
|
|
1333
|
+
// and its listeners on every keystroke that rebuilds the form.
|
|
1334
|
+
const cellMetadataField = new CellMetadataField({
|
|
1335
|
+
editorFactory,
|
|
1336
|
+
tracker,
|
|
1337
|
+
label: 'Cell metadata',
|
|
1338
|
+
translator: translator
|
|
1339
|
+
});
|
|
1320
1340
|
const cellComponent: IFormRenderer = {
|
|
1321
1341
|
fieldRenderer: (props: FieldProps) => {
|
|
1322
|
-
return
|
|
1323
|
-
editorFactory,
|
|
1324
|
-
tracker,
|
|
1325
|
-
label: 'Cell metadata',
|
|
1326
|
-
translator: translator
|
|
1327
|
-
}).render(props);
|
|
1342
|
+
return cellMetadataField.render(props);
|
|
1328
1343
|
}
|
|
1329
1344
|
};
|
|
1330
1345
|
formRegistry.addRenderer(
|
|
@@ -1332,14 +1347,15 @@ const customMetadataEditorFields: JupyterFrontEndPlugin<void> = {
|
|
|
1332
1347
|
cellComponent
|
|
1333
1348
|
);
|
|
1334
1349
|
|
|
1350
|
+
const notebookMetadataField = new NotebookMetadataField({
|
|
1351
|
+
editorFactory,
|
|
1352
|
+
tracker,
|
|
1353
|
+
label: 'Notebook metadata',
|
|
1354
|
+
translator: translator
|
|
1355
|
+
});
|
|
1335
1356
|
const notebookComponent: IFormRenderer = {
|
|
1336
1357
|
fieldRenderer: (props: FieldProps) => {
|
|
1337
|
-
return
|
|
1338
|
-
editorFactory,
|
|
1339
|
-
tracker,
|
|
1340
|
-
label: 'Notebook metadata',
|
|
1341
|
-
translator: translator
|
|
1342
|
-
}).render(props);
|
|
1358
|
+
return notebookMetadataField.render(props);
|
|
1343
1359
|
}
|
|
1344
1360
|
};
|
|
1345
1361
|
formRegistry.addRenderer(
|
|
@@ -1354,28 +1370,50 @@ const customMetadataEditorFields: JupyterFrontEndPlugin<void> = {
|
|
|
1354
1370
|
*/
|
|
1355
1371
|
const activeCellTool: JupyterFrontEndPlugin<void> = {
|
|
1356
1372
|
id: '@jupyterlab/notebook-extension:active-cell-tool',
|
|
1357
|
-
description: 'Adds active cell
|
|
1373
|
+
description: 'Adds active cell fields in the metadata editor tab.',
|
|
1358
1374
|
autoStart: true,
|
|
1359
1375
|
requires: [INotebookTracker, IFormRendererRegistry, IEditorLanguageRegistry],
|
|
1376
|
+
optional: [ITranslator],
|
|
1360
1377
|
activate: (
|
|
1361
1378
|
// Register the custom field.
|
|
1362
1379
|
app: JupyterFrontEnd,
|
|
1363
1380
|
tracker: INotebookTracker,
|
|
1364
1381
|
formRegistry: IFormRendererRegistry,
|
|
1365
|
-
languages: IEditorLanguageRegistry
|
|
1382
|
+
languages: IEditorLanguageRegistry,
|
|
1383
|
+
translator?: ITranslator
|
|
1366
1384
|
) => {
|
|
1385
|
+
// The field renderer is used by rjsf as a React component, so it runs on
|
|
1386
|
+
// every rebuild of the metadata form. The tool is created once and reused:
|
|
1387
|
+
// constructing one per render would rebuild the prompt and the preview from
|
|
1388
|
+
// scratch (showing them empty until the next update) and would leave a
|
|
1389
|
+
// connection to the cell model behind for every abandoned instance.
|
|
1390
|
+
const tool = new ActiveCellTool({
|
|
1391
|
+
tracker,
|
|
1392
|
+
languages
|
|
1393
|
+
});
|
|
1367
1394
|
const component: IFormRenderer = {
|
|
1368
1395
|
fieldRenderer: (props: FieldProps) => {
|
|
1369
|
-
return
|
|
1370
|
-
tracker,
|
|
1371
|
-
languages
|
|
1372
|
-
}).render(props);
|
|
1396
|
+
return tool.render(props);
|
|
1373
1397
|
}
|
|
1374
1398
|
};
|
|
1375
1399
|
formRegistry.addRenderer(
|
|
1376
1400
|
'@jupyterlab/notebook-extension:active-cell-tool.renderer',
|
|
1377
1401
|
component
|
|
1378
1402
|
);
|
|
1403
|
+
|
|
1404
|
+
const cellIdComponent: IFormRenderer = {
|
|
1405
|
+
fieldRenderer: (props: FieldProps) => {
|
|
1406
|
+
return CellIdField({
|
|
1407
|
+
...props,
|
|
1408
|
+
tracker,
|
|
1409
|
+
translator
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
formRegistry.addRenderer(
|
|
1414
|
+
'@jupyterlab/notebook-extension:active-cell-tool.cell-id',
|
|
1415
|
+
cellIdComponent
|
|
1416
|
+
);
|
|
1379
1417
|
}
|
|
1380
1418
|
};
|
|
1381
1419
|
|
|
@@ -1577,7 +1615,8 @@ function activatePageHandler(
|
|
|
1577
1615
|
|
|
1578
1616
|
const mimeData = data as nbformat.IMimeBundle;
|
|
1579
1617
|
const metadata = (payload['metadata'] ?? {}) as ReadonlyJSONObject;
|
|
1580
|
-
const
|
|
1618
|
+
const trusted = false;
|
|
1619
|
+
const mimeType = rendermime.preferredMimeType(mimeData, 'ensure');
|
|
1581
1620
|
if (!mimeType) {
|
|
1582
1621
|
return false;
|
|
1583
1622
|
}
|
|
@@ -1597,7 +1636,7 @@ function activatePageHandler(
|
|
|
1597
1636
|
|
|
1598
1637
|
const renderer = rendermime.createRenderer(mimeType);
|
|
1599
1638
|
void renderer.renderModel(
|
|
1600
|
-
new MimeModel({ data: mimeData, metadata, trusted
|
|
1639
|
+
new MimeModel({ data: mimeData, metadata, trusted })
|
|
1601
1640
|
);
|
|
1602
1641
|
content.addWidget(renderer);
|
|
1603
1642
|
|
|
@@ -2273,8 +2312,12 @@ function activateNotebookHandler(
|
|
|
2273
2312
|
value: settings.get('sideBySideOutputRatio').composite as number
|
|
2274
2313
|
})
|
|
2275
2314
|
.then(result => {
|
|
2276
|
-
|
|
2277
|
-
|
|
2315
|
+
if (
|
|
2316
|
+
result.value !== null &&
|
|
2317
|
+
Number.isFinite(result.value) &&
|
|
2318
|
+
result.value >= 0
|
|
2319
|
+
) {
|
|
2320
|
+
setSideBySideOutputRatio(result.value);
|
|
2278
2321
|
void settings.set('sideBySideOutputRatio', result.value);
|
|
2279
2322
|
}
|
|
2280
2323
|
})
|
|
@@ -2365,10 +2408,13 @@ function activateNotebookHandler(
|
|
|
2365
2408
|
widget.title.iconLabel = ft?.iconLabel ?? '';
|
|
2366
2409
|
widget.content.scrollbar = factory.notebookConfig.showMinimap ?? false;
|
|
2367
2410
|
|
|
2368
|
-
// Notify the widget tracker if restore data needs to update.
|
|
2411
|
+
// Notify the widget tracker if restore data needs to update. The context
|
|
2412
|
+
// outlives this panel when other views of the document stay open, so the
|
|
2413
|
+
// connection is made with the panel as receiver: `Widget.dispose()` calls
|
|
2414
|
+
// `Signal.clearData(this)`, which removes it when the panel is closed.
|
|
2369
2415
|
widget.context.pathChanged.connect(() => {
|
|
2370
2416
|
void tracker.save(widget);
|
|
2371
|
-
});
|
|
2417
|
+
}, widget);
|
|
2372
2418
|
// Add the notebook panel to the tracker.
|
|
2373
2419
|
void tracker.add(widget);
|
|
2374
2420
|
});
|
|
@@ -2674,6 +2720,7 @@ function activateNotebookCompleterService(
|
|
|
2674
2720
|
keys: ['Enter'],
|
|
2675
2721
|
selector: '.jp-Notebook .jp-mod-completer-active'
|
|
2676
2722
|
});
|
|
2723
|
+
const completerConnected = new WeakSet<NotebookPanel>();
|
|
2677
2724
|
const updateCompleter = async (
|
|
2678
2725
|
_: INotebookTracker | undefined,
|
|
2679
2726
|
notebook: NotebookPanel
|
|
@@ -2685,6 +2732,15 @@ function activateNotebookCompleterService(
|
|
|
2685
2732
|
sanitizer: sanitizer
|
|
2686
2733
|
};
|
|
2687
2734
|
await manager.updateCompleter(completerContext);
|
|
2735
|
+
// `updateCompleter` also runs for every panel on `activeProvidersChanged`
|
|
2736
|
+
// below; connect the listeners only on the first call for a panel so
|
|
2737
|
+
// they do not accumulate per settings change. The disposal check covers
|
|
2738
|
+
// a panel closed while `updateCompleter` was pending: connections made
|
|
2739
|
+
// then would outlive the disposal cleanup that has already run.
|
|
2740
|
+
if (notebook.isDisposed || completerConnected.has(notebook)) {
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
completerConnected.add(notebook);
|
|
2688
2744
|
notebook.content.activeCellChanged.connect((_, cell) => {
|
|
2689
2745
|
// Ensure the editor will exist on the cell before adding the completer
|
|
2690
2746
|
cell?.ready
|
|
@@ -2699,6 +2755,10 @@ function activateNotebookCompleterService(
|
|
|
2699
2755
|
})
|
|
2700
2756
|
.catch(console.error);
|
|
2701
2757
|
});
|
|
2758
|
+
// The session context outlives this panel when other views of the
|
|
2759
|
+
// document stay open, so the connection is made with the panel as
|
|
2760
|
+
// receiver: `Widget.dispose()` calls `Signal.clearData(this)`, which
|
|
2761
|
+
// removes it when the panel is closed.
|
|
2702
2762
|
notebook.sessionContext.sessionChanged.connect(() => {
|
|
2703
2763
|
// Ensure the editor will exist on the cell before adding the completer
|
|
2704
2764
|
notebook.content.activeCell?.ready
|
|
@@ -2711,7 +2771,7 @@ function activateNotebookCompleterService(
|
|
|
2711
2771
|
return manager.updateCompleter(newCompleterContext);
|
|
2712
2772
|
})
|
|
2713
2773
|
.catch(console.error);
|
|
2714
|
-
});
|
|
2774
|
+
}, notebook);
|
|
2715
2775
|
};
|
|
2716
2776
|
notebooks.widgetAdded.connect(updateCompleter);
|
|
2717
2777
|
manager.activeProvidersChanged.connect(() => {
|
|
@@ -2851,18 +2911,28 @@ function addCommands(
|
|
|
2851
2911
|
}
|
|
2852
2912
|
};
|
|
2853
2913
|
|
|
2854
|
-
// Set up signal handler to keep the collapse state consistent
|
|
2914
|
+
// Set up signal handler to keep the collapse state consistent. The
|
|
2915
|
+
// connections are made once per panel: `currentChanged` fires repeatedly
|
|
2916
|
+
// for the same panel, and reconnecting each time would pile up duplicate
|
|
2917
|
+
// handlers for as long as the panel lives.
|
|
2918
|
+
const collapseSynchronized = new WeakSet<NotebookPanel>();
|
|
2855
2919
|
tracker.currentChanged.connect(
|
|
2856
2920
|
(sender: INotebookTracker, panel: NotebookPanel) => {
|
|
2857
|
-
if (!panel?.content?.model?.cells) {
|
|
2921
|
+
if (!panel?.content?.model?.cells || collapseSynchronized.has(panel)) {
|
|
2858
2922
|
return;
|
|
2859
2923
|
}
|
|
2924
|
+
collapseSynchronized.add(panel);
|
|
2925
|
+
// The cell list belongs to the model, which outlives this view when
|
|
2926
|
+
// other views of the document stay open, so the connection is made
|
|
2927
|
+
// with the notebook as receiver: `Widget.dispose()` calls
|
|
2928
|
+
// `Signal.clearData(this)`, which removes it when the view is closed.
|
|
2860
2929
|
panel.content.model.cells.changed.connect(
|
|
2861
2930
|
(list: any, args: IObservableList.IChangedArgs<ICellModel>) => {
|
|
2862
2931
|
// Might be overkill to refresh this every time, but
|
|
2863
2932
|
// it helps to keep the collapse state consistent.
|
|
2864
2933
|
refreshCellCollapsed(panel.content);
|
|
2865
|
-
}
|
|
2934
|
+
},
|
|
2935
|
+
panel.content
|
|
2866
2936
|
);
|
|
2867
2937
|
panel.content.activeCellChanged.connect(
|
|
2868
2938
|
(notebook: Notebook, cell: Cell) => {
|
|
@@ -3219,8 +3289,14 @@ function addCommands(
|
|
|
3219
3289
|
commands.addCommand(CommandIDs.trust, {
|
|
3220
3290
|
label: () => trans.__('Trust Notebook'),
|
|
3221
3291
|
execute: async args => {
|
|
3222
|
-
const
|
|
3223
|
-
|
|
3292
|
+
const trustBoundaryId = args[CommandLinker.TRUST_BOUNDARY_ID_ARG];
|
|
3293
|
+
const current =
|
|
3294
|
+
typeof trustBoundaryId === 'string'
|
|
3295
|
+
? (tracker.find(
|
|
3296
|
+
widget => widget.content.node.id === trustBoundaryId
|
|
3297
|
+
) ?? null)
|
|
3298
|
+
: getCurrent(tracker, shell, args);
|
|
3299
|
+
if (current && !current.isDisposed) {
|
|
3224
3300
|
const { context, content } = current;
|
|
3225
3301
|
const trustResult = await NotebookActions.trust(content);
|
|
3226
3302
|
if (trustResult.trusted) {
|
|
@@ -3234,7 +3310,12 @@ function addCommands(
|
|
|
3234
3310
|
describedBy: {
|
|
3235
3311
|
args: {
|
|
3236
3312
|
type: 'object',
|
|
3237
|
-
properties: {
|
|
3313
|
+
properties: {
|
|
3314
|
+
[CommandLinker.TRUST_BOUNDARY_ID_ARG]: {
|
|
3315
|
+
type: 'string',
|
|
3316
|
+
description: trans.__('The notebook trust boundary ID')
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3238
3319
|
}
|
|
3239
3320
|
}
|
|
3240
3321
|
});
|
package/src/nboutput.ts
CHANGED
|
@@ -65,12 +65,19 @@ function activateNBOutput(
|
|
|
65
65
|
// There is overlap here since unhandled messages are also emitted in the
|
|
66
66
|
// iopubMessage signal. However, unhandled messages warrant a higher log
|
|
67
67
|
// severity, so we'll accept that they are logged twice.
|
|
68
|
+
//
|
|
69
|
+
// The session context outlives this panel when other views of the
|
|
70
|
+
// document stay open, so the connections are made with the panel as
|
|
71
|
+
// receiver: `Widget.dispose()` calls `Signal.clearData(this)`, which
|
|
72
|
+
// removes them when the panel is closed.
|
|
68
73
|
nb.context.sessionContext.iopubMessage.connect(
|
|
69
|
-
(_, msg: KernelMessage.IIOPubMessage) => logOutput(msg, 'info', 'info')
|
|
74
|
+
(_, msg: KernelMessage.IIOPubMessage) => logOutput(msg, 'info', 'info'),
|
|
75
|
+
nb
|
|
70
76
|
);
|
|
71
77
|
nb.context.sessionContext.unhandledMessage.connect(
|
|
72
78
|
(_, msg: KernelMessage.IIOPubMessage) =>
|
|
73
|
-
logOutput(msg, 'warning', 'error')
|
|
79
|
+
logOutput(msg, 'warning', 'error'),
|
|
80
|
+
nb
|
|
74
81
|
);
|
|
75
82
|
}
|
|
76
83
|
nbtracker.forEach(nb => registerNB(nb));
|