@jupyterlab/notebook 4.6.0-alpha.5 → 4.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlab/notebook",
3
- "version": "4.6.0-alpha.5",
3
+ "version": "4.6.0-beta.1",
4
4
  "description": "JupyterLab - Notebook",
5
5
  "homepage": "https://github.com/jupyterlab/jupyterlab",
6
6
  "bugs": {
@@ -40,26 +40,27 @@
40
40
  "watch": "tsc -b --watch"
41
41
  },
42
42
  "dependencies": {
43
- "@jupyter/ydoc": "^3.1.0",
44
- "@jupyterlab/apputils": "^4.7.0-alpha.5",
45
- "@jupyterlab/cells": "^4.6.0-alpha.5",
46
- "@jupyterlab/codeeditor": "^4.6.0-alpha.5",
47
- "@jupyterlab/codemirror": "^4.6.0-alpha.5",
48
- "@jupyterlab/coreutils": "^6.6.0-alpha.5",
49
- "@jupyterlab/docregistry": "^4.6.0-alpha.5",
50
- "@jupyterlab/documentsearch": "^4.6.0-alpha.5",
51
- "@jupyterlab/lsp": "^4.6.0-alpha.5",
52
- "@jupyterlab/markedparser-extension": "^4.6.0-alpha.5",
53
- "@jupyterlab/nbformat": "^4.6.0-alpha.5",
54
- "@jupyterlab/observables": "^5.6.0-alpha.5",
55
- "@jupyterlab/rendermime": "^4.6.0-alpha.5",
56
- "@jupyterlab/services": "^7.6.0-alpha.5",
57
- "@jupyterlab/settingregistry": "^4.6.0-alpha.5",
58
- "@jupyterlab/statusbar": "^4.6.0-alpha.5",
59
- "@jupyterlab/toc": "^6.6.0-alpha.5",
60
- "@jupyterlab/translation": "^4.6.0-alpha.5",
61
- "@jupyterlab/ui-components": "^4.6.0-alpha.5",
43
+ "@jupyter/ydoc": "^4.0.0-a3",
44
+ "@jupyterlab/apputils": "^4.7.0-beta.1",
45
+ "@jupyterlab/cells": "^4.6.0-beta.1",
46
+ "@jupyterlab/codeeditor": "^4.6.0-beta.1",
47
+ "@jupyterlab/codemirror": "^4.6.0-beta.1",
48
+ "@jupyterlab/coreutils": "^6.6.0-beta.1",
49
+ "@jupyterlab/docregistry": "^4.6.0-beta.1",
50
+ "@jupyterlab/documentsearch": "^4.6.0-beta.1",
51
+ "@jupyterlab/lsp": "^4.6.0-beta.1",
52
+ "@jupyterlab/markedparser-extension": "^4.6.0-beta.1",
53
+ "@jupyterlab/nbformat": "^4.6.0-beta.1",
54
+ "@jupyterlab/observables": "^5.6.0-beta.1",
55
+ "@jupyterlab/rendermime": "^4.6.0-beta.1",
56
+ "@jupyterlab/services": "^7.6.0-beta.1",
57
+ "@jupyterlab/settingregistry": "^4.6.0-beta.1",
58
+ "@jupyterlab/statusbar": "^4.6.0-beta.1",
59
+ "@jupyterlab/toc": "^6.6.0-beta.1",
60
+ "@jupyterlab/translation": "^4.6.0-beta.1",
61
+ "@jupyterlab/ui-components": "^4.6.0-beta.1",
62
62
  "@lumino/algorithm": "^2.0.4",
63
+ "@lumino/commands": "^2.3.3",
63
64
  "@lumino/coreutils": "^2.2.2",
64
65
  "@lumino/disposable": "^2.1.5",
65
66
  "@lumino/domutils": "^2.0.4",
@@ -69,11 +70,11 @@
69
70
  "@lumino/properties": "^2.0.4",
70
71
  "@lumino/signaling": "^2.1.5",
71
72
  "@lumino/virtualdom": "^2.0.4",
72
- "@lumino/widgets": "^2.7.5",
73
+ "@lumino/widgets": "^2.8.0",
73
74
  "react": "^18.2.0"
74
75
  },
75
76
  "devDependencies": {
76
- "@jupyterlab/testing": "^4.6.0-alpha.5",
77
+ "@jupyterlab/testing": "^4.6.0-beta.1",
77
78
  "@types/jest": "^29.2.0",
78
79
  "jest": "^29.2.0",
79
80
  "rimraf": "~5.0.5",
package/src/actions.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import type {
5
6
  ISessionContext,
@@ -11,8 +12,9 @@ import {
11
12
  showDialog,
12
13
  SystemClipboard
13
14
  } from '@jupyterlab/apputils';
14
- import type { Cell, CodeCell, ICodeCellModel } from '@jupyterlab/cells';
15
+ import type { Cell, ICodeCellModel } from '@jupyterlab/cells';
15
16
  import {
17
+ CodeCell,
16
18
  CodeCellModel,
17
19
  isMarkdownCellModel,
18
20
  isRawCellModel,
@@ -21,8 +23,9 @@ import {
21
23
  import { Notification } from '@jupyterlab/apputils';
22
24
  import { signalToPromise } from '@jupyterlab/coreutils';
23
25
  import * as nbformat from '@jupyterlab/nbformat';
24
- import type { KernelMessage } from '@jupyterlab/services';
26
+ import type { Kernel, KernelMessage } from '@jupyterlab/services';
25
27
  import type { ISharedAttachmentsCell } from '@jupyter/ydoc';
28
+ import type { YNotebook } from '@jupyter/ydoc';
26
29
  import type { ITranslator } from '@jupyterlab/translation';
27
30
  import { nullTranslator } from '@jupyterlab/translation';
28
31
  import { every, findIndex } from '@lumino/algorithm';
@@ -442,6 +445,22 @@ export namespace NotebookActions {
442
445
  : undefined
443
446
  };
444
447
 
448
+ // Detach kernel futures from cells about to be deleted so OutputArea.dispose()
449
+ // does not terminate them - they stay live in kernel._futures for reconnection
450
+ // after undo. Handlers are cleared by detachFuture() so the future no
451
+ // longer holds references to the (soon-to-be-disposed) output area.
452
+ const storedExecutions: Private.IStoredCellExecution[] = [];
453
+ [active, ...toDelete].forEach(index => {
454
+ const cell = notebook.widgets[index];
455
+ if (!(cell instanceof CodeCell)) {
456
+ return;
457
+ }
458
+ const stored = Private.captureExecution(cell);
459
+ if (stored) {
460
+ storedExecutions.push(stored);
461
+ }
462
+ });
463
+
445
464
  // Make the changes while preserving history.
446
465
  model.sharedModel.transact(() => {
447
466
  model.sharedModel.deleteCell(active);
@@ -452,6 +471,14 @@ export namespace NotebookActions {
452
471
  model.sharedModel.deleteCell(index);
453
472
  });
454
473
  });
474
+
475
+ // Store execution context in the undo stack item so undo() can restore state.
476
+ if (storedExecutions.length > 0) {
477
+ const undoManager = (model.sharedModel as YNotebook).undoManager;
478
+ const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
479
+ lastItem?.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
480
+ }
481
+
455
482
  // If the original cell is a markdown cell, make sure
456
483
  // the new cell is unrendered.
457
484
  if (primary instanceof MarkdownCell) {
@@ -1581,7 +1608,60 @@ export namespace NotebookActions {
1581
1608
 
1582
1609
  const state = Private.getState(notebook);
1583
1610
  notebook.mode = 'command';
1611
+
1612
+ // Capture execution context from the stack item being popped.
1613
+ let storedExecutions: Private.IStoredCellExecution[] | undefined;
1614
+ const undoManager = (notebook.model.sharedModel as YNotebook).undoManager;
1615
+ const onStackItemPopped = ({
1616
+ stackItem
1617
+ }: {
1618
+ stackItem: { meta: Map<unknown, unknown> };
1619
+ }) => {
1620
+ storedExecutions = stackItem.meta.get(Private.CELL_EXECUTION_META_KEY) as
1621
+ | Private.IStoredCellExecution[]
1622
+ | undefined;
1623
+ };
1624
+ undoManager.on('stack-item-popped', onStackItemPopped);
1584
1625
  notebook.model.sharedModel.undo();
1626
+ undoManager.off('stack-item-popped', onStackItemPopped);
1627
+
1628
+ // Restore execution state on resurrected cell widgets.
1629
+ storedExecutions?.forEach(({ cellId, future, isDone, buffered }) => {
1630
+ const cell = notebook.widgets.find(w => w.model.id === cellId);
1631
+ if (!(cell instanceof CodeCell)) {
1632
+ return;
1633
+ }
1634
+ if (isDone()) {
1635
+ // Execution finished or was interrupted before undo - ensure state is idle.
1636
+ cell.model.executionState = 'idle';
1637
+ return;
1638
+ }
1639
+ // Still running - reconnect the future so the cell receives remaining
1640
+ // output and stdin requests (e.g. input()), and tracks the idle transition.
1641
+ cell.outputArea.reattachFuture(future);
1642
+ // Replay any IOPub messages that arrived while the future was detached.
1643
+ // After reattachFuture, future.onIOPub routes to the new output area.
1644
+ for (const msg of buffered) {
1645
+ void future.onIOPub(msg);
1646
+ }
1647
+ // The original execute() call targeted the old cell widget, so it will
1648
+ // not update executionCount/executionState on this resurrected cell.
1649
+ // Drive the idle transition here instead.
1650
+ const cellRef = cell;
1651
+ void future.done.then(
1652
+ reply => {
1653
+ if (!cellRef.isDisposed) {
1654
+ cellRef.model.executionCount = reply.content.execution_count;
1655
+ }
1656
+ },
1657
+ () => {
1658
+ if (!cellRef.isDisposed) {
1659
+ cellRef.model.executionState = 'idle';
1660
+ }
1661
+ }
1662
+ );
1663
+ });
1664
+
1585
1665
  notebook.deselectAll();
1586
1666
  void Private.handleState(notebook, state);
1587
1667
  }
@@ -1977,6 +2057,36 @@ export namespace NotebookActions {
1977
2057
  }
1978
2058
  }
1979
2059
 
2060
+ /**
2061
+ * Select the last modified cell and pop it from the back stack
2062
+ *
2063
+ * @param notebook - The target notebook widget.
2064
+ */
2065
+ export async function selectLastModifiedCell(
2066
+ notebook: Notebook
2067
+ ): Promise<void> {
2068
+ const cell = notebook.popLastModifiedCell();
2069
+ if (cell && cell !== notebook.activeCell && !cell.isDisposed) {
2070
+ notebook.activeCellIndex = notebook.widgets.indexOf(cell);
2071
+ await notebook.scrollToCell(cell);
2072
+ }
2073
+ }
2074
+
2075
+ /**
2076
+ * Select the next modified cell and pop it from the forward stack
2077
+ *
2078
+ * @param notebook - The target notebook widget.
2079
+ */
2080
+ export async function selectNextModifiedCell(
2081
+ notebook: Notebook
2082
+ ): Promise<void> {
2083
+ const cell = notebook.popNextModifiedCell();
2084
+ if (cell && cell !== notebook.activeCell && !cell.isDisposed) {
2085
+ notebook.activeCellIndex = notebook.widgets.indexOf(cell);
2086
+ await notebook.scrollToCell(cell);
2087
+ }
2088
+ }
2089
+
1980
2090
  /**
1981
2091
  * Set the markdown header level.
1982
2092
  *
@@ -2455,6 +2565,44 @@ export function setCellExecutor(executor: INotebookCellExecutor): void {
2455
2565
  * A namespace for private data.
2456
2566
  */
2457
2567
  namespace Private {
2568
+ /** Key used to store cell execution state in Y.js undo stack item metadata. */
2569
+ export const CELL_EXECUTION_META_KEY = Symbol('cellExecutionState');
2570
+
2571
+ export interface IStoredCellExecution {
2572
+ cellId: string;
2573
+ future: Kernel.IShellFuture<
2574
+ KernelMessage.IExecuteRequestMsg,
2575
+ KernelMessage.IExecuteReplyMsg
2576
+ >;
2577
+ isDone: () => boolean;
2578
+ /** IOPub messages that arrived while the future was detached. */
2579
+ buffered: KernelMessage.IIOPubMessage[];
2580
+ }
2581
+
2582
+ /**
2583
+ * Detach the kernel future from a code cell, buffering any IOPub messages
2584
+ * that arrive while it is detached so they can be replayed on reattach.
2585
+ *
2586
+ * Returns null if the cell has no active future.
2587
+ */
2588
+ export function captureExecution(
2589
+ cell: CodeCell
2590
+ ): IStoredCellExecution | null {
2591
+ const future = cell.outputArea.detachFuture();
2592
+ if (!future) {
2593
+ return null;
2594
+ }
2595
+ let done = false;
2596
+ void future.done.finally(() => {
2597
+ done = true;
2598
+ });
2599
+ const buffered: KernelMessage.IIOPubMessage[] = [];
2600
+ future.onIOPub = msg => {
2601
+ buffered.push(msg);
2602
+ };
2603
+ return { cellId: cell.model.id, future, isDone: () => done, buffered };
2604
+ }
2605
+
2458
2606
  /**
2459
2607
  * Notebook cell executor
2460
2608
  */
@@ -2901,6 +3049,11 @@ namespace Private {
2901
3049
  if (headingLevel !== undefined) {
2902
3050
  newSource = Private.setMarkdownHeader(newSource, headingLevel);
2903
3051
  }
3052
+ // Detach future before the transaction so dispose() does not cancel it.
3053
+ const storedExecution =
3054
+ child instanceof CodeCell
3055
+ ? Private.captureExecution(child)
3056
+ : undefined;
2904
3057
  notebookSharedModel.transact(() => {
2905
3058
  notebookSharedModel.deleteCell(index);
2906
3059
  if (value === 'code') {
@@ -2922,6 +3075,14 @@ namespace Private {
2922
3075
  raw.attachments as nbformat.IAttachments;
2923
3076
  }
2924
3077
  });
3078
+ if (storedExecution) {
3079
+ const undoManager = (notebookSharedModel as YNotebook).undoManager;
3080
+ const lastItem =
3081
+ undoManager.undoStack[undoManager.undoStack.length - 1];
3082
+ lastItem?.meta.set(Private.CELL_EXECUTION_META_KEY, [
3083
+ storedExecution
3084
+ ]);
3085
+ }
2925
3086
  } else if (value === 'markdown' && headingLevel !== undefined) {
2926
3087
  notebookSharedModel.transact(() => {
2927
3088
  child.model.sharedModel.setSource(
@@ -2971,6 +3132,19 @@ namespace Private {
2971
3132
 
2972
3133
  // If cells are not deletable, we may not have anything to delete.
2973
3134
  if (toDelete.length > 0) {
3135
+ // Detach futures before the transaction so dispose() does not cancel them.
3136
+ const storedExecutions: Private.IStoredCellExecution[] = [];
3137
+ toDelete.forEach(index => {
3138
+ const cell = notebook.widgets[index];
3139
+ if (!(cell instanceof CodeCell)) {
3140
+ return;
3141
+ }
3142
+ const stored = Private.captureExecution(cell);
3143
+ if (stored) {
3144
+ storedExecutions.push(stored);
3145
+ }
3146
+ });
3147
+
2974
3148
  // Delete the cells as one undo event.
2975
3149
  sharedModel.transact(() => {
2976
3150
  // Delete cells in reverse order to maintain the correct indices.
@@ -2994,6 +3168,12 @@ namespace Private {
2994
3168
  });
2995
3169
  }
2996
3170
  });
3171
+ if (storedExecutions.length > 0) {
3172
+ const undoManager = (sharedModel as YNotebook).undoManager;
3173
+ const lastItem =
3174
+ undoManager.undoStack[undoManager.undoStack.length - 1];
3175
+ lastItem?.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
3176
+ }
2997
3177
  // Select the *first* interior cell not deleted or the cell
2998
3178
  // *after* the last selected cell.
2999
3179
  // Note: The activeCellIndex is clamped to the available cells,
@@ -3013,7 +3193,7 @@ namespace Private {
3013
3193
  */
3014
3194
  export function setMarkdownHeader(source: string, level: number): string {
3015
3195
  // Remove existing header or leading white space.
3016
- const regex = /^(#+\s*)|^(\s*)/;
3196
+ const regex = /^#+\s*|^\s*/;
3017
3197
  const newHeader = Array(level + 1).join('#') + ' ';
3018
3198
  const matches = regex.exec(source);
3019
3199
 
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import { Dialog, showDialog } from '@jupyterlab/apputils';
5
6
  import {
package/src/model.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import { Dialog, showDialog } from '@jupyterlab/apputils';
5
6
  import type { ICellModel } from '@jupyterlab/cells';
@@ -15,6 +16,7 @@ import type {
15
16
  import { YNotebook } from '@jupyter/ydoc';
16
17
  import type { ITranslator, TranslationBundle } from '@jupyterlab/translation';
17
18
  import { nullTranslator } from '@jupyterlab/translation';
19
+ import type { JSONValue } from '@lumino/coreutils';
18
20
  import { JSONExt } from '@lumino/coreutils';
19
21
  import type { ISignal } from '@lumino/signaling';
20
22
  import { Signal } from '@lumino/signaling';
@@ -160,19 +162,14 @@ export class NotebookModel implements INotebookModel {
160
162
  * The dirty state of the document.
161
163
  */
162
164
  get dirty(): boolean {
163
- return this._dirty;
165
+ return this.sharedModel.dirty;
164
166
  }
165
167
  set dirty(newValue: boolean) {
166
- const oldValue = this._dirty;
168
+ const oldValue = this.dirty;
167
169
  if (newValue === oldValue) {
168
170
  return;
169
171
  }
170
- this._dirty = newValue;
171
- this.triggerStateChange({
172
- name: 'dirty',
173
- oldValue,
174
- newValue
175
- });
172
+ this.sharedModel.dirty = newValue;
176
173
  }
177
174
 
178
175
  /**
@@ -382,10 +379,9 @@ close the notebook without saving it.`,
382
379
  { cell_type: 'code', source: '', metadata: { trusted: true } }
383
380
  ];
384
381
  }
385
- this.sharedModel.fromJSON(copy);
382
+ this.sharedModel.setSource(copy as JSONValue);
386
383
 
387
384
  this._ensureMetadata();
388
- this.dirty = true;
389
385
  }
390
386
 
391
387
  /**
@@ -431,12 +427,7 @@ close the notebook without saving it.`,
431
427
  ): void {
432
428
  if (changes.stateChange) {
433
429
  changes.stateChange.forEach(value => {
434
- if (value.name === 'dirty') {
435
- // Setting `dirty` will trigger the state change.
436
- // We always set `dirty` because the shared model state
437
- // and the local attribute are synchronized one way shared model -> _dirty
438
- this.dirty = value.newValue;
439
- } else if (value.oldValue !== value.newValue) {
430
+ if (value.oldValue !== value.newValue) {
440
431
  this.triggerStateChange({
441
432
  newValue: undefined,
442
433
  oldValue: undefined,
@@ -474,7 +465,6 @@ close the notebook without saving it.`,
474
465
  */
475
466
  protected triggerContentChange(): void {
476
467
  this._contentChanged.emit(void 0);
477
- this.dirty = true;
478
468
  }
479
469
 
480
470
  /**
@@ -494,7 +484,6 @@ close the notebook without saving it.`,
494
484
  */
495
485
  protected standaloneModel = false;
496
486
 
497
- private _dirty = false;
498
487
  private _readOnly = false;
499
488
  private _contentChanged = new Signal<this, void>(this);
500
489
  private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
package/src/panel.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import type { ISessionContext } from '@jupyterlab/apputils';
5
6
  import { Dialog, Printing, showDialog } from '@jupyterlab/apputils';
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import { Dialog, showDialog } from '@jupyterlab/apputils';
5
6
  import type {
package/src/testutils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import type { ISessionContext } from '@jupyterlab/apputils';
5
6
  import {
package/src/toc.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Jupyter Development Team.
2
2
  // Distributed under the terms of the Modified BSD License.
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
4
 
4
5
  import type { CodeCell, ICellModel } from '@jupyterlab/cells';
5
6
  import { Cell, MarkdownCell } from '@jupyterlab/cells';
@@ -15,6 +16,13 @@ import { NotebookActions } from './actions';
15
16
  import type { NotebookPanel } from './panel';
16
17
  import type { INotebookTracker } from './tokens';
17
18
  import type { Notebook } from './widget';
19
+ import {
20
+ CommandToolbarButton,
21
+ jumpBackIcon,
22
+ jumpForwardIcon
23
+ } from '@jupyterlab/ui-components';
24
+ import type { ToolbarRegistry } from '@jupyterlab/apputils';
25
+ import type { CommandRegistry } from '@lumino/commands';
18
26
 
19
27
  /**
20
28
  * Cell running status
@@ -559,11 +567,13 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
559
567
  * @param tracker Widget tracker
560
568
  * @param parser Markdown parser
561
569
  * @param sanitizer Sanitizer
570
+ * @param commands A registry of commands
562
571
  */
563
572
  constructor(
564
573
  tracker: INotebookTracker,
565
574
  protected parser: IMarkdownParser | null,
566
- protected sanitizer: IRenderMime.ISanitizer
575
+ protected sanitizer: IRenderMime.ISanitizer,
576
+ protected commands: CommandRegistry | undefined
567
577
  ) {
568
578
  super(tracker);
569
579
  }
@@ -790,6 +800,44 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
790
800
  return model;
791
801
  }
792
802
 
803
+ /**
804
+ * Get the toolbar items for the widget
805
+ *
806
+ * @param widget - widget
807
+ * @returns List of toolbar items
808
+ */
809
+ getToolbarItems(widget: NotebookPanel): ToolbarRegistry.IToolbarItem[] {
810
+ if (!this.commands) {
811
+ return [];
812
+ }
813
+ return [
814
+ {
815
+ name: 'select-last-modified-back',
816
+ widget: new CommandToolbarButton({
817
+ commands: this.commands,
818
+ id: 'notebook:select-last-modified-cell',
819
+ args: {
820
+ toolbar: true
821
+ },
822
+ icon: jumpBackIcon,
823
+ label: ''
824
+ })
825
+ },
826
+ {
827
+ name: 'select-last-modified-forward',
828
+ widget: new CommandToolbarButton({
829
+ commands: this.commands,
830
+ id: 'notebook:select-next-modified-cell',
831
+ args: {
832
+ toolbar: true
833
+ },
834
+ icon: jumpForwardIcon,
835
+ label: ''
836
+ })
837
+ }
838
+ ];
839
+ }
840
+
793
841
  private _scrollToTop: boolean = true;
794
842
  }
795
843