@jupyterlab/notebook 4.6.0-alpha.3 → 4.6.0-alpha.5
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/actions.d.ts +9 -3
- package/lib/actions.js +65 -21
- package/lib/actions.js.map +1 -1
- package/lib/cellcounterstatus.d.ts +78 -0
- package/lib/cellcounterstatus.js +281 -0
- package/lib/cellcounterstatus.js.map +1 -0
- package/lib/default-toolbar.js +1 -1
- package/lib/default-toolbar.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/model.js +1 -0
- package/lib/model.js.map +1 -1
- package/lib/widget.d.ts +3 -0
- package/lib/widget.js +82 -11
- package/lib/widget.js.map +1 -1
- package/package.json +20 -20
- package/src/actions.tsx +90 -29
- package/src/cellcounterstatus.tsx +523 -0
- package/src/default-toolbar.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/model.ts +1 -0
- package/src/widget.ts +90 -11
- package/style/base.css +9 -80
- package/style/executionindicator.css +0 -6
package/src/actions.tsx
CHANGED
|
@@ -11,12 +11,7 @@ import {
|
|
|
11
11
|
showDialog,
|
|
12
12
|
SystemClipboard
|
|
13
13
|
} from '@jupyterlab/apputils';
|
|
14
|
-
import type {
|
|
15
|
-
Cell,
|
|
16
|
-
CodeCell,
|
|
17
|
-
ICellModel,
|
|
18
|
-
ICodeCellModel
|
|
19
|
-
} from '@jupyterlab/cells';
|
|
14
|
+
import type { Cell, CodeCell, ICodeCellModel } from '@jupyterlab/cells';
|
|
20
15
|
import {
|
|
21
16
|
CodeCellModel,
|
|
22
17
|
isMarkdownCellModel,
|
|
@@ -146,11 +141,32 @@ export class NotebookActions {
|
|
|
146
141
|
* A namespace for `NotebookActions` static methods.
|
|
147
142
|
*/
|
|
148
143
|
export namespace NotebookActions {
|
|
144
|
+
const READ_ONLY_ACTION_AUTO_CLOSE = 5000;
|
|
145
|
+
|
|
146
|
+
function notifySplitReadOnlyAction(translator?: ITranslator): void {
|
|
147
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
148
|
+
Notification.error(trans.__('The cell is read-only and cannot be split.'), {
|
|
149
|
+
autoClose: READ_ONLY_ACTION_AUTO_CLOSE
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function notifyMergeReadOnlyAction(translator?: ITranslator): void {
|
|
154
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
155
|
+
Notification.error(
|
|
156
|
+
trans.__('The cell is read-only and cannot be merged.'),
|
|
157
|
+
{
|
|
158
|
+
autoClose: READ_ONLY_ACTION_AUTO_CLOSE
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
/**
|
|
150
164
|
* Split the active cell into two or more cells.
|
|
151
165
|
*
|
|
152
166
|
* @param notebook The target notebook widget.
|
|
153
167
|
*
|
|
168
|
+
* @param translator - Application translator.
|
|
169
|
+
*
|
|
154
170
|
* #### Notes
|
|
155
171
|
* It will preserve the existing mode.
|
|
156
172
|
* The last cell will be activated if no selection is found.
|
|
@@ -165,10 +181,17 @@ export namespace NotebookActions {
|
|
|
165
181
|
* This action can be undone.
|
|
166
182
|
* The original cell is preserved to maintain kernel connections.
|
|
167
183
|
*/
|
|
168
|
-
export function splitCell(
|
|
184
|
+
export function splitCell(
|
|
185
|
+
notebook: Notebook,
|
|
186
|
+
translator?: ITranslator
|
|
187
|
+
): void {
|
|
169
188
|
if (!notebook.model || !notebook.activeCell) {
|
|
170
189
|
return;
|
|
171
190
|
}
|
|
191
|
+
if (notebook.activeCell.model.getMetadata('editable') === false) {
|
|
192
|
+
notifySplitReadOnlyAction(translator);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
172
195
|
|
|
173
196
|
const state = Private.getState(notebook);
|
|
174
197
|
// We force the notebook back in edit mode as splitting a cell
|
|
@@ -305,6 +328,8 @@ export namespace NotebookActions {
|
|
|
305
328
|
* @param addExtraLine - Whether to add an extra newline between merged cell contents
|
|
306
329
|
* (true, default) or use only a single newline (false).
|
|
307
330
|
*
|
|
331
|
+
* @param translator - Application translator.
|
|
332
|
+
*
|
|
308
333
|
* #### Notes
|
|
309
334
|
* The widget mode will be preserved.
|
|
310
335
|
* If only one cell is selected and `mergeAbove` is true, the above cell will be selected.
|
|
@@ -317,7 +342,8 @@ export namespace NotebookActions {
|
|
|
317
342
|
export function mergeCells(
|
|
318
343
|
notebook: Notebook,
|
|
319
344
|
mergeAbove: boolean = false,
|
|
320
|
-
addExtraLine: boolean = true
|
|
345
|
+
addExtraLine: boolean = true,
|
|
346
|
+
translator?: ITranslator
|
|
321
347
|
): void {
|
|
322
348
|
if (!notebook.model || !notebook.activeCell) {
|
|
323
349
|
return;
|
|
@@ -331,10 +357,15 @@ export namespace NotebookActions {
|
|
|
331
357
|
const primary = notebook.activeCell;
|
|
332
358
|
const active = notebook.activeCellIndex;
|
|
333
359
|
const attachments: nbformat.IAttachments = {};
|
|
360
|
+
let hasReadOnlyCell = false;
|
|
334
361
|
|
|
335
362
|
// Get the cells to merge.
|
|
336
363
|
notebook.widgets.forEach((child, index) => {
|
|
337
364
|
if (notebook.isSelectedOrActive(child)) {
|
|
365
|
+
if (child.model.getMetadata('editable') === false) {
|
|
366
|
+
hasReadOnlyCell = true;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
338
369
|
toMerge.push(child.model.sharedModel.getSource());
|
|
339
370
|
if (index !== active) {
|
|
340
371
|
toDelete.push(index);
|
|
@@ -349,6 +380,11 @@ export namespace NotebookActions {
|
|
|
349
380
|
}
|
|
350
381
|
});
|
|
351
382
|
|
|
383
|
+
if (hasReadOnlyCell) {
|
|
384
|
+
notifyMergeReadOnlyAction(translator);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
352
388
|
// Check for only a single cell selected.
|
|
353
389
|
if (toMerge.length === 1) {
|
|
354
390
|
// Merge with the cell above when mergeAbove is true
|
|
@@ -357,6 +393,12 @@ export namespace NotebookActions {
|
|
|
357
393
|
if (active === 0) {
|
|
358
394
|
return;
|
|
359
395
|
}
|
|
396
|
+
if (
|
|
397
|
+
notebook.widgets[active - 1].model.getMetadata('editable') === false
|
|
398
|
+
) {
|
|
399
|
+
notifyMergeReadOnlyAction(translator);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
360
402
|
// Otherwise merge with the previous cell.
|
|
361
403
|
const cellModel = cells.get(active - 1);
|
|
362
404
|
|
|
@@ -367,6 +409,12 @@ export namespace NotebookActions {
|
|
|
367
409
|
if (active === cells.length - 1) {
|
|
368
410
|
return;
|
|
369
411
|
}
|
|
412
|
+
if (
|
|
413
|
+
notebook.widgets[active + 1].model.getMetadata('editable') === false
|
|
414
|
+
) {
|
|
415
|
+
notifyMergeReadOnlyAction(translator);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
370
418
|
// Otherwise merge with the next cell.
|
|
371
419
|
const cellModel = cells.get(active + 1);
|
|
372
420
|
|
|
@@ -579,7 +627,7 @@ export namespace NotebookActions {
|
|
|
579
627
|
|
|
580
628
|
const state = Private.getState(notebook);
|
|
581
629
|
|
|
582
|
-
Private.changeCellType(notebook, value, translator);
|
|
630
|
+
Private.changeCellType(notebook, value, { translator });
|
|
583
631
|
void Private.handleState(notebook, state);
|
|
584
632
|
}
|
|
585
633
|
|
|
@@ -1532,7 +1580,6 @@ export namespace NotebookActions {
|
|
|
1532
1580
|
}
|
|
1533
1581
|
|
|
1534
1582
|
const state = Private.getState(notebook);
|
|
1535
|
-
|
|
1536
1583
|
notebook.mode = 'command';
|
|
1537
1584
|
notebook.model.sharedModel.undo();
|
|
1538
1585
|
notebook.deselectAll();
|
|
@@ -1954,15 +2001,12 @@ export namespace NotebookActions {
|
|
|
1954
2001
|
}
|
|
1955
2002
|
|
|
1956
2003
|
const state = Private.getState(notebook);
|
|
1957
|
-
const cells = notebook.model.cells;
|
|
1958
2004
|
|
|
1959
2005
|
level = Math.min(Math.max(level, 1), 6);
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
}
|
|
2006
|
+
Private.changeCellType(notebook, 'markdown', {
|
|
2007
|
+
translator,
|
|
2008
|
+
headingLevel: level
|
|
1964
2009
|
});
|
|
1965
|
-
Private.changeCellType(notebook, 'markdown', translator);
|
|
1966
2010
|
void Private.handleState(notebook, state);
|
|
1967
2011
|
}
|
|
1968
2012
|
|
|
@@ -2257,12 +2301,12 @@ export namespace NotebookActions {
|
|
|
2257
2301
|
export function trust(
|
|
2258
2302
|
notebook: Notebook,
|
|
2259
2303
|
translator?: ITranslator
|
|
2260
|
-
): Promise<
|
|
2304
|
+
): Promise<{ trusted: boolean }> {
|
|
2261
2305
|
translator = translator || nullTranslator;
|
|
2262
2306
|
const trans = translator.load('jupyterlab');
|
|
2263
2307
|
|
|
2264
2308
|
if (!notebook.model) {
|
|
2265
|
-
return Promise.resolve();
|
|
2309
|
+
return Promise.resolve({ trusted: false });
|
|
2266
2310
|
}
|
|
2267
2311
|
// Do nothing if already trusted.
|
|
2268
2312
|
|
|
@@ -2294,7 +2338,7 @@ export namespace NotebookActions {
|
|
|
2294
2338
|
return showDialog({
|
|
2295
2339
|
body: trans.__('Notebook is already trusted'),
|
|
2296
2340
|
buttons: [Dialog.okButton()]
|
|
2297
|
-
}).then(() =>
|
|
2341
|
+
}).then(() => ({ trusted: true }));
|
|
2298
2342
|
}
|
|
2299
2343
|
|
|
2300
2344
|
return showDialog({
|
|
@@ -2314,7 +2358,9 @@ export namespace NotebookActions {
|
|
|
2314
2358
|
cell.trusted = true;
|
|
2315
2359
|
}
|
|
2316
2360
|
}
|
|
2361
|
+
return { trusted: true };
|
|
2317
2362
|
}
|
|
2363
|
+
return { trusted: false };
|
|
2318
2364
|
});
|
|
2319
2365
|
}
|
|
2320
2366
|
|
|
@@ -2412,6 +2458,7 @@ namespace Private {
|
|
|
2412
2458
|
/**
|
|
2413
2459
|
* Notebook cell executor
|
|
2414
2460
|
*/
|
|
2461
|
+
// eslint-disable-next-line no-unassigned-vars
|
|
2415
2462
|
export let executor: INotebookCellExecutor;
|
|
2416
2463
|
|
|
2417
2464
|
/**
|
|
@@ -2812,8 +2859,12 @@ namespace Private {
|
|
|
2812
2859
|
export function changeCellType(
|
|
2813
2860
|
notebook: Notebook,
|
|
2814
2861
|
value: nbformat.CellType,
|
|
2815
|
-
|
|
2862
|
+
options?: {
|
|
2863
|
+
translator?: ITranslator;
|
|
2864
|
+
headingLevel?: number;
|
|
2865
|
+
}
|
|
2816
2866
|
): void {
|
|
2867
|
+
const { translator, headingLevel } = options ?? {};
|
|
2817
2868
|
const notebookSharedModel = notebook.model!.sharedModel;
|
|
2818
2869
|
notebook.widgets.forEach((child, index) => {
|
|
2819
2870
|
if (!notebook.isSelectedOrActive(child)) {
|
|
@@ -2823,8 +2874,7 @@ namespace Private {
|
|
|
2823
2874
|
child.model.type === 'code' &&
|
|
2824
2875
|
(child as CodeCell).outputArea.pendingInput
|
|
2825
2876
|
) {
|
|
2826
|
-
|
|
2827
|
-
const trans = translator.load('jupyterlab');
|
|
2877
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
2828
2878
|
// Do not permit changing cell type when input is pending
|
|
2829
2879
|
void showDialog({
|
|
2830
2880
|
title: trans.__('Cell type not changed due to pending input'),
|
|
@@ -2836,8 +2886,7 @@ namespace Private {
|
|
|
2836
2886
|
return;
|
|
2837
2887
|
}
|
|
2838
2888
|
if (child.model.getMetadata('editable') == false) {
|
|
2839
|
-
|
|
2840
|
-
const trans = translator.load('jupyterlab');
|
|
2889
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
2841
2890
|
// Do not permit changing cell type when the cell is readonly
|
|
2842
2891
|
void showDialog({
|
|
2843
2892
|
title: trans.__('Cell is read-only'),
|
|
@@ -2848,6 +2897,10 @@ namespace Private {
|
|
|
2848
2897
|
}
|
|
2849
2898
|
if (child.model.type !== value) {
|
|
2850
2899
|
const raw = child.model.toJSON();
|
|
2900
|
+
let newSource = raw.source as string;
|
|
2901
|
+
if (headingLevel !== undefined) {
|
|
2902
|
+
newSource = Private.setMarkdownHeader(newSource, headingLevel);
|
|
2903
|
+
}
|
|
2851
2904
|
notebookSharedModel.transact(() => {
|
|
2852
2905
|
notebookSharedModel.deleteCell(index);
|
|
2853
2906
|
if (value === 'code') {
|
|
@@ -2861,7 +2914,7 @@ namespace Private {
|
|
|
2861
2914
|
const newCell = notebookSharedModel.insertCell(index, {
|
|
2862
2915
|
id: raw.id,
|
|
2863
2916
|
cell_type: value,
|
|
2864
|
-
source:
|
|
2917
|
+
source: newSource,
|
|
2865
2918
|
metadata: raw.metadata
|
|
2866
2919
|
});
|
|
2867
2920
|
if (raw.attachments && ['markdown', 'raw'].includes(value)) {
|
|
@@ -2869,6 +2922,15 @@ namespace Private {
|
|
|
2869
2922
|
raw.attachments as nbformat.IAttachments;
|
|
2870
2923
|
}
|
|
2871
2924
|
});
|
|
2925
|
+
} else if (value === 'markdown' && headingLevel !== undefined) {
|
|
2926
|
+
notebookSharedModel.transact(() => {
|
|
2927
|
+
child.model.sharedModel.setSource(
|
|
2928
|
+
Private.setMarkdownHeader(
|
|
2929
|
+
child.model.sharedModel.getSource(),
|
|
2930
|
+
headingLevel
|
|
2931
|
+
)
|
|
2932
|
+
);
|
|
2933
|
+
});
|
|
2872
2934
|
}
|
|
2873
2935
|
if (value === 'markdown') {
|
|
2874
2936
|
// Fetch the new widget and unrender it.
|
|
@@ -2947,11 +3009,10 @@ namespace Private {
|
|
|
2947
3009
|
}
|
|
2948
3010
|
|
|
2949
3011
|
/**
|
|
2950
|
-
* Set the markdown header level of a cell.
|
|
3012
|
+
* Set the markdown header level of a cell source.
|
|
2951
3013
|
*/
|
|
2952
|
-
export function setMarkdownHeader(
|
|
3014
|
+
export function setMarkdownHeader(source: string, level: number): string {
|
|
2953
3015
|
// Remove existing header or leading white space.
|
|
2954
|
-
let source = cell.sharedModel.getSource();
|
|
2955
3016
|
const regex = /^(#+\s*)|^(\s*)/;
|
|
2956
3017
|
const newHeader = Array(level + 1).join('#') + ' ';
|
|
2957
3018
|
const matches = regex.exec(source);
|
|
@@ -2959,7 +3020,7 @@ namespace Private {
|
|
|
2959
3020
|
if (matches) {
|
|
2960
3021
|
source = source.slice(matches[0].length);
|
|
2961
3022
|
}
|
|
2962
|
-
|
|
3023
|
+
return newHeader + source;
|
|
2963
3024
|
}
|
|
2964
3025
|
|
|
2965
3026
|
/** Functionality related to collapsible headings */
|