UNPKG

88.3 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { DOMUtils } from '@jupyterlab/apputils';
4import { Cell, CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells';
5import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
6import { TableOfContentsUtils } from '@jupyterlab/toc';
7import { nullTranslator } from '@jupyterlab/translation';
8import { WindowedList } from '@jupyterlab/ui-components';
9import { ArrayExt, findIndex } from '@lumino/algorithm';
10import { MimeData } from '@lumino/coreutils';
11import { ElementExt } from '@lumino/domutils';
12import { Drag } from '@lumino/dragdrop';
13import { AttachedProperty } from '@lumino/properties';
14import { Signal } from '@lumino/signaling';
15import { h, VirtualDOM } from '@lumino/virtualdom';
16import { PanelLayout, Widget } from '@lumino/widgets';
17import { NotebookActions } from './actions';
18import { DROP_SOURCE_CLASS, DROP_TARGET_CLASS } from './constants';
19import { NotebookViewModel, NotebookWindowedLayout } from './windowing';
20import { NotebookFooter } from './notebookfooter';
21/**
22 * The data attribute added to a widget that has an active kernel.
23 */
24const KERNEL_USER = 'jpKernelUser';
25/**
26 * The data attribute added to a widget that can run code.
27 */
28const CODE_RUNNER = 'jpCodeRunner';
29/**
30 * The data attribute added to a widget that can undo.
31 */
32const UNDOER = 'jpUndoer';
33/**
34 * The class name added to notebook widgets.
35 */
36const NB_CLASS = 'jp-Notebook';
37/**
38 * The class name added to notebook widget cells.
39 */
40const NB_CELL_CLASS = 'jp-Notebook-cell';
41/**
42 * The class name added to a notebook in edit mode.
43 */
44const EDIT_CLASS = 'jp-mod-editMode';
45/**
46 * The class name added to a notebook in command mode.
47 */
48const COMMAND_CLASS = 'jp-mod-commandMode';
49/**
50 * The class name added to the active cell.
51 */
52const ACTIVE_CLASS = 'jp-mod-active';
53/**
54 * The class name added to selected cells.
55 */
56const SELECTED_CLASS = 'jp-mod-selected';
57/**
58 * The class name added to an active cell when there are other selected cells.
59 */
60const OTHER_SELECTED_CLASS = 'jp-mod-multiSelected';
61/**
62 * The class name added to unconfined images.
63 */
64const UNCONFINED_CLASS = 'jp-mod-unconfined';
65/**
66 * The class name added to the notebook when an element within it is focused
67 * and takes keyboard input, such as focused <input> or <div contenteditable>.
68 *
69 * This class is also effective when the focused element is in shadow DOM.
70 */
71const READ_WRITE_CLASS = 'jp-mod-readWrite';
72/**
73 * The class name added to drag images.
74 */
75const DRAG_IMAGE_CLASS = 'jp-dragImage';
76/**
77 * The class name added to singular drag images
78 */
79const SINGLE_DRAG_IMAGE_CLASS = 'jp-dragImage-singlePrompt';
80/**
81 * The class name added to the drag image cell content.
82 */
83const CELL_DRAG_CONTENT_CLASS = 'jp-dragImage-content';
84/**
85 * The class name added to the drag image cell content.
86 */
87const CELL_DRAG_PROMPT_CLASS = 'jp-dragImage-prompt';
88/**
89 * The class name added to the drag image cell content.
90 */
91const CELL_DRAG_MULTIPLE_BACK = 'jp-dragImage-multipleBack';
92/**
93 * The mimetype used for Jupyter cell data.
94 */
95const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
96/**
97 * The threshold in pixels to start a drag event.
98 */
99const DRAG_THRESHOLD = 5;
100/**
101 * Maximal remaining time for idle callback
102 *
103 * Ref: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#getting_the_most_out_of_idle_callbacks
104 */
105const MAXIMUM_TIME_REMAINING = 50;
106/**
107 * The class attached to the heading collapser button
108 */
109const HEADING_COLLAPSER_CLASS = 'jp-collapseHeadingButton';
110/**
111 * The class that controls the visibility of "heading collapser" and "show hidden cells" buttons.
112 */
113const HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS = 'jp-mod-showHiddenCellsButton';
114const SIDE_BY_SIDE_CLASS = 'jp-mod-sideBySide';
115if (window.requestIdleCallback === undefined) {
116 // On Safari, requestIdleCallback is not available, so we use replacement functions for `idleCallbacks`
117 // See: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#falling_back_to_settimeout
118 // eslint-disable-next-line @typescript-eslint/ban-types
119 window.requestIdleCallback = function (handler) {
120 let startTime = Date.now();
121 return setTimeout(function () {
122 handler({
123 didTimeout: false,
124 timeRemaining: function () {
125 return Math.max(0, 50.0 - (Date.now() - startTime));
126 }
127 });
128 }, 1);
129 };
130 window.cancelIdleCallback = function (id) {
131 clearTimeout(id);
132 };
133}
134/**
135 * A widget which renders static non-interactive notebooks.
136 *
137 * #### Notes
138 * The widget model must be set separately and can be changed
139 * at any time. Consumers of the widget must account for a
140 * `null` model, and may want to listen to the `modelChanged`
141 * signal.
142 */
143export class StaticNotebook extends WindowedList {
144 /**
145 * Construct a notebook widget.
146 */
147 constructor(options) {
148 var _a, _b, _c, _d, _e, _f;
149 const cells = new Array();
150 const windowingActive = ((_b = (_a = options.notebookConfig) === null || _a === void 0 ? void 0 : _a.windowingMode) !== null && _b !== void 0 ? _b : StaticNotebook.defaultNotebookConfig.windowingMode) === 'full';
151 super({
152 model: new NotebookViewModel(cells, {
153 overscanCount: (_d = (_c = options.notebookConfig) === null || _c === void 0 ? void 0 : _c.overscanCount) !== null && _d !== void 0 ? _d : StaticNotebook.defaultNotebookConfig.overscanCount,
154 windowingActive
155 }),
156 layout: new NotebookWindowedLayout(),
157 renderer: (_e = options.renderer) !== null && _e !== void 0 ? _e : WindowedList.defaultRenderer,
158 scrollbar: false
159 });
160 this._cellCollapsed = new Signal(this);
161 this._cellInViewportChanged = new Signal(this);
162 this._renderingLayoutChanged = new Signal(this);
163 this.addClass(NB_CLASS);
164 this.cellsArray = cells;
165 this._idleCallBack = null;
166 this._editorConfig = StaticNotebook.defaultEditorConfig;
167 this._notebookConfig = StaticNotebook.defaultNotebookConfig;
168 this._mimetype = IEditorMimeTypeService.defaultMimeType;
169 this._notebookModel = null;
170 this._modelChanged = new Signal(this);
171 this._modelContentChanged = new Signal(this);
172 this.node.dataset[KERNEL_USER] = 'true';
173 this.node.dataset[UNDOER] = 'true';
174 this.node.dataset[CODE_RUNNER] = 'true';
175 this.rendermime = options.rendermime;
176 this.translator = options.translator || nullTranslator;
177 this.contentFactory = options.contentFactory;
178 this.editorConfig =
179 options.editorConfig || StaticNotebook.defaultEditorConfig;
180 this.notebookConfig =
181 options.notebookConfig || StaticNotebook.defaultNotebookConfig;
182 this._updateNotebookConfig();
183 this._mimetypeService = options.mimeTypeService;
184 this.renderingLayout = (_f = options.notebookConfig) === null || _f === void 0 ? void 0 : _f.renderingLayout;
185 this.kernelHistory = options.kernelHistory;
186 }
187 get cellCollapsed() {
188 return this._cellCollapsed;
189 }
190 get cellInViewportChanged() {
191 return this._cellInViewportChanged;
192 }
193 /**
194 * A signal emitted when the model of the notebook changes.
195 */
196 get modelChanged() {
197 return this._modelChanged;
198 }
199 /**
200 * A signal emitted when the model content changes.
201 *
202 * #### Notes
203 * This is a convenience signal that follows the current model.
204 */
205 get modelContentChanged() {
206 return this._modelContentChanged;
207 }
208 /**
209 * A signal emitted when the rendering layout of the notebook changes.
210 */
211 get renderingLayoutChanged() {
212 return this._renderingLayoutChanged;
213 }
214 /**
215 * The model for the widget.
216 */
217 get model() {
218 return this._notebookModel;
219 }
220 set model(newValue) {
221 var _a;
222 newValue = newValue || null;
223 if (this._notebookModel === newValue) {
224 return;
225 }
226 const oldValue = this._notebookModel;
227 this._notebookModel = newValue;
228 // Trigger private, protected, and public changes.
229 this._onModelChanged(oldValue, newValue);
230 this.onModelChanged(oldValue, newValue);
231 this._modelChanged.emit(void 0);
232 // Trigger state change
233 this.viewModel.itemsList = (_a = newValue === null || newValue === void 0 ? void 0 : newValue.cells) !== null && _a !== void 0 ? _a : null;
234 }
235 /**
236 * Get the mimetype for code cells.
237 */
238 get codeMimetype() {
239 return this._mimetype;
240 }
241 /**
242 * A read-only sequence of the widgets in the notebook.
243 */
244 get widgets() {
245 return this.cellsArray;
246 }
247 /**
248 * A configuration object for cell editor settings.
249 */
250 get editorConfig() {
251 return this._editorConfig;
252 }
253 set editorConfig(value) {
254 this._editorConfig = value;
255 this._updateEditorConfig();
256 }
257 /**
258 * A configuration object for notebook settings.
259 */
260 get notebookConfig() {
261 return this._notebookConfig;
262 }
263 set notebookConfig(value) {
264 this._notebookConfig = value;
265 this._updateNotebookConfig();
266 }
267 get renderingLayout() {
268 return this._renderingLayout;
269 }
270 set renderingLayout(value) {
271 var _a;
272 this._renderingLayout = value;
273 if (this._renderingLayout === 'side-by-side') {
274 this.node.classList.add(SIDE_BY_SIDE_CLASS);
275 }
276 else {
277 this.node.classList.remove(SIDE_BY_SIDE_CLASS);
278 }
279 this._renderingLayoutChanged.emit((_a = this._renderingLayout) !== null && _a !== void 0 ? _a : 'default');
280 }
281 /**
282 * Dispose of the resources held by the widget.
283 */
284 dispose() {
285 var _a;
286 // Do nothing if already disposed.
287 if (this.isDisposed) {
288 return;
289 }
290 this._notebookModel = null;
291 (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
292 super.dispose();
293 }
294 /**
295 * Move cells preserving widget view state.
296 *
297 * #### Notes
298 * This is required because at the model level a move is a deletion
299 * followed by an insertion. Hence the view state is not preserved.
300 *
301 * @param from The index of the cell to move
302 * @param to The new index of the cell
303 * @param n Number of cells to move
304 */
305 moveCell(from, to, n = 1) {
306 if (!this.model) {
307 return;
308 }
309 const boundedTo = Math.min(this.model.cells.length - 1, Math.max(0, to));
310 if (boundedTo === from) {
311 return;
312 }
313 const viewModel = new Array(n);
314 for (let i = 0; i < n; i++) {
315 viewModel[i] = {};
316 const oldCell = this.widgets[from + i];
317 if (oldCell.model.type === 'markdown') {
318 for (const k of ['rendered', 'headingCollapsed']) {
319 // @ts-expect-error Cell has no index signature
320 viewModel[i][k] = oldCell[k];
321 }
322 }
323 }
324 this.model.sharedModel.moveCells(from, boundedTo, n);
325 for (let i = 0; i < n; i++) {
326 const newCell = this.widgets[to + i];
327 const view = viewModel[i];
328 for (const state in view) {
329 // @ts-expect-error Cell has no index signature
330 newCell[state] = view[state];
331 }
332 }
333 }
334 /**
335 * Force rendering the cell outputs of a given cell if it is still a placeholder.
336 *
337 * #### Notes
338 * The goal of this method is to allow search on cell outputs (that is based
339 * on DOM tree introspection).
340 *
341 * @param index The cell index
342 */
343 renderCellOutputs(index) {
344 const cell = this.viewModel.widgetRenderer(index);
345 if (cell instanceof CodeCell && cell.isPlaceholder()) {
346 cell.dataset.windowedListIndex = `${index}`;
347 this.layout.insertWidget(index, cell);
348 if (this.notebookConfig.windowingMode === 'full') {
349 // We need to delay slightly the removal to let codemirror properly initialize
350 requestAnimationFrame(() => {
351 this.layout.removeWidget(cell);
352 });
353 }
354 }
355 }
356 /**
357 * Adds a message to the notebook as a header.
358 */
359 addHeader() {
360 const trans = this.translator.load('jupyterlab');
361 const info = new Widget();
362 info.node.textContent = trans.__('The notebook is empty. Click the + button on the toolbar to add a new cell.');
363 this.layout.header = info;
364 }
365 /**
366 * Removes the header.
367 */
368 removeHeader() {
369 var _a;
370 (_a = this.layout.header) === null || _a === void 0 ? void 0 : _a.dispose();
371 this.layout.header = null;
372 }
373 /**
374 * Handle a new model.
375 *
376 * #### Notes
377 * This method is called after the model change has been handled
378 * internally and before the `modelChanged` signal is emitted.
379 * The default implementation is a no-op.
380 */
381 onModelChanged(oldValue, newValue) {
382 // No-op.
383 }
384 /**
385 * Handle changes to the notebook model content.
386 *
387 * #### Notes
388 * The default implementation emits the `modelContentChanged` signal.
389 */
390 onModelContentChanged(model, args) {
391 this._modelContentChanged.emit(void 0);
392 }
393 /**
394 * Handle changes to the notebook model metadata.
395 *
396 * #### Notes
397 * The default implementation updates the mimetypes of the code cells
398 * when the `language_info` metadata changes.
399 */
400 onMetadataChanged(sender, args) {
401 switch (args.key) {
402 case 'language_info':
403 this._updateMimetype();
404 break;
405 default:
406 break;
407 }
408 }
409 /**
410 * Handle a cell being inserted.
411 *
412 * The default implementation is a no-op
413 */
414 onCellInserted(index, cell) {
415 // This is a no-op.
416 }
417 /**
418 * Handle a cell being removed.
419 *
420 * The default implementation is a no-op
421 */
422 onCellRemoved(index, cell) {
423 // This is a no-op.
424 }
425 /**
426 * A message handler invoked on an `'update-request'` message.
427 *
428 * #### Notes
429 * The default implementation of this handler is a no-op.
430 */
431 onUpdateRequest(msg) {
432 if (this.notebookConfig.windowingMode === 'defer') {
433 void this._runOnIdleTime();
434 }
435 else {
436 super.onUpdateRequest(msg);
437 }
438 }
439 /**
440 * Handle a new model on the widget.
441 */
442 _onModelChanged(oldValue, newValue) {
443 var _a;
444 if (oldValue) {
445 oldValue.contentChanged.disconnect(this.onModelContentChanged, this);
446 oldValue.metadataChanged.disconnect(this.onMetadataChanged, this);
447 oldValue.cells.changed.disconnect(this._onCellsChanged, this);
448 while (this.cellsArray.length) {
449 this._removeCell(0);
450 }
451 }
452 if (!newValue) {
453 this._mimetype = IEditorMimeTypeService.defaultMimeType;
454 return;
455 }
456 this._updateMimetype();
457 const cells = newValue.cells;
458 const collab = (_a = newValue.collaborative) !== null && _a !== void 0 ? _a : false;
459 if (!collab && !cells.length) {
460 newValue.sharedModel.insertCell(0, {
461 cell_type: this.notebookConfig.defaultCell,
462 metadata: this.notebookConfig.defaultCell === 'code'
463 ? {
464 // This is an empty cell created in empty notebook, thus is trusted
465 trusted: true
466 }
467 : {}
468 });
469 }
470 let index = -1;
471 for (const cell of cells) {
472 this._insertCell(++index, cell);
473 }
474 newValue.cells.changed.connect(this._onCellsChanged, this);
475 newValue.metadataChanged.connect(this.onMetadataChanged, this);
476 newValue.contentChanged.connect(this.onModelContentChanged, this);
477 }
478 /**
479 * Handle a change cells event.
480 */
481 _onCellsChanged(sender, args) {
482 this.removeHeader();
483 switch (args.type) {
484 case 'add': {
485 let index = 0;
486 index = args.newIndex;
487 for (const value of args.newValues) {
488 this._insertCell(index++, value);
489 }
490 this._updateDataWindowedListIndex(args.newIndex, this.model.cells.length, args.newValues.length);
491 break;
492 }
493 case 'remove':
494 for (let length = args.oldValues.length; length > 0; length--) {
495 this._removeCell(args.oldIndex);
496 }
497 this._updateDataWindowedListIndex(args.oldIndex, this.model.cells.length + args.oldValues.length, -1 * args.oldValues.length);
498 // Add default cell if there are no cells remaining.
499 if (!sender.length) {
500 const model = this.model;
501 // Add the cell in a new context to avoid triggering another
502 // cell changed event during the handling of this signal.
503 requestAnimationFrame(() => {
504 if (model && !model.isDisposed && !model.sharedModel.cells.length) {
505 model.sharedModel.insertCell(0, {
506 cell_type: this.notebookConfig.defaultCell,
507 metadata: this.notebookConfig.defaultCell === 'code'
508 ? {
509 // This is an empty cell created in empty notebook, thus is trusted
510 trusted: true
511 }
512 : {}
513 });
514 }
515 });
516 }
517 break;
518 default:
519 return;
520 }
521 if (!this.model.sharedModel.cells.length) {
522 this.addHeader();
523 }
524 this.update();
525 }
526 /**
527 * Create a cell widget and insert into the notebook.
528 */
529 _insertCell(index, cell) {
530 let widget;
531 switch (cell.type) {
532 case 'code':
533 widget = this._createCodeCell(cell);
534 widget.model.mimeType = this._mimetype;
535 break;
536 case 'markdown':
537 widget = this._createMarkdownCell(cell);
538 if (cell.sharedModel.getSource() === '') {
539 widget.rendered = false;
540 }
541 break;
542 default:
543 widget = this._createRawCell(cell);
544 }
545 widget.inViewportChanged.connect(this._onCellInViewportChanged, this);
546 widget.addClass(NB_CELL_CLASS);
547 ArrayExt.insert(this.cellsArray, index, widget);
548 this.onCellInserted(index, widget);
549 this._scheduleCellRenderOnIdle();
550 }
551 /**
552 * Create a code cell widget from a code cell model.
553 */
554 _createCodeCell(model) {
555 const rendermime = this.rendermime;
556 const contentFactory = this.contentFactory;
557 const editorConfig = this.editorConfig.code;
558 const options = {
559 contentFactory,
560 editorConfig,
561 inputHistoryScope: this.notebookConfig.inputHistoryScope,
562 maxNumberOutputs: this.notebookConfig.maxNumberOutputs,
563 model,
564 placeholder: this._notebookConfig.windowingMode !== 'none',
565 rendermime,
566 translator: this.translator
567 };
568 const cell = this.contentFactory.createCodeCell(options);
569 cell.syncCollapse = true;
570 cell.syncEditable = true;
571 cell.syncScrolled = true;
572 cell.outputArea.inputRequested.connect((_, stdin) => {
573 this._onInputRequested(cell).catch(reason => {
574 console.error('Failed to scroll to cell requesting input.', reason);
575 });
576 stdin.disposed.connect(() => {
577 // The input field is removed from the DOM after the user presses Enter.
578 // This causes focus to be lost if we don't explicitly re-focus
579 // somewhere else.
580 cell.node.focus();
581 });
582 });
583 return cell;
584 }
585 /**
586 * Create a markdown cell widget from a markdown cell model.
587 */
588 _createMarkdownCell(model) {
589 const rendermime = this.rendermime;
590 const contentFactory = this.contentFactory;
591 const editorConfig = this.editorConfig.markdown;
592 const options = {
593 contentFactory,
594 editorConfig,
595 model,
596 placeholder: this._notebookConfig.windowingMode !== 'none',
597 rendermime,
598 showEditorForReadOnlyMarkdown: this._notebookConfig.showEditorForReadOnlyMarkdown
599 };
600 const cell = this.contentFactory.createMarkdownCell(options);
601 cell.syncCollapse = true;
602 cell.syncEditable = true;
603 // Connect collapsed signal for each markdown cell widget
604 cell.headingCollapsedChanged.connect(this._onCellCollapsed, this);
605 return cell;
606 }
607 /**
608 * Create a raw cell widget from a raw cell model.
609 */
610 _createRawCell(model) {
611 const contentFactory = this.contentFactory;
612 const editorConfig = this.editorConfig.raw;
613 const options = {
614 editorConfig,
615 model,
616 contentFactory,
617 placeholder: this._notebookConfig.windowingMode !== 'none'
618 };
619 const cell = this.contentFactory.createRawCell(options);
620 cell.syncCollapse = true;
621 cell.syncEditable = true;
622 return cell;
623 }
624 /**
625 * Remove a cell widget.
626 */
627 _removeCell(index) {
628 const widget = this.cellsArray[index];
629 widget.parent = null;
630 ArrayExt.removeAt(this.cellsArray, index);
631 this.onCellRemoved(index, widget);
632 widget.dispose();
633 }
634 /**
635 * Update the mimetype of the notebook.
636 */
637 _updateMimetype() {
638 var _a;
639 const info = (_a = this._notebookModel) === null || _a === void 0 ? void 0 : _a.getMetadata('language_info');
640 if (!info) {
641 return;
642 }
643 this._mimetype = this._mimetypeService.getMimeTypeByLanguage(info);
644 for (const widget of this.widgets) {
645 if (widget.model.type === 'code') {
646 widget.model.mimeType = this._mimetype;
647 }
648 }
649 }
650 /**
651 * Callback when a cell collapsed status changes.
652 *
653 * @param cell Cell changed
654 * @param collapsed New collapsed status
655 */
656 _onCellCollapsed(cell, collapsed) {
657 NotebookActions.setHeadingCollapse(cell, collapsed, this);
658 this._cellCollapsed.emit(cell);
659 }
660 /**
661 * Callback when a cell viewport status changes.
662 *
663 * @param cell Cell changed
664 */
665 _onCellInViewportChanged(cell) {
666 this._cellInViewportChanged.emit(cell);
667 }
668 /**
669 * Ensure to load in the DOM a cell requesting an user input
670 *
671 * @param cell Cell requesting an input
672 */
673 async _onInputRequested(cell) {
674 if (!cell.inViewport) {
675 const cellIndex = this.widgets.findIndex(c => c === cell);
676 if (cellIndex >= 0) {
677 await this.scrollToItem(cellIndex);
678 const inputEl = cell.node.querySelector('.jp-Stdin');
679 if (inputEl) {
680 ElementExt.scrollIntoViewIfNeeded(this.node, inputEl);
681 inputEl.focus();
682 }
683 }
684 }
685 }
686 _scheduleCellRenderOnIdle() {
687 if (this.notebookConfig.windowingMode !== 'none' && !this.isDisposed) {
688 if (!this._idleCallBack) {
689 this._idleCallBack = requestIdleCallback((deadline) => {
690 this._idleCallBack = null;
691 // In case of timeout, render for some time even if it means freezing the UI
692 // This avoids the cells to never be loaded.
693 void this._runOnIdleTime(deadline.didTimeout
694 ? MAXIMUM_TIME_REMAINING
695 : deadline.timeRemaining());
696 }, {
697 timeout: 3000
698 });
699 }
700 }
701 }
702 _updateDataWindowedListIndex(start, end, delta) {
703 for (let cellIdx = 0; cellIdx < this.viewportNode.childElementCount; cellIdx++) {
704 const cell = this.viewportNode.children[cellIdx];
705 const globalIndex = parseInt(cell.dataset.windowedListIndex, 10);
706 if (globalIndex >= start && globalIndex < end) {
707 cell.dataset.windowedListIndex = `${globalIndex + delta}`;
708 }
709 }
710 }
711 /**
712 * Update editor settings for notebook cells.
713 */
714 _updateEditorConfig() {
715 for (let i = 0; i < this.widgets.length; i++) {
716 const cell = this.widgets[i];
717 let config = {};
718 switch (cell.model.type) {
719 case 'code':
720 config = this._editorConfig.code;
721 break;
722 case 'markdown':
723 config = this._editorConfig.markdown;
724 break;
725 default:
726 config = this._editorConfig.raw;
727 break;
728 }
729 cell.updateEditorConfig({ ...config });
730 }
731 }
732 async _runOnIdleTime(remainingTime = MAXIMUM_TIME_REMAINING) {
733 const startTime = Date.now();
734 let cellIdx = 0;
735 while (Date.now() - startTime < remainingTime &&
736 cellIdx < this.cellsArray.length) {
737 const cell = this.cellsArray[cellIdx];
738 if (cell.isPlaceholder()) {
739 switch (this.notebookConfig.windowingMode) {
740 case 'defer':
741 await this._updateForDeferMode(cell, cellIdx);
742 break;
743 case 'full':
744 this._renderCSSAndJSOutputs(cell, cellIdx);
745 break;
746 }
747 }
748 cellIdx++;
749 }
750 // If the notebook is not fully rendered
751 if (cellIdx < this.cellsArray.length) {
752 // If we are defering the cell rendering and the rendered cells do
753 // not fill the viewport yet
754 if (this.notebookConfig.windowingMode === 'defer' &&
755 this.viewportNode.clientHeight < this.node.clientHeight) {
756 // Spend more time rendering cells to fill the viewport
757 await this._runOnIdleTime();
758 }
759 else {
760 this._scheduleCellRenderOnIdle();
761 }
762 }
763 else {
764 if (this._idleCallBack) {
765 window.cancelIdleCallback(this._idleCallBack);
766 this._idleCallBack = null;
767 }
768 }
769 }
770 async _updateForDeferMode(cell, cellIdx) {
771 cell.dataset.windowedListIndex = `${cellIdx}`;
772 this.layout.insertWidget(cellIdx, cell);
773 await cell.ready;
774 }
775 _renderCSSAndJSOutputs(cell, cellIdx) {
776 var _a, _b, _c;
777 // Only render cell with text/html outputs containing scripts or/and styles
778 // Note:
779 // We don't need to render JavaScript mimetype outputs because they get
780 // directly evaluate without adding DOM elements (see @jupyterlab/javascript-extension)
781 if (cell instanceof CodeCell) {
782 for (let outputIdx = 0; outputIdx < ((_b = (_a = cell.model.outputs) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); outputIdx++) {
783 const output = cell.model.outputs.get(outputIdx);
784 const html = (_c = output.data['text/html']) !== null && _c !== void 0 ? _c : '';
785 if (html.match(/(<style[^>]*>[^<]*<\/style[^>]*>|<script[^>]*>.*?<\/script[^>]*>)/gims)) {
786 this.renderCellOutputs(cellIdx);
787 break;
788 }
789 }
790 }
791 }
792 /**
793 * Apply updated notebook settings.
794 */
795 _updateNotebookConfig() {
796 // Apply scrollPastEnd setting.
797 this.toggleClass('jp-mod-scrollPastEnd', this._notebookConfig.scrollPastEnd);
798 // Control visibility of heading collapser UI
799 this.toggleClass(HEADING_COLLAPSER_VISBILITY_CONTROL_CLASS, this._notebookConfig.showHiddenCellsButton);
800 // Control editor visibility for read-only Markdown cells
801 const showEditorForReadOnlyMarkdown = this._notebookConfig.showEditorForReadOnlyMarkdown;
802 if (showEditorForReadOnlyMarkdown !== undefined) {
803 for (const cell of this.cellsArray) {
804 if (cell.model.type === 'markdown') {
805 cell.showEditorForReadOnly =
806 showEditorForReadOnlyMarkdown;
807 }
808 }
809 }
810 this.viewModel.windowingActive =
811 this._notebookConfig.windowingMode === 'full';
812 }
813}
814/**
815 * The namespace for the `StaticNotebook` class statics.
816 */
817(function (StaticNotebook) {
818 /**
819 * Default configuration options for cell editors.
820 */
821 StaticNotebook.defaultEditorConfig = {
822 code: {
823 lineNumbers: false,
824 lineWrap: false,
825 matchBrackets: true,
826 tabFocusable: false
827 },
828 markdown: {
829 lineNumbers: false,
830 lineWrap: true,
831 matchBrackets: false,
832 tabFocusable: false
833 },
834 raw: {
835 lineNumbers: false,
836 lineWrap: true,
837 matchBrackets: false,
838 tabFocusable: false
839 }
840 };
841 /**
842 * Default configuration options for notebooks.
843 */
844 StaticNotebook.defaultNotebookConfig = {
845 enableKernelInitNotification: false,
846 showHiddenCellsButton: true,
847 scrollPastEnd: true,
848 defaultCell: 'code',
849 recordTiming: false,
850 inputHistoryScope: 'global',
851 maxNumberOutputs: 50,
852 showEditorForReadOnlyMarkdown: true,
853 disableDocumentWideUndoRedo: true,
854 renderingLayout: 'default',
855 sideBySideLeftMarginOverride: '10px',
856 sideBySideRightMarginOverride: '10px',
857 sideBySideOutputRatio: 1,
858 overscanCount: 1,
859 windowingMode: 'full',
860 accessKernelHistory: false
861 };
862 /**
863 * The default implementation of an `IContentFactory`.
864 */
865 class ContentFactory extends Cell.ContentFactory {
866 /**
867 * Create a new code cell widget.
868 *
869 * #### Notes
870 * If no cell content factory is passed in with the options, the one on the
871 * notebook content factory is used.
872 */
873 createCodeCell(options) {
874 return new CodeCell(options).initializeState();
875 }
876 /**
877 * Create a new markdown cell widget.
878 *
879 * #### Notes
880 * If no cell content factory is passed in with the options, the one on the
881 * notebook content factory is used.
882 */
883 createMarkdownCell(options) {
884 return new MarkdownCell(options).initializeState();
885 }
886 /**
887 * Create a new raw cell widget.
888 *
889 * #### Notes
890 * If no cell content factory is passed in with the options, the one on the
891 * notebook content factory is used.
892 */
893 createRawCell(options) {
894 return new RawCell(options).initializeState();
895 }
896 }
897 StaticNotebook.ContentFactory = ContentFactory;
898})(StaticNotebook || (StaticNotebook = {}));
899/**
900 * A notebook widget that supports interactivity.
901 */
902export class Notebook extends StaticNotebook {
903 /**
904 * Construct a notebook widget.
905 */
906 constructor(options) {
907 super({
908 renderer: {
909 createOuter() {
910 return document.createElement('div');
911 },
912 createViewport() {
913 const el = document.createElement('div');
914 el.setAttribute('role', 'feed');
915 el.setAttribute('aria-label', 'Cells');
916 return el;
917 },
918 createScrollbar() {
919 return document.createElement('ol');
920 },
921 createScrollbarItem(notebook, index, model) {
922 const li = document.createElement('li');
923 li.appendChild(document.createTextNode(`${index + 1}`));
924 if (notebook.activeCellIndex === index) {
925 li.classList.add('jp-mod-active');
926 }
927 if (notebook.selectedCells.some(cell => model === cell.model)) {
928 li.classList.add('jp-mod-selected');
929 }
930 return li;
931 }
932 },
933 ...options
934 });
935 this._activeCellIndex = -1;
936 this._activeCell = null;
937 this._mode = 'command';
938 this._drag = null;
939 this._dragData = null;
940 this._mouseMode = null;
941 this._activeCellChanged = new Signal(this);
942 this._stateChanged = new Signal(this);
943 this._selectionChanged = new Signal(this);
944 this._checkCacheOnNextResize = false;
945 this._lastClipboardInteraction = null;
946 this._selectedCells = [];
947 // Allow the node to scroll while dragging items.
948 this.outerNode.setAttribute('data-lm-dragscroll', 'true');
949 this.activeCellChanged.connect(this._updateSelectedCells, this);
950 this.jumped.connect((_, index) => (this.activeCellIndex = index));
951 this.selectionChanged.connect(this._updateSelectedCells, this);
952 this.addFooter();
953 }
954 /**
955 * List of selected and active cells
956 */
957 get selectedCells() {
958 return this._selectedCells;
959 }
960 /**
961 * Adds a footer to the notebook.
962 */
963 addFooter() {
964 const info = new NotebookFooter(this);
965 this.layout.footer = info;
966 }
967 /**
968 * Handle a change cells event.
969 */
970 _onCellsChanged(sender, args) {
971 var _a, _b;
972 const activeCellId = (_a = this.activeCell) === null || _a === void 0 ? void 0 : _a.model.id;
973 super._onCellsChanged(sender, args);
974 if (activeCellId) {
975 const newActiveCellIndex = (_b = this.model) === null || _b === void 0 ? void 0 : _b.sharedModel.cells.findIndex(cell => cell.getId() === activeCellId);
976 if (newActiveCellIndex != null) {
977 this.activeCellIndex = newActiveCellIndex;
978 }
979 }
980 }
981 /**
982 * A signal emitted when the active cell changes.
983 *
984 * #### Notes
985 * This can be due to the active index changing or the
986 * cell at the active index changing.
987 */
988 get activeCellChanged() {
989 return this._activeCellChanged;
990 }
991 /**
992 * A signal emitted when the state of the notebook changes.
993 */
994 get stateChanged() {
995 return this._stateChanged;
996 }
997 /**
998 * A signal emitted when the selection state of the notebook changes.
999 */
1000 get selectionChanged() {
1001 return this._selectionChanged;
1002 }
1003 /**
1004 * The interactivity mode of the notebook.
1005 */
1006 get mode() {
1007 return this._mode;
1008 }
1009 set mode(newValue) {
1010 this.setMode(newValue);
1011 }
1012 /**
1013 * Set the notebook mode.
1014 *
1015 * @param newValue Notebook mode
1016 * @param options Control mode side-effect
1017 * @param options.focus Whether to ensure focus (default) or not when setting the mode.
1018 */
1019 setMode(newValue, options = {}) {
1020 var _a;
1021 const setFocus = (_a = options.focus) !== null && _a !== void 0 ? _a : true;
1022 const activeCell = this.activeCell;
1023 if (!activeCell) {
1024 newValue = 'command';
1025 }
1026 if (newValue === this._mode) {
1027 if (setFocus) {
1028 this._ensureFocus();
1029 }
1030 return;
1031 }
1032 // Post an update request.
1033 this.update();
1034 const oldValue = this._mode;
1035 this._mode = newValue;
1036 if (newValue === 'edit') {
1037 // Edit mode deselects all cells.
1038 for (const widget of this.widgets) {
1039 this.deselect(widget);
1040 }
1041 // Edit mode unrenders an active markdown widget.
1042 if (activeCell instanceof MarkdownCell) {
1043 activeCell.rendered = false;
1044 }
1045 activeCell.inputHidden = false;
1046 }
1047 else {
1048 if (setFocus) {
1049 void NotebookActions.focusActiveCell(this, {
1050 // Do not await the active cell because that creates a bug. If the user
1051 // is editing a code cell and presses Accel Shift C to open the command
1052 // palette, then the command palette opens before
1053 // activeCell.node.focus() is called, which closes the command palette.
1054 // To the end user, it looks as if all the keyboard shortcut did was
1055 // move focus from the cell editor to the cell as a whole.
1056 waitUntilReady: false,
1057 preventScroll: true
1058 });
1059 }
1060 }
1061 this._stateChanged.emit({ name: 'mode', oldValue, newValue });
1062 if (setFocus) {
1063 this._ensureFocus();
1064 }
1065 }
1066 /**
1067 * The active cell index of the notebook.
1068 *
1069 * #### Notes
1070 * The index will be clamped to the bounds of the notebook cells.
1071 */
1072 get activeCellIndex() {
1073 if (!this.model) {
1074 return -1;
1075 }
1076 return this.widgets.length ? this._activeCellIndex : -1;
1077 }
1078 set activeCellIndex(newValue) {
1079 var _a;
1080 const oldValue = this._activeCellIndex;
1081 if (!this.model || !this.widgets.length) {
1082 newValue = -1;
1083 }
1084 else {
1085 newValue = Math.max(newValue, 0);
1086 newValue = Math.min(newValue, this.widgets.length - 1);
1087 }
1088 this._activeCellIndex = newValue;
1089 const cell = (_a = this.widgets[newValue]) !== null && _a !== void 0 ? _a : null;
1090 this.layout.activeCell = cell;
1091 const cellChanged = cell !== this._activeCell;
1092 if (cellChanged) {
1093 // Post an update request.
1094 this.update();
1095 this._activeCell = cell;
1096 }
1097 if (cellChanged || newValue != oldValue) {
1098 this._activeCellChanged.emit(cell);
1099 }
1100 if (this.mode === 'edit' && cell instanceof MarkdownCell) {
1101 cell.rendered = false;
1102 }
1103 this._ensureFocus();
1104 if (newValue === oldValue) {
1105 return;
1106 }
1107 this._trimSelections();
1108 this._stateChanged.emit({ name: 'activeCellIndex', oldValue, newValue });
1109 }
1110 /**
1111 * Get the active cell widget.
1112 *
1113 * #### Notes
1114 * This is a cell or `null` if there is no active cell.
1115 */
1116 get activeCell() {
1117 return this._activeCell;
1118 }
1119 get lastClipboardInteraction() {
1120 return this._lastClipboardInteraction;
1121 }
1122 set lastClipboardInteraction(newValue) {
1123 this._lastClipboardInteraction = newValue;
1124 }
1125 /**
1126 * Dispose of the resources held by the widget.
1127 */
1128 dispose() {
1129 if (this.isDisposed) {
1130 return;
1131 }
1132 this._activeCell = null;
1133 super.dispose();
1134 }
1135 /**
1136 * Move cells preserving widget view state.
1137 *
1138 * #### Notes
1139 * This is required because at the model level a move is a deletion
1140 * followed by an insertion. Hence the view state is not preserved.
1141 *
1142 * @param from The index of the cell to move
1143 * @param to The new index of the cell
1144 * @param n Number of cells to move
1145 */
1146 moveCell(from, to, n = 1) {
1147 // Save active cell id to be restored
1148 const newActiveCellIndex = from <= this.activeCellIndex && this.activeCellIndex < from + n
1149 ? this.activeCellIndex + to - from - (from > to ? 0 : n - 1)
1150 : -1;
1151 const isSelected = this.widgets
1152 .slice(from, from + n)
1153 .map(w => this.isSelected(w));
1154 super.moveCell(from, to, n);
1155 if (newActiveCellIndex >= 0) {
1156 this.activeCellIndex = newActiveCellIndex;
1157 }
1158 if (from > to) {
1159 isSelected.forEach((selected, idx) => {
1160 if (selected) {
1161 this.select(this.widgets[to + idx]);
1162 }
1163 });
1164 }
1165 else {
1166 isSelected.forEach((selected, idx) => {
1167 if (selected) {
1168 this.select(this.widgets[to - n + 1 + idx]);
1169 }
1170 });
1171 }
1172 }
1173 /**
1174 * Select a cell widget.
1175 *
1176 * #### Notes
1177 * It is a no-op if the value does not change.
1178 * It will emit the `selectionChanged` signal.
1179 */
1180 select(widget) {
1181 if (Private.selectedProperty.get(widget)) {
1182 return;
1183 }
1184 Private.selectedProperty.set(widget, true);
1185 this._selectionChanged.emit(void 0);
1186 this.update();
1187 }
1188 /**
1189 * Deselect a cell widget.
1190 *
1191 * #### Notes
1192 * It is a no-op if the value does not change.
1193 * It will emit the `selectionChanged` signal.
1194 */
1195 deselect(widget) {
1196 if (!Private.selectedProperty.get(widget)) {
1197 return;
1198 }
1199 Private.selectedProperty.set(widget, false);
1200 this._selectionChanged.emit(void 0);
1201 this.update();
1202 }
1203 /**
1204 * Whether a cell is selected.
1205 */
1206 isSelected(widget) {
1207 return Private.selectedProperty.get(widget);
1208 }
1209 /**
1210 * Whether a cell is selected or is the active cell.
1211 */
1212 isSelectedOrActive(widget) {
1213 if (widget === this._activeCell) {
1214 return true;
1215 }
1216 return Private.selectedProperty.get(widget);
1217 }
1218 /**
1219 * Deselect all of the cells.
1220 */
1221 deselectAll() {
1222 let changed = false;
1223 for (const widget of this.widgets) {
1224 if (Private.selectedProperty.get(widget)) {
1225 changed = true;
1226 }
1227 Private.selectedProperty.set(widget, false);
1228 }
1229 if (changed) {
1230 this._selectionChanged.emit(void 0);
1231 }
1232 // Make sure we have a valid active cell.
1233 this.activeCellIndex = this.activeCellIndex; // eslint-disable-line
1234 this.update();
1235 }
1236 /**
1237 * Move the head of an existing contiguous selection to extend the selection.
1238 *
1239 * @param index - The new head of the existing selection.
1240 *
1241 * #### Notes
1242 * If there is no existing selection, the active cell is considered an
1243 * existing one-cell selection.
1244 *
1245 * If the new selection is a single cell, that cell becomes the active cell
1246 * and all cells are deselected.
1247 *
1248 * There is no change if there are no cells (i.e., activeCellIndex is -1).
1249 */
1250 extendContiguousSelectionTo(index) {
1251 let { head, anchor } = this.getContiguousSelection();
1252 let i;
1253 // Handle the case of no current selection.
1254 if (anchor === null || head === null) {
1255 if (index === this.activeCellIndex) {
1256 // Already collapsed selection, nothing more to do.
1257 return;
1258 }
1259 // We will start a new selection below.
1260 head = this.activeCellIndex;
1261 anchor = this.activeCellIndex;
1262 }
1263 // Move the active cell. We do this before the collapsing shortcut below.
1264 this.activeCellIndex = index;
1265 // Make sure the index is valid, according to the rules for setting and clipping the
1266 // active cell index. This may change the index.
1267 index = this.activeCellIndex;
1268 // Collapse the selection if it is only the active cell.
1269 if (index === anchor) {
1270 this.deselectAll();
1271 return;
1272 }
1273 let selectionChanged = false;
1274 if (head < index) {
1275 if (head < anchor) {
1276 Private.selectedProperty.set(this.widgets[head], false);
1277 selectionChanged = true;
1278 }
1279 // Toggle everything strictly between head and index except anchor.
1280 for (i = head + 1; i < index; i++) {
1281 if (i !== anchor) {
1282 Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
1283 selectionChanged = true;
1284 }
1285 }
1286 }
1287 else if (index < head) {
1288 if (anchor < head) {
1289 Private.selectedProperty.set(this.widgets[head], false);
1290 selectionChanged = true;
1291 }
1292 // Toggle everything strictly between index and head except anchor.
1293 for (i = index + 1; i < head; i++) {
1294 if (i !== anchor) {
1295 Private.selectedProperty.set(this.widgets[i], !Private.selectedProperty.get(this.widgets[i]));
1296 selectionChanged = true;
1297 }
1298 }
1299 }
1300 // Anchor and index should *always* be selected.
1301 if (!Private.selectedProperty.get(this.widgets[anchor])) {
1302 selectionChanged = true;
1303 }
1304 Private.selectedProperty.set(this.widgets[anchor], true);
1305 if (!Private.selectedProperty.get(this.widgets[index])) {
1306 selectionChanged = true;
1307 }
1308 Private.selectedProperty.set(this.widgets[index], true);
1309 if (selectionChanged) {
1310 this._selectionChanged.emit(void 0);
1311 }
1312 }
1313 /**
1314 * Get the head and anchor of a contiguous cell selection.
1315 *
1316 * The head of a contiguous selection is always the active cell.
1317 *
1318 * If there are no cells selected, `{head: null, anchor: null}` is returned.
1319 *
1320 * Throws an error if the currently selected cells do not form a contiguous
1321 * selection.
1322 */
1323 getContiguousSelection() {
1324 const cells = this.widgets;
1325 const first = ArrayExt.findFirstIndex(cells, c => this.isSelected(c));
1326 // Return early if no cells are selected.
1327 if (first === -1) {
1328 return { head: null, anchor: null };
1329 }
1330 const last = ArrayExt.findLastIndex(cells, c => this.isSelected(c), -1, first);
1331 // Check that the selection is contiguous.
1332 for (let i = first; i <= last; i++) {
1333 if (!this.isSelected(cells[i])) {
1334 throw new Error('Selection not contiguous');
1335 }
1336 }
1337 // Check that the active cell is one of the endpoints of the selection.
1338 const activeIndex = this.activeCellIndex;
1339 if (first !== activeIndex && last !== activeIndex) {
1340 throw new Error('Active cell not at endpoint of selection');
1341 }
1342 // Determine the head and anchor of the selection.
1343 if (first === activeIndex) {
1344 return { head: first, anchor: last };
1345 }
1346 else {
1347 return { head: last, anchor: first };
1348 }
1349 }
1350 /**
1351 * Scroll so that the given cell is in view. Selects and activates cell.
1352 *
1353 * @param cell - A cell in the notebook widget.
1354 * @param align - Type of alignment.
1355 *
1356 */
1357 async scrollToCell(cell, align = 'auto') {
1358 try {
1359 await this.scrollToItem(this.widgets.findIndex(c => c === cell), align);
1360 }
1361 catch (r) {
1362 //no-op
1363 }
1364 // change selection and active cell:
1365 this.deselectAll();
1366 this.select(cell);
1367 cell.activate();
1368 }
1369 _parseFragment(fragment) {
1370 const cleanedFragment = fragment.slice(1);
1371 if (!cleanedFragment) {
1372 // Bail early
1373 return;
1374 }
1375 const parts = cleanedFragment.split('=');
1376 if (parts.length === 1) {
1377 // Default to heading if no prefix is given.
1378 return {
1379 kind: 'heading',
1380 value: cleanedFragment
1381 };
1382 }
1383 return {
1384 kind: parts[0],
1385 value: parts.slice(1).join('=')
1386 };
1387 }
1388 /**
1389 * Set URI fragment identifier.
1390 */
1391 async setFragment(fragment) {
1392 const parsedFragment = this._parseFragment(fragment);
1393 if (!parsedFragment) {
1394 // Bail early
1395 return;
1396 }
1397 let result;
1398 switch (parsedFragment.kind) {
1399 case 'heading':
1400 result = await this._findHeading(parsedFragment.value);
1401 break;
1402 case 'cell-id':
1403 result = this._findCellById(parsedFragment.value);
1404 break;
1405 default:
1406 console.warn(`Unknown target type for URI fragment ${fragment}, interpreting as a heading`);
1407 result = await this._findHeading(parsedFragment.kind + '=' + parsedFragment.value);
1408 break;
1409 }
1410 if (result == null) {
1411 return;
1412 }
1413 let { cell, element } = result;
1414 if (!cell.inViewport) {
1415 await this.scrollToCell(cell, 'center');
1416 }
1417 if (element == null) {
1418 element = cell.node;
1419 }
1420 const widgetBox = this.node.getBoundingClientRect();
1421 const elementBox = element.getBoundingClientRect();
1422 if (elementBox.top > widgetBox.bottom ||
1423 elementBox.bottom < widgetBox.top) {
1424 element.scrollIntoView({ block: 'center' });
1425 }
1426 }
1427 /**
1428 * Handle the DOM events for the widget.
1429 *
1430 * @param event - The DOM event sent to the widget.
1431 *
1432 * #### Notes
1433 * This method implements the DOM `EventListener` interface and is
1434 * called in response to events on the notebook panel's node. It should
1435 * not be called directly by user code.
1436 */
1437 handleEvent(event) {
1438 if (!this.model) {
1439 return;
1440 }
1441 switch (event.type) {
1442 case 'contextmenu':
1443 if (event.eventPhase === Event.CAPTURING_PHASE) {
1444 this._evtContextMenuCapture(event);
1445 }
1446 break;
1447 case 'mousedown':
1448 if (event.eventPhase === Event.CAPTURING_PHASE) {
1449 this._evtMouseDownCapture(event);
1450 }
1451 else {
1452 // Skip processing the event when it resulted from a toolbar button click
1453 if (!event.defaultPrevented) {
1454 this._evtMouseDown(event);
1455 }
1456 }
1457 break;
1458 case 'mouseup':
1459 if (event.currentTarget === document) {
1460 this._evtDocumentMouseup(event);
1461 }
1462 break;
1463 case 'mousemove':
1464 if (event.currentTarget === document) {
1465 this._evtDocumentMousemove(event);
1466 }
1467 break;
1468 case 'keydown':
1469 // This works because CodeMirror does not stop the event propagation
1470 this._ensureFocus(true);
1471 break;
1472 case 'dblclick':
1473 this._evtDblClick(event);
1474 break;
1475 case 'focusin':
1476 this._evtFocusIn(event);
1477 break;
1478 case 'focusout':
1479 this._evtFocusOut(event);
1480 break;
1481 case 'lm-dragenter':
1482 this._evtDragEnter(event);
1483 break;
1484 case 'lm-dragleave':
1485 this._evtDragLeave(event);
1486 break;
1487 case 'lm-dragover':
1488 this._evtDragOver(event);
1489 break;
1490 case 'lm-drop':
1491 this._evtDrop(event);
1492 break;
1493 default:
1494 super.handleEvent(event);
1495 break;
1496 }
1497 }
1498 /**
1499 * Handle `after-attach` messages for the widget.
1500 */
1501 onAfterAttach(msg) {
1502 super.onAfterAttach(msg);
1503 const node = this.node;
1504 node.addEventListener('contextmenu', this, true);
1505 node.addEventListener('mousedown', this, true);
1506 node.addEventListener('mousedown', this);
1507 node.addEventListener('keydown', this);
1508 node.addEventListener('dblclick', this);
1509 node.addEventListener('focusin', this);
1510 node.addEventListener('focusout', this);
1511 // Capture drag events for the notebook widget
1512 // in order to preempt the drag/drop handlers in the
1513 // code editor widgets, which can take text data.
1514 node.addEventListener('lm-dragenter', this, true);
1515 node.addEventListener('lm-dragleave', this, true);
1516 node.addEventListener('lm-dragover', this, true);
1517 node.addEventListener('lm-drop', this, true);
1518 }
1519 /**
1520 * Handle `before-detach` messages for the widget.
1521 */
1522 onBeforeDetach(msg) {
1523 const node = this.node;
1524 node.removeEventListener('contextmenu', this, true);
1525 node.removeEventListener('mousedown', this, true);
1526 node.removeEventListener('mousedown', this);
1527 node.removeEventListener('keydown', this);
1528 node.removeEventListener('dblclick', this);
1529 node.removeEventListener('focusin', this);
1530 node.removeEventListener('focusout', this);
1531 node.removeEventListener('lm-dragenter', this, true);
1532 node.removeEventListener('lm-dragleave', this, true);
1533 node.removeEventListener('lm-dragover', this, true);
1534 node.removeEventListener('lm-drop', this, true);
1535 document.removeEventListener('mousemove', this, true);
1536 document.removeEventListener('mouseup', this, true);
1537 super.onBeforeAttach(msg);
1538 }
1539 /**
1540 * A message handler invoked on an `'after-show'` message.
1541 */
1542 onAfterShow(msg) {
1543 super.onAfterShow(msg);
1544 this._checkCacheOnNextResize = true;
1545 }
1546 /**
1547 * A message handler invoked on a `'resize'` message.
1548 */
1549 onResize(msg) {
1550 var _a;
1551 // TODO
1552 if (!this._checkCacheOnNextResize) {
1553 return super.onResize(msg);
1554 }
1555 super.onResize(msg);
1556 this._checkCacheOnNextResize = false;
1557 const cache = this._cellLayoutStateCache;
1558 const width = parseInt(this.node.style.width, 10);
1559 if (cache) {
1560 if (width === cache.width) {
1561 // Cache identical, do nothing
1562 return;
1563 }
1564 }
1565 // Update cache
1566 this._cellLayoutStateCache = { width };
1567 // Fallback:
1568 for (const w of this.widgets) {
1569 if (w instanceof Cell && w.inViewport) {
1570 (_a = w.editorWidget) === null || _a === void 0 ? void 0 : _a.update();
1571 }
1572 }
1573 }
1574 /**
1575 * A message handler invoked on an `'before-hide'` message.
1576 */
1577 onBeforeHide(msg) {
1578 super.onBeforeHide(msg);
1579 // Update cache
1580 const width = parseInt(this.node.style.width, 10);
1581 this._cellLayoutStateCache = { width };
1582 }
1583 /**
1584 * Handle `'activate-request'` messages.
1585 */
1586 onActivateRequest(msg) {
1587 super.onActivateRequest(msg);
1588 this._ensureFocus(true);
1589 }
1590 /**
1591 * Handle `update-request` messages sent to the widget.
1592 */
1593 onUpdateRequest(msg) {
1594 super.onUpdateRequest(msg);
1595 const activeCell = this.activeCell;
1596 // Set the appropriate classes on the cells.
1597 if (this.mode === 'edit') {
1598 this.addClass(EDIT_CLASS);
1599 this.removeClass(COMMAND_CLASS);
1600 }
1601 else {
1602 this.addClass(COMMAND_CLASS);
1603 this.removeClass(EDIT_CLASS);
1604 }
1605 let count = 0;
1606 for (const widget of this.widgets) {
1607 // Set tabIndex to -1 to allow calling .focus() on cell without allowing
1608 // focus via tab key. This allows focus (document.activeElement) to move
1609 // up and down the document, cell by cell, when the user presses J/K or
1610 // ArrowDown/ArrowUp, but (unlike tabIndex = 0) does not add the notebook
1611 // cells (which could be numerous) to the set of nodes that the user would
1612 // have to visit when pressing the tab key to move about the UI.
1613 widget.node.tabIndex = -1;
1614 widget.removeClass(ACTIVE_CLASS);
1615 widget.removeClass(OTHER_SELECTED_CLASS);
1616 if (this.isSelectedOrActive(widget)) {
1617 widget.addClass(SELECTED_CLASS);
1618 count++;
1619 }
1620 else {
1621 widget.removeClass(SELECTED_CLASS);
1622 }
1623 }
1624 if (activeCell) {
1625 activeCell.addClass(ACTIVE_CLASS);
1626 activeCell.addClass(SELECTED_CLASS);
1627 // Set tab index to 0 on the active cell so that if the user tabs away from
1628 // the notebook then tabs back, they will return to the cell where they
1629 // left off.
1630 activeCell.node.tabIndex = 0;
1631 if (count > 1) {
1632 activeCell.addClass(OTHER_SELECTED_CLASS);
1633 }
1634 }
1635 }
1636 /**
1637 * Handle a cell being inserted.
1638 */
1639 onCellInserted(index, cell) {
1640 void cell.ready.then(() => {
1641 if (!cell.isDisposed) {
1642 cell.editor.edgeRequested.connect(this._onEdgeRequest, this);
1643 }
1644 });
1645 // If the insertion happened above, increment the active cell
1646 // index, otherwise it stays the same.
1647 this.activeCellIndex =
1648 index <= this.activeCellIndex
1649 ? this.activeCellIndex + 1
1650 : this.activeCellIndex;
1651 }
1652 /**
1653 * Handle a cell being removed.
1654 */
1655 onCellRemoved(index, cell) {
1656 // If the removal happened above, decrement the active
1657 // cell index, otherwise it stays the same.
1658 this.activeCellIndex =
1659 index <= this.activeCellIndex
1660 ? this.activeCellIndex - 1
1661 : this.activeCellIndex;
1662 if (this.isSelected(cell)) {
1663 this._selectionChanged.emit(void 0);
1664 }
1665 }
1666 /**
1667 * Handle a new model.
1668 */
1669 onModelChanged(oldValue, newValue) {
1670 super.onModelChanged(oldValue, newValue);
1671 // Try to set the active cell index to 0.
1672 // It will be set to `-1` if there is no new model or the model is empty.
1673 this.activeCellIndex = 0;
1674 }
1675 /**
1676 * Handle edge request signals from cells.
1677 */
1678 _onEdgeRequest(editor, location) {
1679 const prev = this.activeCellIndex;
1680 if (location === 'top') {
1681 this.activeCellIndex--;
1682 // Move the cursor to the first position on the last line.
1683 if (this.activeCellIndex < prev) {
1684 const editor = this.activeCell.editor;
1685 if (editor) {
1686 const lastLine = editor.lineCount - 1;
1687 editor.setCursorPosition({ line: lastLine, column: 0 });
1688 }
1689 }
1690 }
1691 else if (location === 'bottom') {
1692 this.activeCellIndex++;
1693 // Move the cursor to the first character.
1694 if (this.activeCellIndex > prev) {
1695 const editor = this.activeCell.editor;
1696 if (editor) {
1697 editor.setCursorPosition({ line: 0, column: 0 });
1698 }
1699 }
1700 }
1701 this.mode = 'edit';
1702 }
1703 /**
1704 * Ensure that the notebook has proper focus.
1705 */
1706 _ensureFocus(force = false) {
1707 var _a, _b;
1708 // No-op is the footer has the focus.
1709 const footer = this.layout.footer;
1710 if (footer && document.activeElement === footer.node) {
1711 return;
1712 }
1713 const activeCell = this.activeCell;
1714 if (this.mode === 'edit' && activeCell) {
1715 // Test for !== true to cover hasFocus is false and editor is not yet rendered.
1716 if (((_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.hasFocus()) !== true || !activeCell.inViewport) {
1717 if (activeCell.inViewport) {
1718 (_b = activeCell.editor) === null || _b === void 0 ? void 0 : _b.focus();
1719 }
1720 else {
1721 this.scrollToItem(this.activeCellIndex)
1722 .then(() => {
1723 void activeCell.ready.then(() => {
1724 var _a;
1725 (_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.focus();
1726 });
1727 })
1728 .catch(reason => {
1729 // no-op
1730 });
1731 }
1732 }
1733 }
1734 if (force &&
1735 activeCell &&
1736 !activeCell.node.contains(document.activeElement)) {
1737 void NotebookActions.focusActiveCell(this, {
1738 preventScroll: true
1739 });
1740 }
1741 }
1742 /**
1743 * Find the cell index containing the target html element.
1744 *
1745 * #### Notes
1746 * Returns -1 if the cell is not found.
1747 */
1748 _findCell(node) {
1749 // Trace up the DOM hierarchy to find the root cell node.
1750 // Then find the corresponding child and select it.
1751 let n = node;
1752 while (n && n !== this.node) {
1753 if (n.classList.contains(NB_CELL_CLASS)) {
1754 const i = ArrayExt.findFirstIndex(this.widgets, widget => widget.node === n);
1755 if (i !== -1) {
1756 return i;
1757 }
1758 break;
1759 }
1760 n = n.parentElement;
1761 }
1762 return -1;
1763 }
1764 /**
1765 * Find the target of html mouse event and cell index containing this target.
1766 *
1767 * #### Notes
1768 * Returned index is -1 if the cell is not found.
1769 */
1770 _findEventTargetAndCell(event) {
1771 let target = event.target;
1772 let index = this._findCell(target);
1773 if (index === -1) {
1774 // `event.target` sometimes gives an orphaned node in Firefox 57, which
1775 // can have `null` anywhere in its parent line. If we fail to find a cell
1776 // using `event.target`, try again using a target reconstructed from the
1777 // position of the click event.
1778 target = document.elementFromPoint(event.clientX, event.clientY);
1779 index = this._findCell(target);
1780 }
1781 return [target, index];
1782 }
1783 /**
1784 * Find heading with given ID in any of the cells.
1785 */
1786 async _findHeading(queryId) {
1787 // Loop on cells, get headings and search for first matching id.
1788 for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
1789 const cell = this.widgets[cellIdx];
1790 if (cell.model.type === 'raw' ||
1791 (cell.model.type === 'markdown' && !cell.rendered)) {
1792 // Bail early
1793 continue;
1794 }
1795 for (const heading of cell.headings) {
1796 let id = '';
1797 switch (heading.type) {
1798 case Cell.HeadingType.HTML:
1799 id = heading.id;
1800 break;
1801 case Cell.HeadingType.Markdown:
1802 {
1803 const mdHeading = heading;
1804 id = await TableOfContentsUtils.Markdown.getHeadingId(this.rendermime.markdownParser, mdHeading.raw, mdHeading.level, this.rendermime.sanitizer);
1805 }
1806 break;
1807 }
1808 if (id === queryId) {
1809 const element = this.node.querySelector(`h${heading.level}[id="${CSS.escape(id)}"]`);
1810 return {
1811 cell,
1812 element
1813 };
1814 }
1815 }
1816 }
1817 return null;
1818 }
1819 /**
1820 * Find cell by its unique ID.
1821 */
1822 _findCellById(queryId) {
1823 for (let cellIdx = 0; cellIdx < this.widgets.length; cellIdx++) {
1824 const cell = this.widgets[cellIdx];
1825 if (cell.model.id === queryId) {
1826 return {
1827 cell
1828 };
1829 }
1830 }
1831 return null;
1832 }
1833 /**
1834 * Handle `contextmenu` event.
1835 */
1836 _evtContextMenuCapture(event) {
1837 var _a;
1838 // Allow the event to propagate un-modified if the user
1839 // is holding the shift-key (and probably requesting
1840 // the native context menu).
1841 if (event.shiftKey) {
1842 return;
1843 }
1844 const [target, index] = this._findEventTargetAndCell(event);
1845 const widget = this.widgets[index];
1846 if (widget && ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
1847 // Prevent CodeMirror from focusing the editor.
1848 // TODO: find an editor-agnostic solution.
1849 event.preventDefault();
1850 }
1851 }
1852 /**
1853 * Handle `mousedown` event in the capture phase for the widget.
1854 */
1855 _evtMouseDownCapture(event) {
1856 var _a;
1857 const { button, shiftKey } = event;
1858 const [target, index] = this._findEventTargetAndCell(event);
1859 const widget = this.widgets[index];
1860 // On OS X, the context menu may be triggered with ctrl-left-click. In
1861 // Firefox, ctrl-left-click gives an event with button 2, but in Chrome,
1862 // ctrl-left-click gives an event with button 0 with the ctrl modifier.
1863 if (button === 2 &&
1864 !shiftKey &&
1865 widget &&
1866 ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target))) {
1867 this.mode = 'command';
1868 // Prevent CodeMirror from focusing the editor.
1869 // TODO: find an editor-agnostic solution.
1870 event.preventDefault();
1871 }
1872 }
1873 /**
1874 * Handle `mousedown` events for the widget.
1875 */
1876 _evtMouseDown(event) {
1877 var _a, _b, _c;
1878 const { button, shiftKey } = event;
1879 // We only handle main or secondary button actions.
1880 if (!(button === 0 || button === 2)) {
1881 return;
1882 }
1883 // Shift right-click gives the browser default behavior.
1884 if (shiftKey && button === 2) {
1885 return;
1886 }
1887 const [target, index] = this._findEventTargetAndCell(event);
1888 const widget = this.widgets[index];
1889 let targetArea;
1890 if (widget) {
1891 if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(target)) {
1892 targetArea = 'input';
1893 }
1894 else if ((_b = widget.promptNode) === null || _b === void 0 ? void 0 : _b.contains(target)) {
1895 targetArea = 'prompt';
1896 }
1897 else {
1898 targetArea = 'cell';
1899 }
1900 }
1901 else {
1902 targetArea = 'notebook';
1903 }
1904 // Make sure we go to command mode if the click isn't in the cell editor If
1905 // we do click in the cell editor, the editor handles the focus event to
1906 // switch to edit mode.
1907 if (targetArea !== 'input') {
1908 this.mode = 'command';
1909 }
1910 if (targetArea === 'notebook') {
1911 this.deselectAll();
1912 }
1913 else if (targetArea === 'prompt' || targetArea === 'cell') {
1914 // We don't want to prevent the default selection behavior
1915 // if there is currently text selected in an output.
1916 const hasSelection = ((_c = window.getSelection()) !== null && _c !== void 0 ? _c : '').toString() !== '';
1917 if (button === 0 &&
1918 shiftKey &&
1919 !hasSelection &&
1920 !['INPUT', 'OPTION'].includes(target.tagName)) {
1921 // Prevent browser selecting text in prompt or output
1922 event.preventDefault();
1923 // Shift-click - extend selection
1924 try {
1925 this.extendContiguousSelectionTo(index);
1926 }
1927 catch (e) {
1928 console.error(e);
1929 this.deselectAll();
1930 return;
1931 }
1932 // Enter selecting mode
1933 this._mouseMode = 'select';
1934 document.addEventListener('mouseup', this, true);
1935 document.addEventListener('mousemove', this, true);
1936 }
1937 else if (button === 0 && !shiftKey) {
1938 // Prepare to start a drag if we are on the drag region.
1939 if (targetArea === 'prompt') {
1940 // Prepare for a drag start
1941 this._dragData = {
1942 pressX: event.clientX,
1943 pressY: event.clientY,
1944 index: index
1945 };
1946 // Enter possible drag mode
1947 this._mouseMode = 'couldDrag';
1948 document.addEventListener('mouseup', this, true);
1949 document.addEventListener('mousemove', this, true);
1950 event.preventDefault();
1951 }
1952 if (!this.isSelectedOrActive(widget)) {
1953 this.deselectAll();
1954 this.activeCellIndex = index;
1955 }
1956 }
1957 else if (button === 2) {
1958 if (!this.isSelectedOrActive(widget)) {
1959 this.deselectAll();
1960 this.activeCellIndex = index;
1961 }
1962 event.preventDefault();
1963 }
1964 }
1965 else if (targetArea === 'input') {
1966 if (button === 2 && !this.isSelectedOrActive(widget)) {
1967 this.deselectAll();
1968 this.activeCellIndex = index;
1969 }
1970 }
1971 // If we didn't set focus above, make sure we get focus now.
1972 this._ensureFocus(true);
1973 }
1974 /**
1975 * Handle the `'mouseup'` event on the document.
1976 */
1977 _evtDocumentMouseup(event) {
1978 event.preventDefault();
1979 event.stopPropagation();
1980 // Remove the event listeners we put on the document
1981 document.removeEventListener('mousemove', this, true);
1982 document.removeEventListener('mouseup', this, true);
1983 if (this._mouseMode === 'couldDrag') {
1984 // We didn't end up dragging if we are here, so treat it as a click event.
1985 const [, index] = this._findEventTargetAndCell(event);
1986 this.deselectAll();
1987 this.activeCellIndex = index;
1988 // Focus notebook if active cell changes but does not have focus.
1989 if (!this.activeCell.node.contains(document.activeElement)) {
1990 void NotebookActions.focusActiveCell(this);
1991 }
1992 }
1993 this._mouseMode = null;
1994 }
1995 /**
1996 * Handle the `'mousemove'` event for the widget.
1997 */
1998 _evtDocumentMousemove(event) {
1999 event.preventDefault();
2000 event.stopPropagation();
2001 // If in select mode, update the selection
2002 switch (this._mouseMode) {
2003 case 'select': {
2004 const target = event.target;
2005 const index = this._findCell(target);
2006 if (index !== -1) {
2007 this.extendContiguousSelectionTo(index);
2008 }
2009 break;
2010 }
2011 case 'couldDrag': {
2012 // Check for a drag initialization.
2013 const data = this._dragData;
2014 const dx = Math.abs(event.clientX - data.pressX);
2015 const dy = Math.abs(event.clientY - data.pressY);
2016 if (dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD) {
2017 this._mouseMode = null;
2018 this._startDrag(data.index, event.clientX, event.clientY);
2019 }
2020 break;
2021 }
2022 default:
2023 break;
2024 }
2025 }
2026 /**
2027 * Handle the `'lm-dragenter'` event for the widget.
2028 */
2029 _evtDragEnter(event) {
2030 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
2031 return;
2032 }
2033 event.preventDefault();
2034 event.stopPropagation();
2035 const target = event.target;
2036 const index = this._findCell(target);
2037 if (index === -1) {
2038 return;
2039 }
2040 const widget = this.cellsArray[index];
2041 widget.node.classList.add(DROP_TARGET_CLASS);
2042 }
2043 /**
2044 * Handle the `'lm-dragleave'` event for the widget.
2045 */
2046 _evtDragLeave(event) {
2047 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
2048 return;
2049 }
2050 event.preventDefault();
2051 event.stopPropagation();
2052 const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
2053 if (elements.length) {
2054 elements[0].classList.remove(DROP_TARGET_CLASS);
2055 }
2056 }
2057 /**
2058 * Handle the `'lm-dragover'` event for the widget.
2059 */
2060 _evtDragOver(event) {
2061 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
2062 return;
2063 }
2064 event.preventDefault();
2065 event.stopPropagation();
2066 event.dropAction = event.proposedAction;
2067 const elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
2068 if (elements.length) {
2069 elements[0].classList.remove(DROP_TARGET_CLASS);
2070 }
2071 const target = event.target;
2072 const index = this._findCell(target);
2073 if (index === -1) {
2074 return;
2075 }
2076 const widget = this.cellsArray[index];
2077 widget.node.classList.add(DROP_TARGET_CLASS);
2078 }
2079 /**
2080 * Handle the `'lm-drop'` event for the widget.
2081 */
2082 _evtDrop(event) {
2083 if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
2084 return;
2085 }
2086 event.preventDefault();
2087 event.stopPropagation();
2088 if (event.proposedAction === 'none') {
2089 event.dropAction = 'none';
2090 return;
2091 }
2092 let target = event.target;
2093 while (target && target.parentElement) {
2094 if (target.classList.contains(DROP_TARGET_CLASS)) {
2095 target.classList.remove(DROP_TARGET_CLASS);
2096 break;
2097 }
2098 target = target.parentElement;
2099 }
2100 // Model presence should be checked before calling event handlers
2101 const model = this.model;
2102 const source = event.source;
2103 if (source === this) {
2104 // Handle the case where we are moving cells within
2105 // the same notebook.
2106 event.dropAction = 'move';
2107 const toMove = event.mimeData.getData('internal:cells');
2108 // For collapsed markdown headings with hidden "child" cells, move all
2109 // child cells as well as the markdown heading.
2110 const cell = toMove[toMove.length - 1];
2111 if (cell instanceof MarkdownCell && cell.headingCollapsed) {
2112 const nextParent = NotebookActions.findNextParentHeading(cell, source);
2113 if (nextParent > 0) {
2114 const index = findIndex(source.widgets, (possibleCell) => {
2115 return cell.model.id === possibleCell.model.id;
2116 });
2117 toMove.push(...source.widgets.slice(index + 1, nextParent));
2118 }
2119 }
2120 // Compute the to/from indices for the move.
2121 let fromIndex = ArrayExt.firstIndexOf(this.widgets, toMove[0]);
2122 let toIndex = this._findCell(target);
2123 // This check is needed for consistency with the view.
2124 if (toIndex !== -1 && toIndex > fromIndex) {
2125 toIndex -= 1;
2126 }
2127 else if (toIndex === -1) {
2128 // If the drop is within the notebook but not on any cell,
2129 // most often this means it is past the cell areas, so
2130 // set it to move the cells to the end of the notebook.
2131 toIndex = this.widgets.length - 1;
2132 }
2133 // Don't move if we are within the block of selected cells.
2134 if (toIndex >= fromIndex && toIndex < fromIndex + toMove.length) {
2135 return;
2136 }
2137 // Move the cells one by one
2138 this.moveCell(fromIndex, toIndex, toMove.length);
2139 }
2140 else {
2141 // Handle the case where we are copying cells between
2142 // notebooks.
2143 event.dropAction = 'copy';
2144 // Find the target cell and insert the copied cells.
2145 let index = this._findCell(target);
2146 if (index === -1) {
2147 index = this.widgets.length;
2148 }
2149 const start = index;
2150 const values = event.mimeData.getData(JUPYTER_CELL_MIME);
2151 // Insert the copies of the original cells.
2152 // We preserve trust status of pasted cells by not modifying metadata.
2153 model.sharedModel.insertCells(index, values);
2154 // Select the inserted cells.
2155 this.deselectAll();
2156 this.activeCellIndex = start;
2157 this.extendContiguousSelectionTo(index - 1);
2158 }
2159 void NotebookActions.focusActiveCell(this);
2160 }
2161 /**
2162 * Start a drag event.
2163 */
2164 _startDrag(index, clientX, clientY) {
2165 var _a;
2166 const cells = this.model.cells;
2167 const selected = [];
2168 const toMove = [];
2169 let i = -1;
2170 for (const widget of this.widgets) {
2171 const cell = cells.get(++i);
2172 if (this.isSelectedOrActive(widget)) {
2173 widget.addClass(DROP_SOURCE_CLASS);
2174 selected.push(cell.toJSON());
2175 toMove.push(widget);
2176 }
2177 }
2178 const activeCell = this.activeCell;
2179 let dragImage = null;
2180 let countString;
2181 if ((activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.type) === 'code') {
2182 const executionCount = activeCell.model
2183 .executionCount;
2184 countString = ' ';
2185 if (executionCount) {
2186 countString = executionCount.toString();
2187 }
2188 }
2189 else {
2190 countString = '';
2191 }
2192 // Create the drag image.
2193 dragImage = Private.createDragImage(selected.length, countString, (_a = activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.sharedModel.getSource().split('\n')[0].slice(0, 26)) !== null && _a !== void 0 ? _a : '');
2194 // Set up the drag event.
2195 this._drag = new Drag({
2196 mimeData: new MimeData(),
2197 dragImage,
2198 supportedActions: 'copy-move',
2199 proposedAction: 'copy',
2200 source: this
2201 });
2202 this._drag.mimeData.setData(JUPYTER_CELL_MIME, selected);
2203 // Add mimeData for the fully reified cell widgets, for the
2204 // case where the target is in the same notebook and we
2205 // can just move the cells.
2206 this._drag.mimeData.setData('internal:cells', toMove);
2207 // Add mimeData for the text content of the selected cells,
2208 // allowing for drag/drop into plain text fields.
2209 const textContent = toMove
2210 .map(cell => cell.model.sharedModel.getSource())
2211 .join('\n');
2212 this._drag.mimeData.setData('text/plain', textContent);
2213 // Remove mousemove and mouseup listeners and start the drag.
2214 document.removeEventListener('mousemove', this, true);
2215 document.removeEventListener('mouseup', this, true);
2216 this._mouseMode = null;
2217 void this._drag.start(clientX, clientY).then(action => {
2218 if (this.isDisposed) {
2219 return;
2220 }
2221 this._drag = null;
2222 for (const widget of toMove) {
2223 widget.removeClass(DROP_SOURCE_CLASS);
2224 }
2225 });
2226 }
2227 /**
2228 * Update the notebook node with class indicating read-write state.
2229 */
2230 _updateReadWrite() {
2231 const inReadWrite = DOMUtils.hasActiveEditableElement(this.node);
2232 this.node.classList.toggle(READ_WRITE_CLASS, inReadWrite);
2233 }
2234 /**
2235 * Handle `focus` events for the widget.
2236 */
2237 _evtFocusIn(event) {
2238 var _a, _b;
2239 // Update read-write class state.
2240 this._updateReadWrite();
2241 const target = event.target;
2242 const index = this._findCell(target);
2243 if (index !== -1) {
2244 const widget = this.widgets[index];
2245 // If the editor itself does not have focus, ensure command mode.
2246 if (widget.editorWidget && !widget.editorWidget.node.contains(target)) {
2247 this.setMode('command', { focus: false });
2248 }
2249 // Cell index needs to be updated before changing mode,
2250 // otherwise the previous cell may get un-rendered.
2251 this.activeCellIndex = index;
2252 // If the editor has focus, ensure edit mode.
2253 const node = (_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node;
2254 if (node === null || node === void 0 ? void 0 : node.contains(target)) {
2255 this.setMode('edit', { focus: false });
2256 }
2257 }
2258 else {
2259 // No cell has focus, ensure command mode.
2260 this.setMode('command', { focus: false });
2261 // Prevents the parent element to get the focus.
2262 event.preventDefault();
2263 // Check if the focus was previously in the active cell to avoid focus looping
2264 // between the cell and the cell toolbar.
2265 const source = event.relatedTarget;
2266 // Focuses on the active cell if the focus did not come from it.
2267 // Otherwise focus on the footer element (add cell button).
2268 if (this._activeCell && !this._activeCell.node.contains(source)) {
2269 this._activeCell.ready
2270 .then(() => {
2271 var _a;
2272 (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.node.focus({
2273 preventScroll: true
2274 });
2275 })
2276 .catch(() => {
2277 var _a;
2278 (_a = this.layout.footer) === null || _a === void 0 ? void 0 : _a.node.focus({
2279 preventScroll: true
2280 });
2281 });
2282 }
2283 else {
2284 (_b = this.layout.footer) === null || _b === void 0 ? void 0 : _b.node.focus({
2285 preventScroll: true
2286 });
2287 }
2288 }
2289 }
2290 /**
2291 * Handle `focusout` events for the notebook.
2292 */
2293 _evtFocusOut(event) {
2294 var _a;
2295 // Update read-write class state.
2296 this._updateReadWrite();
2297 const relatedTarget = event.relatedTarget;
2298 // Bail if the window is losing focus, to preserve edit mode. This test
2299 // assumes that we explicitly focus things rather than calling blur()
2300 if (!relatedTarget) {
2301 return;
2302 }
2303 // Bail if the item gaining focus is another cell,
2304 // and we should not be entering command mode.
2305 const index = this._findCell(relatedTarget);
2306 if (index !== -1) {
2307 const widget = this.widgets[index];
2308 if ((_a = widget.editorWidget) === null || _a === void 0 ? void 0 : _a.node.contains(relatedTarget)) {
2309 return;
2310 }
2311 }
2312 // Otherwise enter command mode if not already.
2313 if (this.mode !== 'command') {
2314 this.setMode('command', { focus: false });
2315 }
2316 }
2317 /**
2318 * Handle `dblclick` events for the widget.
2319 */
2320 _evtDblClick(event) {
2321 const model = this.model;
2322 if (!model) {
2323 return;
2324 }
2325 this.deselectAll();
2326 const [target, index] = this._findEventTargetAndCell(event);
2327 if (event.target.classList.contains(HEADING_COLLAPSER_CLASS)) {
2328 return;
2329 }
2330 if (index === -1) {
2331 return;
2332 }
2333 this.activeCellIndex = index;
2334 if (model.cells.get(index).type === 'markdown') {
2335 const widget = this.widgets[index];
2336 widget.rendered = false;
2337 }
2338 else if (target.localName === 'img') {
2339 target.classList.toggle(UNCONFINED_CLASS);
2340 }
2341 }
2342 /**
2343 * Remove selections from inactive cells to avoid
2344 * spurious cursors.
2345 */
2346 _trimSelections() {
2347 for (let i = 0; i < this.widgets.length; i++) {
2348 if (i !== this._activeCellIndex) {
2349 const cell = this.widgets[i];
2350 if (!cell.model.isDisposed && cell.editor) {
2351 cell.model.selections.delete(cell.editor.uuid);
2352 }
2353 }
2354 }
2355 }
2356 _updateSelectedCells() {
2357 this._selectedCells = this.widgets.filter(cell => this.isSelectedOrActive(cell));
2358 if (this.kernelHistory) {
2359 this.kernelHistory.reset();
2360 }
2361 }
2362}
2363/**
2364 * The namespace for the `Notebook` class statics.
2365 */
2366(function (Notebook) {
2367 /**
2368 * The default implementation of a notebook content factory..
2369 *
2370 * #### Notes
2371 * Override methods on this class to customize the default notebook factory
2372 * methods that create notebook content.
2373 */
2374 class ContentFactory extends StaticNotebook.ContentFactory {
2375 }
2376 Notebook.ContentFactory = ContentFactory;
2377})(Notebook || (Notebook = {}));
2378/**
2379 * A namespace for private data.
2380 */
2381var Private;
2382(function (Private) {
2383 /**
2384 * An attached property for the selected state of a cell.
2385 */
2386 Private.selectedProperty = new AttachedProperty({
2387 name: 'selected',
2388 create: () => false
2389 });
2390 /**
2391 * A custom panel layout for the notebook.
2392 */
2393 class NotebookPanelLayout extends PanelLayout {
2394 /**
2395 * A message handler invoked on an `'update-request'` message.
2396 *
2397 * #### Notes
2398 * This is a reimplementation of the base class method,
2399 * and is a no-op.
2400 */
2401 onUpdateRequest(msg) {
2402 // This is a no-op.
2403 }
2404 }
2405 Private.NotebookPanelLayout = NotebookPanelLayout;
2406 /**
2407 * Create a cell drag image.
2408 */
2409 function createDragImage(count, promptNumber, cellContent) {
2410 if (count > 1) {
2411 if (promptNumber !== '') {
2412 return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
2413 }
2414 else {
2415 return VirtualDOM.realize(h.div(h.div({ className: DRAG_IMAGE_CLASS }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)), h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')));
2416 }
2417 }
2418 else {
2419 if (promptNumber !== '') {
2420 return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }, '[' + promptNumber + ']:'), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
2421 }
2422 else {
2423 return VirtualDOM.realize(h.div(h.div({ className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` }, h.span({ className: CELL_DRAG_PROMPT_CLASS }), h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent))));
2424 }
2425 }
2426 }
2427 Private.createDragImage = createDragImage;
2428})(Private || (Private = {}));
2429//# sourceMappingURL=widget.js.map
\No newline at end of file