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