Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50654 views
1
//----------------------------------------------------------------------------
2
// Copyright (C) 2011 The IPython Development Team
3
//
4
// Distributed under the terms of the BSD License. The full license is in
5
// the file COPYING, distributed as part of this software.
6
//----------------------------------------------------------------------------
7
8
//============================================================================
9
// Notebook
10
//============================================================================
11
12
var IPython = (function (IPython) {
13
"use strict";
14
15
var utils = IPython.utils;
16
17
/**
18
* A notebook contains and manages cells.
19
*
20
* @class Notebook
21
* @constructor
22
* @param {String} selector A jQuery selector for the notebook's DOM element
23
* @param {Object} [options] A config object
24
*/
25
var Notebook = function (selector, options) {
26
this.options = options = options || {};
27
this.base_url = options.base_url;
28
this.notebook_path = options.notebook_path;
29
this.notebook_name = options.notebook_name;
30
this.element = $(selector);
31
this.element.scroll();
32
this.element.data("notebook", this);
33
this.next_prompt_number = 1;
34
this.session = null;
35
this.kernel = null;
36
this.clipboard = null;
37
this.undelete_backup = null;
38
this.undelete_index = null;
39
this.undelete_below = false;
40
this.paste_enabled = false;
41
// It is important to start out in command mode to match the intial mode
42
// of the KeyboardManager.
43
this.mode = 'command';
44
this.set_dirty(false);
45
this.metadata = {};
46
this._checkpoint_after_save = false;
47
this.last_checkpoint = null;
48
this.checkpoints = [];
49
this.autosave_interval = 0;
50
this.autosave_timer = null;
51
// autosave *at most* every two minutes
52
this.minimum_autosave_interval = 120000;
53
// single worksheet for now
54
this.worksheet_metadata = {};
55
this.notebook_name_blacklist_re = /[\/\\:]/;
56
this.nbformat = 3; // Increment this when changing the nbformat
57
this.nbformat_minor = 0; // Increment this when changing the nbformat
58
this.style();
59
this.create_elements();
60
this.bind_events();
61
this.save_notebook = function() { // don't allow save until notebook_loaded
62
this.save_notebook_error(null, null, "Load failed, save is disabled");
63
};
64
};
65
66
/**
67
* Tweak the notebook's CSS style.
68
*
69
* @method style
70
*/
71
Notebook.prototype.style = function () {
72
$('div#notebook').addClass('border-box-sizing');
73
};
74
75
/**
76
* Create an HTML and CSS representation of the notebook.
77
*
78
* @method create_elements
79
*/
80
Notebook.prototype.create_elements = function () {
81
var that = this;
82
this.element.attr('tabindex','-1');
83
this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
84
// We add this end_space div to the end of the notebook div to:
85
// i) provide a margin between the last cell and the end of the notebook
86
// ii) to prevent the div from scrolling up when the last cell is being
87
// edited, but is too low on the page, which browsers will do automatically.
88
var end_space = $('<div/>').addClass('end_space');
89
end_space.dblclick(function (e) {
90
var ncells = that.ncells();
91
that.insert_cell_below('code',ncells-1);
92
});
93
this.element.append(this.container);
94
this.container.append(end_space);
95
};
96
97
/**
98
* Bind JavaScript events: key presses and custom IPython events.
99
*
100
* @method bind_events
101
*/
102
Notebook.prototype.bind_events = function () {
103
var that = this;
104
105
$([IPython.events]).on('set_next_input.Notebook', function (event, data) {
106
var index = that.find_cell_index(data.cell);
107
var new_cell = that.insert_cell_below('code',index);
108
new_cell.set_text(data.text);
109
that.dirty = true;
110
});
111
112
$([IPython.events]).on('set_dirty.Notebook', function (event, data) {
113
that.dirty = data.value;
114
});
115
116
$([IPython.events]).on('trust_changed.Notebook', function (event, data) {
117
that.trusted = data.value;
118
});
119
120
$([IPython.events]).on('select.Cell', function (event, data) {
121
var index = that.find_cell_index(data.cell);
122
that.select(index);
123
});
124
125
$([IPython.events]).on('edit_mode.Cell', function (event, data) {
126
that.handle_edit_mode(data.cell);
127
});
128
129
$([IPython.events]).on('command_mode.Cell', function (event, data) {
130
that.handle_command_mode(data.cell);
131
});
132
133
$([IPython.events]).on('status_autorestarting.Kernel', function () {
134
IPython.dialog.modal({
135
title: "Kernel Restarting",
136
body: "The kernel appears to have died. It will restart automatically.",
137
buttons: {
138
OK : {
139
class : "btn-primary"
140
}
141
}
142
});
143
});
144
145
var collapse_time = function (time) {
146
var app_height = $('#ipython-main-app').height(); // content height
147
var splitter_height = $('div#pager_splitter').outerHeight(true);
148
var new_height = app_height - splitter_height;
149
that.element.animate({height : new_height + 'px'}, time);
150
};
151
152
this.element.bind('collapse_pager', function (event, extrap) {
153
var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
154
collapse_time(time);
155
});
156
157
var expand_time = function (time) {
158
var app_height = $('#ipython-main-app').height(); // content height
159
var splitter_height = $('div#pager_splitter').outerHeight(true);
160
var pager_height = $('div#pager').outerHeight(true);
161
var new_height = app_height - pager_height - splitter_height;
162
that.element.animate({height : new_height + 'px'}, time);
163
};
164
165
this.element.bind('expand_pager', function (event, extrap) {
166
var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
167
expand_time(time);
168
});
169
170
// Firefox 22 broke $(window).on("beforeunload")
171
// I'm not sure why or how.
172
window.onbeforeunload = function (e) {
173
// TODO: Make killing the kernel configurable.
174
var kill_kernel = false;
175
if (kill_kernel) {
176
that.session.kill_kernel();
177
}
178
// if we are autosaving, trigger an autosave on nav-away.
179
// still warn, because if we don't the autosave may fail.
180
if (that.dirty) {
181
if ( that.autosave_interval ) {
182
// schedule autosave in a timeout
183
// this gives you a chance to forcefully discard changes
184
// by reloading the page if you *really* want to.
185
// the timer doesn't start until you *dismiss* the dialog.
186
setTimeout(function () {
187
if (that.dirty) {
188
that.save_notebook();
189
}
190
}, 1000);
191
return "Autosave in progress, latest changes may be lost.";
192
} else {
193
return "Unsaved changes will be lost.";
194
}
195
}
196
// Null is the *only* return value that will make the browser not
197
// pop up the "don't leave" dialog.
198
return null;
199
};
200
};
201
202
/**
203
* Set the dirty flag, and trigger the set_dirty.Notebook event
204
*
205
* @method set_dirty
206
*/
207
Notebook.prototype.set_dirty = function (value) {
208
if (value === undefined) {
209
value = true;
210
}
211
if (this.dirty == value) {
212
return;
213
}
214
$([IPython.events]).trigger('set_dirty.Notebook', {value: value});
215
};
216
217
/**
218
* Scroll the top of the page to a given cell.
219
*
220
* @method scroll_to_cell
221
* @param {Number} cell_number An index of the cell to view
222
* @param {Number} time Animation time in milliseconds
223
* @return {Number} Pixel offset from the top of the container
224
*/
225
Notebook.prototype.scroll_to_cell = function (cell_number, time) {
226
var cells = this.get_cells();
227
time = time || 0;
228
cell_number = Math.min(cells.length-1,cell_number);
229
cell_number = Math.max(0 ,cell_number);
230
var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
231
this.element.animate({scrollTop:scroll_value}, time);
232
return scroll_value;
233
};
234
235
/**
236
* Scroll to the bottom of the page.
237
*
238
* @method scroll_to_bottom
239
*/
240
Notebook.prototype.scroll_to_bottom = function () {
241
this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
242
};
243
244
/**
245
* Scroll to the top of the page.
246
*
247
* @method scroll_to_top
248
*/
249
Notebook.prototype.scroll_to_top = function () {
250
this.element.animate({scrollTop:0}, 0);
251
};
252
253
// Edit Notebook metadata
254
255
Notebook.prototype.edit_metadata = function () {
256
var that = this;
257
IPython.dialog.edit_metadata(this.metadata, function (md) {
258
that.metadata = md;
259
}, 'Notebook');
260
};
261
262
// Cell indexing, retrieval, etc.
263
264
/**
265
* Get all cell elements in the notebook.
266
*
267
* @method get_cell_elements
268
* @return {jQuery} A selector of all cell elements
269
*/
270
Notebook.prototype.get_cell_elements = function () {
271
return this.container.children("div.cell");
272
};
273
274
/**
275
* Get a particular cell element.
276
*
277
* @method get_cell_element
278
* @param {Number} index An index of a cell to select
279
* @return {jQuery} A selector of the given cell.
280
*/
281
Notebook.prototype.get_cell_element = function (index) {
282
var result = null;
283
var e = this.get_cell_elements().eq(index);
284
if (e.length !== 0) {
285
result = e;
286
}
287
return result;
288
};
289
290
/**
291
* Try to get a particular cell by msg_id.
292
*
293
* @method get_msg_cell
294
* @param {String} msg_id A message UUID
295
* @return {Cell} Cell or null if no cell was found.
296
*/
297
Notebook.prototype.get_msg_cell = function (msg_id) {
298
return IPython.CodeCell.msg_cells[msg_id] || null;
299
};
300
301
/**
302
* Count the cells in this notebook.
303
*
304
* @method ncells
305
* @return {Number} The number of cells in this notebook
306
*/
307
Notebook.prototype.ncells = function () {
308
return this.get_cell_elements().length;
309
};
310
311
/**
312
* Get all Cell objects in this notebook.
313
*
314
* @method get_cells
315
* @return {Array} This notebook's Cell objects
316
*/
317
// TODO: we are often calling cells as cells()[i], which we should optimize
318
// to cells(i) or a new method.
319
Notebook.prototype.get_cells = function () {
320
return this.get_cell_elements().toArray().map(function (e) {
321
return $(e).data("cell");
322
});
323
};
324
325
/**
326
* Get a Cell object from this notebook.
327
*
328
* @method get_cell
329
* @param {Number} index An index of a cell to retrieve
330
* @return {Cell} A particular cell
331
*/
332
Notebook.prototype.get_cell = function (index) {
333
var result = null;
334
var ce = this.get_cell_element(index);
335
if (ce !== null) {
336
result = ce.data('cell');
337
}
338
return result;
339
};
340
341
/**
342
* Get the cell below a given cell.
343
*
344
* @method get_next_cell
345
* @param {Cell} cell The provided cell
346
* @return {Cell} The next cell
347
*/
348
Notebook.prototype.get_next_cell = function (cell) {
349
var result = null;
350
var index = this.find_cell_index(cell);
351
if (this.is_valid_cell_index(index+1)) {
352
result = this.get_cell(index+1);
353
}
354
return result;
355
};
356
357
/**
358
* Get the cell above a given cell.
359
*
360
* @method get_prev_cell
361
* @param {Cell} cell The provided cell
362
* @return {Cell} The previous cell
363
*/
364
Notebook.prototype.get_prev_cell = function (cell) {
365
// TODO: off-by-one
366
// nb.get_prev_cell(nb.get_cell(1)) is null
367
var result = null;
368
var index = this.find_cell_index(cell);
369
if (index !== null && index > 1) {
370
result = this.get_cell(index-1);
371
}
372
return result;
373
};
374
375
/**
376
* Get the numeric index of a given cell.
377
*
378
* @method find_cell_index
379
* @param {Cell} cell The provided cell
380
* @return {Number} The cell's numeric index
381
*/
382
Notebook.prototype.find_cell_index = function (cell) {
383
var result = null;
384
this.get_cell_elements().filter(function (index) {
385
if ($(this).data("cell") === cell) {
386
result = index;
387
}
388
});
389
return result;
390
};
391
392
/**
393
* Get a given index , or the selected index if none is provided.
394
*
395
* @method index_or_selected
396
* @param {Number} index A cell's index
397
* @return {Number} The given index, or selected index if none is provided.
398
*/
399
Notebook.prototype.index_or_selected = function (index) {
400
var i;
401
if (index === undefined || index === null) {
402
i = this.get_selected_index();
403
if (i === null) {
404
i = 0;
405
}
406
} else {
407
i = index;
408
}
409
return i;
410
};
411
412
/**
413
* Get the currently selected cell.
414
* @method get_selected_cell
415
* @return {Cell} The selected cell
416
*/
417
Notebook.prototype.get_selected_cell = function () {
418
var index = this.get_selected_index();
419
return this.get_cell(index);
420
};
421
422
/**
423
* Check whether a cell index is valid.
424
*
425
* @method is_valid_cell_index
426
* @param {Number} index A cell index
427
* @return True if the index is valid, false otherwise
428
*/
429
Notebook.prototype.is_valid_cell_index = function (index) {
430
if (index !== null && index >= 0 && index < this.ncells()) {
431
return true;
432
} else {
433
return false;
434
}
435
};
436
437
/**
438
* Get the index of the currently selected cell.
439
440
* @method get_selected_index
441
* @return {Number} The selected cell's numeric index
442
*/
443
Notebook.prototype.get_selected_index = function () {
444
var result = null;
445
this.get_cell_elements().filter(function (index) {
446
if ($(this).data("cell").selected === true) {
447
result = index;
448
}
449
});
450
return result;
451
};
452
453
454
// Cell selection.
455
456
/**
457
* Programmatically select a cell.
458
*
459
* @method select
460
* @param {Number} index A cell's index
461
* @return {Notebook} This notebook
462
*/
463
Notebook.prototype.select = function (index) {
464
if (this.is_valid_cell_index(index)) {
465
var sindex = this.get_selected_index();
466
if (sindex !== null && index !== sindex) {
467
// If we are about to select a different cell, make sure we are
468
// first in command mode.
469
if (this.mode !== 'command') {
470
this.command_mode();
471
}
472
this.get_cell(sindex).unselect();
473
}
474
var cell = this.get_cell(index);
475
cell.select();
476
if (cell.cell_type === 'heading') {
477
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
478
{'cell_type':cell.cell_type,level:cell.level}
479
);
480
} else {
481
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
482
{'cell_type':cell.cell_type}
483
);
484
}
485
}
486
return this;
487
};
488
489
/**
490
* Programmatically select the next cell.
491
*
492
* @method select_next
493
* @return {Notebook} This notebook
494
*/
495
Notebook.prototype.select_next = function () {
496
var index = this.get_selected_index();
497
this.select(index+1);
498
return this;
499
};
500
501
/**
502
* Programmatically select the previous cell.
503
*
504
* @method select_prev
505
* @return {Notebook} This notebook
506
*/
507
Notebook.prototype.select_prev = function () {
508
var index = this.get_selected_index();
509
this.select(index-1);
510
return this;
511
};
512
513
514
// Edit/Command mode
515
516
/**
517
* Gets the index of the cell that is in edit mode.
518
*
519
* @method get_edit_index
520
*
521
* @return index {int}
522
**/
523
Notebook.prototype.get_edit_index = function () {
524
var result = null;
525
this.get_cell_elements().filter(function (index) {
526
if ($(this).data("cell").mode === 'edit') {
527
result = index;
528
}
529
});
530
return result;
531
};
532
533
/**
534
* Handle when a a cell blurs and the notebook should enter command mode.
535
*
536
* @method handle_command_mode
537
* @param [cell] {Cell} Cell to enter command mode on.
538
**/
539
Notebook.prototype.handle_command_mode = function (cell) {
540
if (this.mode !== 'command') {
541
cell.command_mode();
542
this.mode = 'command';
543
$([IPython.events]).trigger('command_mode.Notebook');
544
IPython.keyboard_manager.command_mode();
545
}
546
};
547
548
/**
549
* Make the notebook enter command mode.
550
*
551
* @method command_mode
552
**/
553
Notebook.prototype.command_mode = function () {
554
var cell = this.get_cell(this.get_edit_index());
555
if (cell && this.mode !== 'command') {
556
// We don't call cell.command_mode, but rather call cell.focus_cell()
557
// which will blur and CM editor and trigger the call to
558
// handle_command_mode.
559
cell.focus_cell();
560
}
561
};
562
563
/**
564
* Handle when a cell fires it's edit_mode event.
565
*
566
* @method handle_edit_mode
567
* @param [cell] {Cell} Cell to enter edit mode on.
568
**/
569
Notebook.prototype.handle_edit_mode = function (cell) {
570
if (cell && this.mode !== 'edit') {
571
cell.edit_mode();
572
this.mode = 'edit';
573
$([IPython.events]).trigger('edit_mode.Notebook');
574
IPython.keyboard_manager.edit_mode();
575
}
576
};
577
578
/**
579
* Make a cell enter edit mode.
580
*
581
* @method edit_mode
582
**/
583
Notebook.prototype.edit_mode = function () {
584
var cell = this.get_selected_cell();
585
if (cell && this.mode !== 'edit') {
586
cell.unrender();
587
cell.focus_editor();
588
}
589
};
590
591
/**
592
* Focus the currently selected cell.
593
*
594
* @method focus_cell
595
**/
596
Notebook.prototype.focus_cell = function () {
597
var cell = this.get_selected_cell();
598
if (cell === null) {return;} // No cell is selected
599
cell.focus_cell();
600
};
601
602
// Cell movement
603
604
/**
605
* Move given (or selected) cell up and select it.
606
*
607
* @method move_cell_up
608
* @param [index] {integer} cell index
609
* @return {Notebook} This notebook
610
**/
611
Notebook.prototype.move_cell_up = function (index) {
612
var i = this.index_or_selected(index);
613
if (this.is_valid_cell_index(i) && i > 0) {
614
var pivot = this.get_cell_element(i-1);
615
var tomove = this.get_cell_element(i);
616
if (pivot !== null && tomove !== null) {
617
tomove.detach();
618
pivot.before(tomove);
619
this.select(i-1);
620
var cell = this.get_selected_cell();
621
cell.focus_cell();
622
}
623
this.set_dirty(true);
624
}
625
return this;
626
};
627
628
629
/**
630
* Move given (or selected) cell down and select it
631
*
632
* @method move_cell_down
633
* @param [index] {integer} cell index
634
* @return {Notebook} This notebook
635
**/
636
Notebook.prototype.move_cell_down = function (index) {
637
var i = this.index_or_selected(index);
638
if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
639
var pivot = this.get_cell_element(i+1);
640
var tomove = this.get_cell_element(i);
641
if (pivot !== null && tomove !== null) {
642
tomove.detach();
643
pivot.after(tomove);
644
this.select(i+1);
645
var cell = this.get_selected_cell();
646
cell.focus_cell();
647
}
648
}
649
this.set_dirty();
650
return this;
651
};
652
653
654
// Insertion, deletion.
655
656
/**
657
* Delete a cell from the notebook.
658
*
659
* @method delete_cell
660
* @param [index] A cell's numeric index
661
* @return {Notebook} This notebook
662
*/
663
Notebook.prototype.delete_cell = function (index) {
664
var i = this.index_or_selected(index);
665
var cell = this.get_selected_cell();
666
this.undelete_backup = cell.toJSON();
667
$('#undelete_cell').removeClass('disabled');
668
if (this.is_valid_cell_index(i)) {
669
var old_ncells = this.ncells();
670
var ce = this.get_cell_element(i);
671
ce.remove();
672
if (i === 0) {
673
// Always make sure we have at least one cell.
674
if (old_ncells === 1) {
675
this.insert_cell_below('code');
676
}
677
this.select(0);
678
this.undelete_index = 0;
679
this.undelete_below = false;
680
} else if (i === old_ncells-1 && i !== 0) {
681
this.select(i-1);
682
this.undelete_index = i - 1;
683
this.undelete_below = true;
684
} else {
685
this.select(i);
686
this.undelete_index = i;
687
this.undelete_below = false;
688
}
689
$([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
690
this.set_dirty(true);
691
}
692
return this;
693
};
694
695
/**
696
* Restore the most recently deleted cell.
697
*
698
* @method undelete
699
*/
700
Notebook.prototype.undelete_cell = function() {
701
if (this.undelete_backup !== null && this.undelete_index !== null) {
702
var current_index = this.get_selected_index();
703
if (this.undelete_index < current_index) {
704
current_index = current_index + 1;
705
}
706
if (this.undelete_index >= this.ncells()) {
707
this.select(this.ncells() - 1);
708
}
709
else {
710
this.select(this.undelete_index);
711
}
712
var cell_data = this.undelete_backup;
713
var new_cell = null;
714
if (this.undelete_below) {
715
new_cell = this.insert_cell_below(cell_data.cell_type);
716
} else {
717
new_cell = this.insert_cell_above(cell_data.cell_type);
718
}
719
new_cell.fromJSON(cell_data);
720
if (this.undelete_below) {
721
this.select(current_index+1);
722
} else {
723
this.select(current_index);
724
}
725
this.undelete_backup = null;
726
this.undelete_index = null;
727
}
728
$('#undelete_cell').addClass('disabled');
729
};
730
731
/**
732
* Insert a cell so that after insertion the cell is at given index.
733
*
734
* Similar to insert_above, but index parameter is mandatory
735
*
736
* Index will be brought back into the accissible range [0,n]
737
*
738
* @method insert_cell_at_index
739
* @param type {string} in ['code','markdown','heading']
740
* @param [index] {int} a valid index where to inser cell
741
*
742
* @return cell {cell|null} created cell or null
743
**/
744
Notebook.prototype.insert_cell_at_index = function(type, index){
745
746
var ncells = this.ncells();
747
index = Math.min(index,ncells);
748
index = Math.max(index,0);
749
var cell = null;
750
751
if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
752
if (type === 'code') {
753
cell = new IPython.CodeCell(this.kernel);
754
cell.set_input_prompt();
755
} else if (type === 'markdown') {
756
cell = new IPython.MarkdownCell();
757
} else if (type === 'raw') {
758
cell = new IPython.RawCell();
759
} else if (type === 'heading') {
760
cell = new IPython.HeadingCell();
761
}
762
763
if(this._insert_element_at_index(cell.element,index)) {
764
cell.render();
765
$([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
766
cell.refresh();
767
// We used to select the cell after we refresh it, but there
768
// are now cases were this method is called where select is
769
// not appropriate. The selection logic should be handled by the
770
// caller of the the top level insert_cell methods.
771
this.set_dirty(true);
772
}
773
}
774
return cell;
775
776
};
777
778
/**
779
* Insert an element at given cell index.
780
*
781
* @method _insert_element_at_index
782
* @param element {dom element} a cell element
783
* @param [index] {int} a valid index where to inser cell
784
* @private
785
*
786
* return true if everything whent fine.
787
**/
788
Notebook.prototype._insert_element_at_index = function(element, index){
789
if (element === undefined){
790
return false;
791
}
792
793
var ncells = this.ncells();
794
795
if (ncells === 0) {
796
// special case append if empty
797
this.element.find('div.end_space').before(element);
798
} else if ( ncells === index ) {
799
// special case append it the end, but not empty
800
this.get_cell_element(index-1).after(element);
801
} else if (this.is_valid_cell_index(index)) {
802
// otherwise always somewhere to append to
803
this.get_cell_element(index).before(element);
804
} else {
805
return false;
806
}
807
808
if (this.undelete_index !== null && index <= this.undelete_index) {
809
this.undelete_index = this.undelete_index + 1;
810
this.set_dirty(true);
811
}
812
return true;
813
};
814
815
/**
816
* Insert a cell of given type above given index, or at top
817
* of notebook if index smaller than 0.
818
*
819
* default index value is the one of currently selected cell
820
*
821
* @method insert_cell_above
822
* @param type {string} cell type
823
* @param [index] {integer}
824
*
825
* @return handle to created cell or null
826
**/
827
Notebook.prototype.insert_cell_above = function (type, index) {
828
index = this.index_or_selected(index);
829
return this.insert_cell_at_index(type, index);
830
};
831
832
/**
833
* Insert a cell of given type below given index, or at bottom
834
* of notebook if index greater thatn number of cell
835
*
836
* default index value is the one of currently selected cell
837
*
838
* @method insert_cell_below
839
* @param type {string} cell type
840
* @param [index] {integer}
841
*
842
* @return handle to created cell or null
843
*
844
**/
845
Notebook.prototype.insert_cell_below = function (type, index) {
846
index = this.index_or_selected(index);
847
return this.insert_cell_at_index(type, index+1);
848
};
849
850
851
/**
852
* Insert cell at end of notebook
853
*
854
* @method insert_cell_at_bottom
855
* @param {String} type cell type
856
*
857
* @return the added cell; or null
858
**/
859
Notebook.prototype.insert_cell_at_bottom = function (type){
860
var len = this.ncells();
861
return this.insert_cell_below(type,len-1);
862
};
863
864
/**
865
* Turn a cell into a code cell.
866
*
867
* @method to_code
868
* @param {Number} [index] A cell's index
869
*/
870
Notebook.prototype.to_code = function (index) {
871
var i = this.index_or_selected(index);
872
if (this.is_valid_cell_index(i)) {
873
var source_element = this.get_cell_element(i);
874
var source_cell = source_element.data("cell");
875
if (!(source_cell instanceof IPython.CodeCell)) {
876
var target_cell = this.insert_cell_below('code',i);
877
var text = source_cell.get_text();
878
if (text === source_cell.placeholder) {
879
text = '';
880
}
881
target_cell.set_text(text);
882
// make this value the starting point, so that we can only undo
883
// to this state, instead of a blank cell
884
target_cell.code_mirror.clearHistory();
885
source_element.remove();
886
this.select(i);
887
this.set_dirty(true);
888
}
889
}
890
};
891
892
/**
893
* Turn a cell into a Markdown cell.
894
*
895
* @method to_markdown
896
* @param {Number} [index] A cell's index
897
*/
898
Notebook.prototype.to_markdown = function (index) {
899
var i = this.index_or_selected(index);
900
if (this.is_valid_cell_index(i)) {
901
var source_element = this.get_cell_element(i);
902
var source_cell = source_element.data("cell");
903
if (!(source_cell instanceof IPython.MarkdownCell)) {
904
var target_cell = this.insert_cell_below('markdown',i);
905
var text = source_cell.get_text();
906
if (text === source_cell.placeholder) {
907
text = '';
908
}
909
// We must show the editor before setting its contents
910
target_cell.unrender();
911
target_cell.set_text(text);
912
// make this value the starting point, so that we can only undo
913
// to this state, instead of a blank cell
914
target_cell.code_mirror.clearHistory();
915
source_element.remove();
916
this.select(i);
917
if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
918
target_cell.render();
919
}
920
this.set_dirty(true);
921
}
922
}
923
};
924
925
/**
926
* Turn a cell into a raw text cell.
927
*
928
* @method to_raw
929
* @param {Number} [index] A cell's index
930
*/
931
Notebook.prototype.to_raw = function (index) {
932
var i = this.index_or_selected(index);
933
if (this.is_valid_cell_index(i)) {
934
var source_element = this.get_cell_element(i);
935
var source_cell = source_element.data("cell");
936
var target_cell = null;
937
if (!(source_cell instanceof IPython.RawCell)) {
938
target_cell = this.insert_cell_below('raw',i);
939
var text = source_cell.get_text();
940
if (text === source_cell.placeholder) {
941
text = '';
942
}
943
// We must show the editor before setting its contents
944
target_cell.unrender();
945
target_cell.set_text(text);
946
// make this value the starting point, so that we can only undo
947
// to this state, instead of a blank cell
948
target_cell.code_mirror.clearHistory();
949
source_element.remove();
950
this.select(i);
951
this.set_dirty(true);
952
}
953
}
954
};
955
956
/**
957
* Turn a cell into a heading cell.
958
*
959
* @method to_heading
960
* @param {Number} [index] A cell's index
961
* @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
962
*/
963
Notebook.prototype.to_heading = function (index, level) {
964
level = level || 1;
965
var i = this.index_or_selected(index);
966
if (this.is_valid_cell_index(i)) {
967
var source_element = this.get_cell_element(i);
968
var source_cell = source_element.data("cell");
969
var target_cell = null;
970
if (source_cell instanceof IPython.HeadingCell) {
971
source_cell.set_level(level);
972
} else {
973
target_cell = this.insert_cell_below('heading',i);
974
var text = source_cell.get_text();
975
if (text === source_cell.placeholder) {
976
text = '';
977
}
978
// We must show the editor before setting its contents
979
target_cell.set_level(level);
980
target_cell.unrender();
981
target_cell.set_text(text);
982
// make this value the starting point, so that we can only undo
983
// to this state, instead of a blank cell
984
target_cell.code_mirror.clearHistory();
985
source_element.remove();
986
this.select(i);
987
if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
988
target_cell.render();
989
}
990
}
991
this.set_dirty(true);
992
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
993
{'cell_type':'heading',level:level}
994
);
995
}
996
};
997
998
999
// Cut/Copy/Paste
1000
1001
/**
1002
* Enable UI elements for pasting cells.
1003
*
1004
* @method enable_paste
1005
*/
1006
Notebook.prototype.enable_paste = function () {
1007
var that = this;
1008
if (!this.paste_enabled) {
1009
$('#paste_cell_replace').removeClass('disabled')
1010
.on('click', function () {that.paste_cell_replace();});
1011
$('#paste_cell_above').removeClass('disabled')
1012
.on('click', function () {that.paste_cell_above();});
1013
$('#paste_cell_below').removeClass('disabled')
1014
.on('click', function () {that.paste_cell_below();});
1015
this.paste_enabled = true;
1016
}
1017
};
1018
1019
/**
1020
* Disable UI elements for pasting cells.
1021
*
1022
* @method disable_paste
1023
*/
1024
Notebook.prototype.disable_paste = function () {
1025
if (this.paste_enabled) {
1026
$('#paste_cell_replace').addClass('disabled').off('click');
1027
$('#paste_cell_above').addClass('disabled').off('click');
1028
$('#paste_cell_below').addClass('disabled').off('click');
1029
this.paste_enabled = false;
1030
}
1031
};
1032
1033
/**
1034
* Cut a cell.
1035
*
1036
* @method cut_cell
1037
*/
1038
Notebook.prototype.cut_cell = function () {
1039
this.copy_cell();
1040
this.delete_cell();
1041
};
1042
1043
/**
1044
* Copy a cell.
1045
*
1046
* @method copy_cell
1047
*/
1048
Notebook.prototype.copy_cell = function () {
1049
var cell = this.get_selected_cell();
1050
this.clipboard = cell.toJSON();
1051
this.enable_paste();
1052
};
1053
1054
/**
1055
* Replace the selected cell with a cell in the clipboard.
1056
*
1057
* @method paste_cell_replace
1058
*/
1059
Notebook.prototype.paste_cell_replace = function () {
1060
if (this.clipboard !== null && this.paste_enabled) {
1061
var cell_data = this.clipboard;
1062
var new_cell = this.insert_cell_above(cell_data.cell_type);
1063
new_cell.fromJSON(cell_data);
1064
var old_cell = this.get_next_cell(new_cell);
1065
this.delete_cell(this.find_cell_index(old_cell));
1066
this.select(this.find_cell_index(new_cell));
1067
}
1068
};
1069
1070
/**
1071
* Paste a cell from the clipboard above the selected cell.
1072
*
1073
* @method paste_cell_above
1074
*/
1075
Notebook.prototype.paste_cell_above = function () {
1076
if (this.clipboard !== null && this.paste_enabled) {
1077
var cell_data = this.clipboard;
1078
var new_cell = this.insert_cell_above(cell_data.cell_type);
1079
new_cell.fromJSON(cell_data);
1080
new_cell.focus_cell();
1081
}
1082
};
1083
1084
/**
1085
* Paste a cell from the clipboard below the selected cell.
1086
*
1087
* @method paste_cell_below
1088
*/
1089
Notebook.prototype.paste_cell_below = function () {
1090
if (this.clipboard !== null && this.paste_enabled) {
1091
var cell_data = this.clipboard;
1092
var new_cell = this.insert_cell_below(cell_data.cell_type);
1093
new_cell.fromJSON(cell_data);
1094
new_cell.focus_cell();
1095
}
1096
};
1097
1098
// Split/merge
1099
1100
/**
1101
* Split the selected cell into two, at the cursor.
1102
*
1103
* @method split_cell
1104
*/
1105
Notebook.prototype.split_cell = function () {
1106
var mdc = IPython.MarkdownCell;
1107
var rc = IPython.RawCell;
1108
var cell = this.get_selected_cell();
1109
if (cell.is_splittable()) {
1110
var texta = cell.get_pre_cursor();
1111
var textb = cell.get_post_cursor();
1112
if (cell instanceof IPython.CodeCell) {
1113
// In this case the operations keep the notebook in its existing mode
1114
// so we don't need to do any post-op mode changes.
1115
cell.set_text(textb);
1116
var new_cell = this.insert_cell_above('code');
1117
new_cell.set_text(texta);
1118
} else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1119
// We know cell is !rendered so we can use set_text.
1120
cell.set_text(textb);
1121
var new_cell = this.insert_cell_above(cell.cell_type);
1122
// Unrender the new cell so we can call set_text.
1123
new_cell.unrender();
1124
new_cell.set_text(texta);
1125
}
1126
}
1127
};
1128
1129
/**
1130
* Combine the selected cell into the cell above it.
1131
*
1132
* @method merge_cell_above
1133
*/
1134
Notebook.prototype.merge_cell_above = function () {
1135
var mdc = IPython.MarkdownCell;
1136
var rc = IPython.RawCell;
1137
var index = this.get_selected_index();
1138
var cell = this.get_cell(index);
1139
var render = cell.rendered;
1140
if (!cell.is_mergeable()) {
1141
return;
1142
}
1143
if (index > 0) {
1144
var upper_cell = this.get_cell(index-1);
1145
if (!upper_cell.is_mergeable()) {
1146
return;
1147
}
1148
var upper_text = upper_cell.get_text();
1149
var text = cell.get_text();
1150
if (cell instanceof IPython.CodeCell) {
1151
cell.set_text(upper_text+'\n'+text);
1152
} else if ((cell instanceof mdc) || (cell instanceof rc)) {
1153
cell.unrender(); // Must unrender before we set_text.
1154
cell.set_text(upper_text+'\n\n'+text);
1155
if (render) {
1156
// The rendered state of the final cell should match
1157
// that of the original selected cell;
1158
cell.render();
1159
}
1160
}
1161
this.delete_cell(index-1);
1162
this.select(this.find_cell_index(cell));
1163
}
1164
};
1165
1166
/**
1167
* Combine the selected cell into the cell below it.
1168
*
1169
* @method merge_cell_below
1170
*/
1171
Notebook.prototype.merge_cell_below = function () {
1172
var mdc = IPython.MarkdownCell;
1173
var rc = IPython.RawCell;
1174
var index = this.get_selected_index();
1175
var cell = this.get_cell(index);
1176
var render = cell.rendered;
1177
if (!cell.is_mergeable()) {
1178
return;
1179
}
1180
if (index < this.ncells()-1) {
1181
var lower_cell = this.get_cell(index+1);
1182
if (!lower_cell.is_mergeable()) {
1183
return;
1184
}
1185
var lower_text = lower_cell.get_text();
1186
var text = cell.get_text();
1187
if (cell instanceof IPython.CodeCell) {
1188
cell.set_text(text+'\n'+lower_text);
1189
} else if ((cell instanceof mdc) || (cell instanceof rc)) {
1190
cell.unrender(); // Must unrender before we set_text.
1191
cell.set_text(text+'\n\n'+lower_text);
1192
if (render) {
1193
// The rendered state of the final cell should match
1194
// that of the original selected cell;
1195
cell.render();
1196
}
1197
}
1198
this.delete_cell(index+1);
1199
this.select(this.find_cell_index(cell));
1200
}
1201
};
1202
1203
1204
// Cell collapsing and output clearing
1205
1206
/**
1207
* Hide a cell's output.
1208
*
1209
* @method collapse_output
1210
* @param {Number} index A cell's numeric index
1211
*/
1212
Notebook.prototype.collapse_output = function (index) {
1213
var i = this.index_or_selected(index);
1214
var cell = this.get_cell(i);
1215
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1216
cell.collapse_output();
1217
this.set_dirty(true);
1218
}
1219
};
1220
1221
/**
1222
* Hide each code cell's output area.
1223
*
1224
* @method collapse_all_output
1225
*/
1226
Notebook.prototype.collapse_all_output = function () {
1227
$.map(this.get_cells(), function (cell, i) {
1228
if (cell instanceof IPython.CodeCell) {
1229
cell.collapse_output();
1230
}
1231
});
1232
// this should not be set if the `collapse` key is removed from nbformat
1233
this.set_dirty(true);
1234
};
1235
1236
/**
1237
* Show a cell's output.
1238
*
1239
* @method expand_output
1240
* @param {Number} index A cell's numeric index
1241
*/
1242
Notebook.prototype.expand_output = function (index) {
1243
var i = this.index_or_selected(index);
1244
var cell = this.get_cell(i);
1245
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1246
cell.expand_output();
1247
this.set_dirty(true);
1248
}
1249
};
1250
1251
/**
1252
* Expand each code cell's output area, and remove scrollbars.
1253
*
1254
* @method expand_all_output
1255
*/
1256
Notebook.prototype.expand_all_output = function () {
1257
$.map(this.get_cells(), function (cell, i) {
1258
if (cell instanceof IPython.CodeCell) {
1259
cell.expand_output();
1260
}
1261
});
1262
// this should not be set if the `collapse` key is removed from nbformat
1263
this.set_dirty(true);
1264
};
1265
1266
/**
1267
* Clear the selected CodeCell's output area.
1268
*
1269
* @method clear_output
1270
* @param {Number} index A cell's numeric index
1271
*/
1272
Notebook.prototype.clear_output = function (index) {
1273
var i = this.index_or_selected(index);
1274
var cell = this.get_cell(i);
1275
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1276
cell.clear_output();
1277
this.set_dirty(true);
1278
}
1279
};
1280
1281
/**
1282
* Clear each code cell's output area.
1283
*
1284
* @method clear_all_output
1285
*/
1286
Notebook.prototype.clear_all_output = function () {
1287
$.map(this.get_cells(), function (cell, i) {
1288
if (cell instanceof IPython.CodeCell) {
1289
cell.clear_output();
1290
}
1291
});
1292
this.set_dirty(true);
1293
};
1294
1295
/**
1296
* Scroll the selected CodeCell's output area.
1297
*
1298
* @method scroll_output
1299
* @param {Number} index A cell's numeric index
1300
*/
1301
Notebook.prototype.scroll_output = function (index) {
1302
var i = this.index_or_selected(index);
1303
var cell = this.get_cell(i);
1304
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1305
cell.scroll_output();
1306
this.set_dirty(true);
1307
}
1308
};
1309
1310
/**
1311
* Expand each code cell's output area, and add a scrollbar for long output.
1312
*
1313
* @method scroll_all_output
1314
*/
1315
Notebook.prototype.scroll_all_output = function () {
1316
$.map(this.get_cells(), function (cell, i) {
1317
if (cell instanceof IPython.CodeCell) {
1318
cell.scroll_output();
1319
}
1320
});
1321
// this should not be set if the `collapse` key is removed from nbformat
1322
this.set_dirty(true);
1323
};
1324
1325
/** Toggle whether a cell's output is collapsed or expanded.
1326
*
1327
* @method toggle_output
1328
* @param {Number} index A cell's numeric index
1329
*/
1330
Notebook.prototype.toggle_output = function (index) {
1331
var i = this.index_or_selected(index);
1332
var cell = this.get_cell(i);
1333
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1334
cell.toggle_output();
1335
this.set_dirty(true);
1336
}
1337
};
1338
1339
/**
1340
* Hide/show the output of all cells.
1341
*
1342
* @method toggle_all_output
1343
*/
1344
Notebook.prototype.toggle_all_output = function () {
1345
$.map(this.get_cells(), function (cell, i) {
1346
if (cell instanceof IPython.CodeCell) {
1347
cell.toggle_output();
1348
}
1349
});
1350
// this should not be set if the `collapse` key is removed from nbformat
1351
this.set_dirty(true);
1352
};
1353
1354
/**
1355
* Toggle a scrollbar for long cell outputs.
1356
*
1357
* @method toggle_output_scroll
1358
* @param {Number} index A cell's numeric index
1359
*/
1360
Notebook.prototype.toggle_output_scroll = function (index) {
1361
var i = this.index_or_selected(index);
1362
var cell = this.get_cell(i);
1363
if (cell !== null && (cell instanceof IPython.CodeCell)) {
1364
cell.toggle_output_scroll();
1365
this.set_dirty(true);
1366
}
1367
};
1368
1369
/**
1370
* Toggle the scrolling of long output on all cells.
1371
*
1372
* @method toggle_all_output_scrolling
1373
*/
1374
Notebook.prototype.toggle_all_output_scroll = function () {
1375
$.map(this.get_cells(), function (cell, i) {
1376
if (cell instanceof IPython.CodeCell) {
1377
cell.toggle_output_scroll();
1378
}
1379
});
1380
// this should not be set if the `collapse` key is removed from nbformat
1381
this.set_dirty(true);
1382
};
1383
1384
// Other cell functions: line numbers, ...
1385
1386
/**
1387
* Toggle line numbers in the selected cell's input area.
1388
*
1389
* @method cell_toggle_line_numbers
1390
*/
1391
Notebook.prototype.cell_toggle_line_numbers = function() {
1392
this.get_selected_cell().toggle_line_numbers();
1393
};
1394
1395
// Session related things
1396
1397
/**
1398
* Start a new session and set it on each code cell.
1399
*
1400
* @method start_session
1401
*/
1402
Notebook.prototype.start_session = function () {
1403
this.session = new IPython.Session(this, this.options);
1404
this.session.start($.proxy(this._session_started, this));
1405
};
1406
1407
1408
/**
1409
* Once a session is started, link the code cells to the kernel and pass the
1410
* comm manager to the widget manager
1411
*
1412
*/
1413
Notebook.prototype._session_started = function(){
1414
this.kernel = this.session.kernel;
1415
var ncells = this.ncells();
1416
for (var i=0; i<ncells; i++) {
1417
var cell = this.get_cell(i);
1418
if (cell instanceof IPython.CodeCell) {
1419
cell.set_kernel(this.session.kernel);
1420
}
1421
}
1422
};
1423
1424
/**
1425
* Prompt the user to restart the IPython kernel.
1426
*
1427
* @method restart_kernel
1428
*/
1429
Notebook.prototype.restart_kernel = function () {
1430
var that = this;
1431
IPython.dialog.modal({
1432
title : "Restart kernel or continue running?",
1433
body : $("<p/>").text(
1434
'Do you want to restart the current kernel? You will lose all variables defined in it.'
1435
),
1436
buttons : {
1437
"Continue running" : {},
1438
"Restart" : {
1439
"class" : "btn-danger",
1440
"click" : function() {
1441
that.session.restart_kernel();
1442
}
1443
}
1444
}
1445
});
1446
};
1447
1448
/**
1449
* Execute or render cell outputs and go into command mode.
1450
*
1451
* @method execute_cell
1452
*/
1453
Notebook.prototype.execute_cell = function () {
1454
// mode = shift, ctrl, alt
1455
var cell = this.get_selected_cell();
1456
var cell_index = this.find_cell_index(cell);
1457
1458
cell.execute();
1459
this.command_mode();
1460
this.set_dirty(true);
1461
};
1462
1463
/**
1464
* Execute or render cell outputs and insert a new cell below.
1465
*
1466
* @method execute_cell_and_insert_below
1467
*/
1468
Notebook.prototype.execute_cell_and_insert_below = function () {
1469
var cell = this.get_selected_cell();
1470
var cell_index = this.find_cell_index(cell);
1471
1472
cell.execute();
1473
1474
// If we are at the end always insert a new cell and return
1475
if (cell_index === (this.ncells()-1)) {
1476
this.command_mode();
1477
this.insert_cell_below('code');
1478
this.select(cell_index+1);
1479
this.edit_mode();
1480
this.scroll_to_bottom();
1481
this.set_dirty(true);
1482
return;
1483
}
1484
1485
this.command_mode();
1486
this.insert_cell_below('code');
1487
this.select(cell_index+1);
1488
this.edit_mode();
1489
this.set_dirty(true);
1490
};
1491
1492
/**
1493
* Execute or render cell outputs and select the next cell.
1494
*
1495
* @method execute_cell_and_select_below
1496
*/
1497
Notebook.prototype.execute_cell_and_select_below = function () {
1498
1499
var cell = this.get_selected_cell();
1500
var cell_index = this.find_cell_index(cell);
1501
1502
cell.execute();
1503
1504
// If we are at the end always insert a new cell and return
1505
if (cell_index === (this.ncells()-1)) {
1506
this.command_mode();
1507
this.insert_cell_below('code');
1508
this.select(cell_index+1);
1509
this.edit_mode();
1510
this.scroll_to_bottom();
1511
this.set_dirty(true);
1512
return;
1513
}
1514
1515
this.command_mode();
1516
this.select(cell_index+1);
1517
this.focus_cell();
1518
this.set_dirty(true);
1519
};
1520
1521
/**
1522
* Execute all cells below the selected cell.
1523
*
1524
* @method execute_cells_below
1525
*/
1526
Notebook.prototype.execute_cells_below = function () {
1527
this.execute_cell_range(this.get_selected_index(), this.ncells());
1528
this.scroll_to_bottom();
1529
};
1530
1531
/**
1532
* Execute all cells above the selected cell.
1533
*
1534
* @method execute_cells_above
1535
*/
1536
Notebook.prototype.execute_cells_above = function () {
1537
this.execute_cell_range(0, this.get_selected_index());
1538
};
1539
1540
/**
1541
* Execute all cells.
1542
*
1543
* @method execute_all_cells
1544
*/
1545
Notebook.prototype.execute_all_cells = function () {
1546
this.execute_cell_range(0, this.ncells());
1547
this.scroll_to_bottom();
1548
};
1549
1550
/**
1551
* Execute a contiguous range of cells.
1552
*
1553
* @method execute_cell_range
1554
* @param {Number} start Index of the first cell to execute (inclusive)
1555
* @param {Number} end Index of the last cell to execute (exclusive)
1556
*/
1557
Notebook.prototype.execute_cell_range = function (start, end) {
1558
this.command_mode();
1559
for (var i=start; i<end; i++) {
1560
this.select(i);
1561
this.execute_cell();
1562
}
1563
};
1564
1565
// Persistance and loading
1566
1567
/**
1568
* Getter method for this notebook's name.
1569
*
1570
* @method get_notebook_name
1571
* @return {String} This notebook's name (excluding file extension)
1572
*/
1573
Notebook.prototype.get_notebook_name = function () {
1574
var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1575
return nbname;
1576
};
1577
1578
/**
1579
* Setter method for this notebook's name.
1580
*
1581
* @method set_notebook_name
1582
* @param {String} name A new name for this notebook
1583
*/
1584
Notebook.prototype.set_notebook_name = function (name) {
1585
this.notebook_name = name;
1586
};
1587
1588
/**
1589
* Check that a notebook's name is valid.
1590
*
1591
* @method test_notebook_name
1592
* @param {String} nbname A name for this notebook
1593
* @return {Boolean} True if the name is valid, false if invalid
1594
*/
1595
Notebook.prototype.test_notebook_name = function (nbname) {
1596
nbname = nbname || '';
1597
if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1598
return true;
1599
} else {
1600
return false;
1601
}
1602
};
1603
1604
/**
1605
* Load a notebook from JSON (.ipynb).
1606
*
1607
* This currently handles one worksheet: others are deleted.
1608
*
1609
* @method fromJSON
1610
* @param {Object} data JSON representation of a notebook
1611
*/
1612
Notebook.prototype.fromJSON = function (data) {
1613
var content = data.content;
1614
var ncells = this.ncells();
1615
var i;
1616
for (i=0; i<ncells; i++) {
1617
// Always delete cell 0 as they get renumbered as they are deleted.
1618
this.delete_cell(0);
1619
}
1620
// Save the metadata and name.
1621
this.metadata = content.metadata;
1622
this.notebook_name = data.name;
1623
var trusted = true;
1624
// Only handle 1 worksheet for now.
1625
var worksheet = content.worksheets[0];
1626
if (worksheet !== undefined) {
1627
if (worksheet.metadata) {
1628
this.worksheet_metadata = worksheet.metadata;
1629
}
1630
var new_cells = worksheet.cells;
1631
ncells = new_cells.length;
1632
var cell_data = null;
1633
var new_cell = null;
1634
for (i=0; i<ncells; i++) {
1635
cell_data = new_cells[i];
1636
// VERSIONHACK: plaintext -> raw
1637
// handle never-released plaintext name for raw cells
1638
if (cell_data.cell_type === 'plaintext'){
1639
cell_data.cell_type = 'raw';
1640
}
1641
1642
new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1643
new_cell.fromJSON(cell_data);
1644
if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1645
trusted = false;
1646
}
1647
}
1648
}
1649
if (trusted != this.trusted) {
1650
this.trusted = trusted;
1651
$([IPython.events]).trigger("trust_changed.Notebook", trusted);
1652
}
1653
if (content.worksheets.length > 1) {
1654
IPython.dialog.modal({
1655
title : "Multiple worksheets",
1656
body : "This notebook has " + data.worksheets.length + " worksheets, " +
1657
"but this version of IPython can only handle the first. " +
1658
"If you save this notebook, worksheets after the first will be lost.",
1659
buttons : {
1660
OK : {
1661
class : "btn-danger"
1662
}
1663
}
1664
});
1665
}
1666
};
1667
1668
/**
1669
* Dump this notebook into a JSON-friendly object.
1670
*
1671
* @method toJSON
1672
* @return {Object} A JSON-friendly representation of this notebook.
1673
*/
1674
Notebook.prototype.toJSON = function () {
1675
var cells = this.get_cells();
1676
var ncells = cells.length;
1677
var cell_array = new Array(ncells);
1678
var trusted = true;
1679
for (var i=0; i<ncells; i++) {
1680
var cell = cells[i];
1681
if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1682
trusted = false;
1683
}
1684
cell_array[i] = cell.toJSON();
1685
}
1686
var data = {
1687
// Only handle 1 worksheet for now.
1688
worksheets : [{
1689
cells: cell_array,
1690
metadata: this.worksheet_metadata
1691
}],
1692
metadata : this.metadata
1693
};
1694
if (trusted != this.trusted) {
1695
this.trusted = trusted;
1696
$([IPython.events]).trigger("trust_changed.Notebook", trusted);
1697
}
1698
return data;
1699
};
1700
1701
/**
1702
* Start an autosave timer, for periodically saving the notebook.
1703
*
1704
* @method set_autosave_interval
1705
* @param {Integer} interval the autosave interval in milliseconds
1706
*/
1707
Notebook.prototype.set_autosave_interval = function (interval) {
1708
var that = this;
1709
// clear previous interval, so we don't get simultaneous timers
1710
if (this.autosave_timer) {
1711
clearInterval(this.autosave_timer);
1712
}
1713
1714
this.autosave_interval = this.minimum_autosave_interval = interval;
1715
if (interval) {
1716
this.autosave_timer = setInterval(function() {
1717
if (that.dirty) {
1718
that.save_notebook();
1719
}
1720
}, interval);
1721
$([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1722
} else {
1723
this.autosave_timer = null;
1724
$([IPython.events]).trigger("autosave_disabled.Notebook");
1725
}
1726
};
1727
1728
/**
1729
* Save this notebook on the server. This becomes a notebook instance's
1730
* .save_notebook method *after* the entire notebook has been loaded.
1731
*
1732
* @method save_notebook
1733
*/
1734
Notebook.prototype.save_notebook = function (extra_settings) {
1735
// Create a JSON model to be sent to the server.
1736
var model = {};
1737
model.name = this.notebook_name;
1738
model.path = this.notebook_path;
1739
model.content = this.toJSON();
1740
model.content.nbformat = this.nbformat;
1741
model.content.nbformat_minor = this.nbformat_minor;
1742
// time the ajax call for autosave tuning purposes.
1743
var start = new Date().getTime();
1744
// We do the call with settings so we can set cache to false.
1745
var settings = {
1746
processData : false,
1747
cache : false,
1748
type : "PUT",
1749
data : JSON.stringify(model),
1750
headers : {'Content-Type': 'application/json'},
1751
success : $.proxy(this.save_notebook_success, this, start),
1752
error : $.proxy(this.save_notebook_error, this)
1753
};
1754
if (extra_settings) {
1755
for (var key in extra_settings) {
1756
settings[key] = extra_settings[key];
1757
}
1758
}
1759
$([IPython.events]).trigger('notebook_saving.Notebook');
1760
var url = utils.url_join_encode(
1761
this.base_url,
1762
'api/notebooks',
1763
this.notebook_path,
1764
this.notebook_name
1765
);
1766
$.ajax(url, settings);
1767
};
1768
1769
/**
1770
* Success callback for saving a notebook.
1771
*
1772
* @method save_notebook_success
1773
* @param {Integer} start the time when the save request started
1774
* @param {Object} data JSON representation of a notebook
1775
* @param {String} status Description of response status
1776
* @param {jqXHR} xhr jQuery Ajax object
1777
*/
1778
Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1779
this.set_dirty(false);
1780
$([IPython.events]).trigger('notebook_saved.Notebook');
1781
this._update_autosave_interval(start);
1782
if (this._checkpoint_after_save) {
1783
this.create_checkpoint();
1784
this._checkpoint_after_save = false;
1785
}
1786
};
1787
1788
/**
1789
* update the autosave interval based on how long the last save took
1790
*
1791
* @method _update_autosave_interval
1792
* @param {Integer} timestamp when the save request started
1793
*/
1794
Notebook.prototype._update_autosave_interval = function (start) {
1795
var duration = (new Date().getTime() - start);
1796
if (this.autosave_interval) {
1797
// new save interval: higher of 10x save duration or parameter (default 30 seconds)
1798
var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1799
// round to 10 seconds, otherwise we will be setting a new interval too often
1800
interval = 10000 * Math.round(interval / 10000);
1801
// set new interval, if it's changed
1802
if (interval != this.autosave_interval) {
1803
this.set_autosave_interval(interval);
1804
}
1805
}
1806
};
1807
1808
/**
1809
* Failure callback for saving a notebook.
1810
*
1811
* @method save_notebook_error
1812
* @param {jqXHR} xhr jQuery Ajax object
1813
* @param {String} status Description of response status
1814
* @param {String} error HTTP error message
1815
*/
1816
Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1817
$([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1818
};
1819
1820
/**
1821
* Explicitly trust the output of this notebook.
1822
*
1823
* @method trust_notebook
1824
*/
1825
Notebook.prototype.trust_notebook = function (extra_settings) {
1826
var body = $("<div>").append($("<p>")
1827
.text("A trusted IPython notebook may execute hidden malicious code ")
1828
.append($("<strong>")
1829
.append(
1830
$("<em>").text("when you open it")
1831
)
1832
).append(".").append(
1833
" Selecting trust will immediately reload this notebook in a trusted state."
1834
).append(
1835
" For more information, see the "
1836
).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1837
.text("IPython security documentation")
1838
).append(".")
1839
);
1840
1841
var nb = this;
1842
IPython.dialog.modal({
1843
title: "Trust this notebook?",
1844
body: body,
1845
1846
buttons: {
1847
Cancel : {},
1848
Trust : {
1849
class : "btn-danger",
1850
click : function () {
1851
var cells = nb.get_cells();
1852
for (var i = 0; i < cells.length; i++) {
1853
var cell = cells[i];
1854
if (cell.cell_type == 'code') {
1855
cell.output_area.trusted = true;
1856
}
1857
}
1858
$([IPython.events]).on('notebook_saved.Notebook', function () {
1859
window.location.reload();
1860
});
1861
nb.save_notebook();
1862
}
1863
}
1864
}
1865
});
1866
};
1867
1868
Notebook.prototype.new_notebook = function(){
1869
var path = this.notebook_path;
1870
var base_url = this.base_url;
1871
var settings = {
1872
processData : false,
1873
cache : false,
1874
type : "POST",
1875
dataType : "json",
1876
async : false,
1877
success : function (data, status, xhr){
1878
var notebook_name = data.name;
1879
window.open(
1880
utils.url_join_encode(
1881
base_url,
1882
'notebooks',
1883
path,
1884
notebook_name
1885
),
1886
'_blank'
1887
);
1888
},
1889
error : utils.log_ajax_error,
1890
};
1891
var url = utils.url_join_encode(
1892
base_url,
1893
'api/notebooks',
1894
path
1895
);
1896
$.ajax(url,settings);
1897
};
1898
1899
1900
Notebook.prototype.copy_notebook = function(){
1901
var path = this.notebook_path;
1902
var base_url = this.base_url;
1903
var settings = {
1904
processData : false,
1905
cache : false,
1906
type : "POST",
1907
dataType : "json",
1908
data : JSON.stringify({copy_from : this.notebook_name}),
1909
async : false,
1910
success : function (data, status, xhr) {
1911
window.open(utils.url_join_encode(
1912
base_url,
1913
'notebooks',
1914
data.path,
1915
data.name
1916
), '_blank');
1917
},
1918
error : utils.log_ajax_error,
1919
};
1920
var url = utils.url_join_encode(
1921
base_url,
1922
'api/notebooks',
1923
path
1924
);
1925
$.ajax(url,settings);
1926
};
1927
1928
Notebook.prototype.rename = function (nbname) {
1929
var that = this;
1930
if (!nbname.match(/\.ipynb$/)) {
1931
nbname = nbname + ".ipynb";
1932
}
1933
var data = {name: nbname};
1934
var settings = {
1935
processData : false,
1936
cache : false,
1937
type : "PATCH",
1938
data : JSON.stringify(data),
1939
dataType: "json",
1940
headers : {'Content-Type': 'application/json'},
1941
success : $.proxy(that.rename_success, this),
1942
error : $.proxy(that.rename_error, this)
1943
};
1944
$([IPython.events]).trigger('rename_notebook.Notebook', data);
1945
var url = utils.url_join_encode(
1946
this.base_url,
1947
'api/notebooks',
1948
this.notebook_path,
1949
this.notebook_name
1950
);
1951
$.ajax(url, settings);
1952
};
1953
1954
Notebook.prototype.delete = function () {
1955
var that = this;
1956
var settings = {
1957
processData : false,
1958
cache : false,
1959
type : "DELETE",
1960
dataType: "json",
1961
error : utils.log_ajax_error,
1962
};
1963
var url = utils.url_join_encode(
1964
this.base_url,
1965
'api/notebooks',
1966
this.notebook_path,
1967
this.notebook_name
1968
);
1969
$.ajax(url, settings);
1970
};
1971
1972
1973
Notebook.prototype.rename_success = function (json, status, xhr) {
1974
var name = this.notebook_name = json.name;
1975
var path = json.path;
1976
this.session.rename_notebook(name, path);
1977
$([IPython.events]).trigger('notebook_renamed.Notebook', json);
1978
};
1979
1980
Notebook.prototype.rename_error = function (xhr, status, error) {
1981
var that = this;
1982
var dialog = $('<div/>').append(
1983
$("<p/>").addClass("rename-message")
1984
.text('This notebook name already exists.')
1985
);
1986
$([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1987
IPython.dialog.modal({
1988
title: "Notebook Rename Error!",
1989
body: dialog,
1990
buttons : {
1991
"Cancel": {},
1992
"OK": {
1993
class: "btn-primary",
1994
click: function () {
1995
IPython.save_widget.rename_notebook();
1996
}}
1997
},
1998
open : function (event, ui) {
1999
var that = $(this);
2000
// Upon ENTER, click the OK button.
2001
that.find('input[type="text"]').keydown(function (event, ui) {
2002
if (event.which === IPython.keyboard.keycodes.enter) {
2003
that.find('.btn-primary').first().click();
2004
}
2005
});
2006
that.find('input[type="text"]').focus();
2007
}
2008
});
2009
};
2010
2011
/**
2012
* Request a notebook's data from the server.
2013
*
2014
* @method load_notebook
2015
* @param {String} notebook_name and path A notebook to load
2016
*/
2017
Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2018
var that = this;
2019
this.notebook_name = notebook_name;
2020
this.notebook_path = notebook_path;
2021
// We do the call with settings so we can set cache to false.
2022
var settings = {
2023
processData : false,
2024
cache : false,
2025
type : "GET",
2026
dataType : "json",
2027
success : $.proxy(this.load_notebook_success,this),
2028
error : $.proxy(this.load_notebook_error,this),
2029
};
2030
$([IPython.events]).trigger('notebook_loading.Notebook');
2031
var url = utils.url_join_encode(
2032
this.base_url,
2033
'api/notebooks',
2034
this.notebook_path,
2035
this.notebook_name
2036
);
2037
$.ajax(url, settings);
2038
};
2039
2040
/**
2041
* Success callback for loading a notebook from the server.
2042
*
2043
* Load notebook data from the JSON response.
2044
*
2045
* @method load_notebook_success
2046
* @param {Object} data JSON representation of a notebook
2047
* @param {String} status Description of response status
2048
* @param {jqXHR} xhr jQuery Ajax object
2049
*/
2050
Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2051
this.fromJSON(data);
2052
if (this.ncells() === 0) {
2053
this.insert_cell_below('code');
2054
this.edit_mode(0);
2055
} else {
2056
this.select(0);
2057
this.handle_command_mode(this.get_cell(0));
2058
}
2059
this.set_dirty(false);
2060
this.scroll_to_top();
2061
if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2062
var msg = "This notebook has been converted from an older " +
2063
"notebook format (v"+data.orig_nbformat+") to the current notebook " +
2064
"format (v"+data.nbformat+"). The next time you save this notebook, the " +
2065
"newer notebook format will be used and older versions of IPython " +
2066
"may not be able to read it. To keep the older version, close the " +
2067
"notebook without saving it.";
2068
IPython.dialog.modal({
2069
title : "Notebook converted",
2070
body : msg,
2071
buttons : {
2072
OK : {
2073
class : "btn-primary"
2074
}
2075
}
2076
});
2077
} else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2078
var that = this;
2079
var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2080
var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2081
var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2082
this_vs + ". You can still work with this notebook, but some features " +
2083
"introduced in later notebook versions may not be available.";
2084
2085
IPython.dialog.modal({
2086
title : "Newer Notebook",
2087
body : msg,
2088
buttons : {
2089
OK : {
2090
class : "btn-danger"
2091
}
2092
}
2093
});
2094
2095
}
2096
2097
// Create the session after the notebook is completely loaded to prevent
2098
// code execution upon loading, which is a security risk.
2099
if (this.session === null) {
2100
this.start_session();
2101
}
2102
// load our checkpoint list
2103
this.list_checkpoints();
2104
2105
// load toolbar state
2106
if (this.metadata.celltoolbar) {
2107
IPython.CellToolbar.global_show();
2108
IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2109
} else {
2110
IPython.CellToolbar.global_hide();
2111
}
2112
2113
// now that we're fully loaded, it is safe to restore save functionality
2114
delete(this.save_notebook);
2115
$([IPython.events]).trigger('notebook_loaded.Notebook');
2116
};
2117
2118
/**
2119
* Failure callback for loading a notebook from the server.
2120
*
2121
* @method load_notebook_error
2122
* @param {jqXHR} xhr jQuery Ajax object
2123
* @param {String} status Description of response status
2124
* @param {String} error HTTP error message
2125
*/
2126
Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2127
$([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2128
utils.log_ajax_error(xhr, status, error);
2129
var msg = $("<div>");
2130
if (xhr.status === 400) {
2131
msg.text(utils.ajax_error_msg(xhr));
2132
} else if (xhr.status === 500) {
2133
msg.text("An unknown error occurred while loading this notebook. " +
2134
"This version can load notebook formats " +
2135
"v" + this.nbformat + " or earlier. See the server log for details.");
2136
}
2137
IPython.dialog.modal({
2138
title: "Error loading notebook",
2139
body : msg,
2140
buttons : {
2141
"OK": {}
2142
}
2143
});
2144
};
2145
2146
/********************* checkpoint-related *********************/
2147
2148
/**
2149
* Save the notebook then immediately create a checkpoint.
2150
*
2151
* @method save_checkpoint
2152
*/
2153
Notebook.prototype.save_checkpoint = function () {
2154
this._checkpoint_after_save = true;
2155
this.save_notebook();
2156
};
2157
2158
/**
2159
* Add a checkpoint for this notebook.
2160
* for use as a callback from checkpoint creation.
2161
*
2162
* @method add_checkpoint
2163
*/
2164
Notebook.prototype.add_checkpoint = function (checkpoint) {
2165
var found = false;
2166
for (var i = 0; i < this.checkpoints.length; i++) {
2167
var existing = this.checkpoints[i];
2168
if (existing.id == checkpoint.id) {
2169
found = true;
2170
this.checkpoints[i] = checkpoint;
2171
break;
2172
}
2173
}
2174
if (!found) {
2175
this.checkpoints.push(checkpoint);
2176
}
2177
this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2178
};
2179
2180
/**
2181
* List checkpoints for this notebook.
2182
*
2183
* @method list_checkpoints
2184
*/
2185
Notebook.prototype.list_checkpoints = function () {
2186
var url = utils.url_join_encode(
2187
this.base_url,
2188
'api/notebooks',
2189
this.notebook_path,
2190
this.notebook_name,
2191
'checkpoints'
2192
);
2193
$.get(url).done(
2194
$.proxy(this.list_checkpoints_success, this)
2195
).fail(
2196
$.proxy(this.list_checkpoints_error, this)
2197
);
2198
};
2199
2200
/**
2201
* Success callback for listing checkpoints.
2202
*
2203
* @method list_checkpoint_success
2204
* @param {Object} data JSON representation of a checkpoint
2205
* @param {String} status Description of response status
2206
* @param {jqXHR} xhr jQuery Ajax object
2207
*/
2208
Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2209
data = $.parseJSON(data);
2210
this.checkpoints = data;
2211
if (data.length) {
2212
this.last_checkpoint = data[data.length - 1];
2213
} else {
2214
this.last_checkpoint = null;
2215
}
2216
$([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2217
};
2218
2219
/**
2220
* Failure callback for listing a checkpoint.
2221
*
2222
* @method list_checkpoint_error
2223
* @param {jqXHR} xhr jQuery Ajax object
2224
* @param {String} status Description of response status
2225
* @param {String} error_msg HTTP error message
2226
*/
2227
Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2228
$([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2229
};
2230
2231
/**
2232
* Create a checkpoint of this notebook on the server from the most recent save.
2233
*
2234
* @method create_checkpoint
2235
*/
2236
Notebook.prototype.create_checkpoint = function () {
2237
var url = utils.url_join_encode(
2238
this.base_url,
2239
'api/notebooks',
2240
this.notebook_path,
2241
this.notebook_name,
2242
'checkpoints'
2243
);
2244
$.post(url).done(
2245
$.proxy(this.create_checkpoint_success, this)
2246
).fail(
2247
$.proxy(this.create_checkpoint_error, this)
2248
);
2249
};
2250
2251
/**
2252
* Success callback for creating a checkpoint.
2253
*
2254
* @method create_checkpoint_success
2255
* @param {Object} data JSON representation of a checkpoint
2256
* @param {String} status Description of response status
2257
* @param {jqXHR} xhr jQuery Ajax object
2258
*/
2259
Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2260
data = $.parseJSON(data);
2261
this.add_checkpoint(data);
2262
$([IPython.events]).trigger('checkpoint_created.Notebook', data);
2263
};
2264
2265
/**
2266
* Failure callback for creating a checkpoint.
2267
*
2268
* @method create_checkpoint_error
2269
* @param {jqXHR} xhr jQuery Ajax object
2270
* @param {String} status Description of response status
2271
* @param {String} error_msg HTTP error message
2272
*/
2273
Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2274
$([IPython.events]).trigger('checkpoint_failed.Notebook');
2275
};
2276
2277
Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2278
var that = this;
2279
checkpoint = checkpoint || this.last_checkpoint;
2280
if ( ! checkpoint ) {
2281
console.log("restore dialog, but no checkpoint to restore to!");
2282
return;
2283
}
2284
var body = $('<div/>').append(
2285
$('<p/>').addClass("p-space").text(
2286
"Are you sure you want to revert the notebook to " +
2287
"the latest checkpoint?"
2288
).append(
2289
$("<strong/>").text(
2290
" This cannot be undone."
2291
)
2292
)
2293
).append(
2294
$('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2295
).append(
2296
$('<p/>').addClass("p-space").text(
2297
Date(checkpoint.last_modified)
2298
).css("text-align", "center")
2299
);
2300
2301
IPython.dialog.modal({
2302
title : "Revert notebook to checkpoint",
2303
body : body,
2304
buttons : {
2305
Revert : {
2306
class : "btn-danger",
2307
click : function () {
2308
that.restore_checkpoint(checkpoint.id);
2309
}
2310
},
2311
Cancel : {}
2312
}
2313
});
2314
};
2315
2316
/**
2317
* Restore the notebook to a checkpoint state.
2318
*
2319
* @method restore_checkpoint
2320
* @param {String} checkpoint ID
2321
*/
2322
Notebook.prototype.restore_checkpoint = function (checkpoint) {
2323
$([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2324
var url = utils.url_join_encode(
2325
this.base_url,
2326
'api/notebooks',
2327
this.notebook_path,
2328
this.notebook_name,
2329
'checkpoints',
2330
checkpoint
2331
);
2332
$.post(url).done(
2333
$.proxy(this.restore_checkpoint_success, this)
2334
).fail(
2335
$.proxy(this.restore_checkpoint_error, this)
2336
);
2337
};
2338
2339
/**
2340
* Success callback for restoring a notebook to a checkpoint.
2341
*
2342
* @method restore_checkpoint_success
2343
* @param {Object} data (ignored, should be empty)
2344
* @param {String} status Description of response status
2345
* @param {jqXHR} xhr jQuery Ajax object
2346
*/
2347
Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2348
$([IPython.events]).trigger('checkpoint_restored.Notebook');
2349
this.load_notebook(this.notebook_name, this.notebook_path);
2350
};
2351
2352
/**
2353
* Failure callback for restoring a notebook to a checkpoint.
2354
*
2355
* @method restore_checkpoint_error
2356
* @param {jqXHR} xhr jQuery Ajax object
2357
* @param {String} status Description of response status
2358
* @param {String} error_msg HTTP error message
2359
*/
2360
Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2361
$([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2362
};
2363
2364
/**
2365
* Delete a notebook checkpoint.
2366
*
2367
* @method delete_checkpoint
2368
* @param {String} checkpoint ID
2369
*/
2370
Notebook.prototype.delete_checkpoint = function (checkpoint) {
2371
$([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2372
var url = utils.url_join_encode(
2373
this.base_url,
2374
'api/notebooks',
2375
this.notebook_path,
2376
this.notebook_name,
2377
'checkpoints',
2378
checkpoint
2379
);
2380
$.ajax(url, {
2381
type: 'DELETE',
2382
success: $.proxy(this.delete_checkpoint_success, this),
2383
error: $.proxy(this.delete_checkpoint_error, this)
2384
});
2385
};
2386
2387
/**
2388
* Success callback for deleting a notebook checkpoint
2389
*
2390
* @method delete_checkpoint_success
2391
* @param {Object} data (ignored, should be empty)
2392
* @param {String} status Description of response status
2393
* @param {jqXHR} xhr jQuery Ajax object
2394
*/
2395
Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2396
$([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2397
this.load_notebook(this.notebook_name, this.notebook_path);
2398
};
2399
2400
/**
2401
* Failure callback for deleting a notebook checkpoint.
2402
*
2403
* @method delete_checkpoint_error
2404
* @param {jqXHR} xhr jQuery Ajax object
2405
* @param {String} status Description of response status
2406
* @param {String} error HTTP error message
2407
*/
2408
Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2409
$([IPython.events]).trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2410
};
2411
2412
2413
IPython.Notebook = Notebook;
2414
2415
2416
return IPython;
2417
2418
}(IPython));
2419
2420
2421