##// END OF EJS Templates
Address review comments
Thomas Kluyver -
Show More
@@ -1,80 +1,81
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 // Make the object globally available for user convenience & inspection
23 IPython.kernelselector = this;
22 24 };
23 25
24 26 KernelSelector.prototype.request_kernelspecs = function() {
25 27 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
26 $.ajax(url, {success: $.proxy(this.got_kernelspecs, this)});
28 $.ajax(url, {success: $.proxy(this._got_kernelspecs, this)});
27 29 };
28 30
29 KernelSelector.prototype.got_kernelspecs = function(data, status, xhr) {
31 KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) {
30 32 this.kernelspecs = {};
31 33 var menu = this.element.find("#kernel_selector");
32 34 var change_kernel_submenu = $("#menu-change-kernel-submenu");
33 console.log(change_kernel_submenu);
34 35 for (var i = 0; i < data.length; i++) {
35 36 var ks = data[i];
36 37 this.kernelspecs[ks.name] = ks;
37 38 var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
38 39 .attr('href', '#')
39 40 .click($.proxy(this.change_kernel, this, ks.name))
40 41 .text(ks.display_name));
41 42 menu.append(ksentry);
42 43
43 44 var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
44 45 .attr('href', '#')
45 46 .click($.proxy(this.change_kernel, this, ks.name))
46 47 .text(ks.display_name));
47 48 change_kernel_submenu.append(ks_submenu_entry);
48 49 }
49 50 };
50 51
51 52 KernelSelector.prototype.change_kernel = function(kernel_name) {
52 53 if (kernel_name === this.current_selection) {
53 54 return;
54 55 }
55 56 var ks = this.kernelspecs[kernel_name];
56 57 this.events.trigger('spec_changed.Kernel', ks);
57 58 this.notebook.session.delete();
58 59 this.notebook.start_session(kernel_name);
59 60 };
60 61
61 62 KernelSelector.prototype.bind_events = function() {
62 63 var that = this;
63 64 this.events.on('spec_changed.Kernel', function(event, data) {
64 65 that.current_selection = data.name;
65 66 that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
66 67 });
67 68
68 69 this.events.on('started.Session', function(events, session) {
69 70 if (session.kernel_name !== that.current_selection) {
70 71 // If we created a 'python' session, we only know if it's Python
71 72 // 3 or 2 on the server's reply, so we fire the event again to
72 73 // set things up.
73 74 var ks = that.kernelspecs[session.kernel_name];
74 75 that.events.trigger('spec_changed.Kernel', ks);
75 76 }
76 77 });
77 78 };
78 79
79 80 return {'KernelSelector': KernelSelector};
80 81 });
@@ -1,2599 +1,2598
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 console.log("start_session", kernel_name);
1550 1549 this.session = new session.Session({
1551 1550 base_url: this.base_url,
1552 1551 ws_url: this.ws_url,
1553 1552 notebook_path: this.notebook_path,
1554 1553 notebook_name: this.notebook_name,
1555 1554 // For now, create all sessions with the 'python' kernel, which is the
1556 1555 // default. Later, the user will be able to select kernels. This is
1557 1556 // overridden if KernelManager.kernel_cmd is specified for the server.
1558 1557 kernel_name: kernel_name,
1559 1558 notebook: this});
1560 1559
1561 1560 this.session.start($.proxy(this._session_started, this));
1562 1561 };
1563 1562
1564 1563
1565 1564 /**
1566 1565 * Once a session is started, link the code cells to the kernel and pass the
1567 1566 * comm manager to the widget manager
1568 1567 *
1569 1568 */
1570 1569 Notebook.prototype._session_started = function(){
1571 1570 this.kernel = this.session.kernel;
1572 1571 var ncells = this.ncells();
1573 1572 for (var i=0; i<ncells; i++) {
1574 1573 var cell = this.get_cell(i);
1575 1574 if (cell instanceof codecell.CodeCell) {
1576 1575 cell.set_kernel(this.session.kernel);
1577 1576 }
1578 1577 }
1579 1578 };
1580 1579
1581 1580 /**
1582 1581 * Prompt the user to restart the IPython kernel.
1583 1582 *
1584 1583 * @method restart_kernel
1585 1584 */
1586 1585 Notebook.prototype.restart_kernel = function () {
1587 1586 var that = this;
1588 1587 dialog.modal({
1589 1588 notebook: this,
1590 1589 keyboard_manager: this.keyboard_manager,
1591 1590 title : "Restart kernel or continue running?",
1592 1591 body : $("<p/>").text(
1593 1592 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1594 1593 ),
1595 1594 buttons : {
1596 1595 "Continue running" : {},
1597 1596 "Restart" : {
1598 1597 "class" : "btn-danger",
1599 1598 "click" : function() {
1600 1599 that.session.restart_kernel();
1601 1600 }
1602 1601 }
1603 1602 }
1604 1603 });
1605 1604 };
1606 1605
1607 1606 /**
1608 1607 * Execute or render cell outputs and go into command mode.
1609 1608 *
1610 1609 * @method execute_cell
1611 1610 */
1612 1611 Notebook.prototype.execute_cell = function () {
1613 1612 // mode = shift, ctrl, alt
1614 1613 var cell = this.get_selected_cell();
1615 1614 var cell_index = this.find_cell_index(cell);
1616 1615
1617 1616 cell.execute();
1618 1617 this.command_mode();
1619 1618 this.set_dirty(true);
1620 1619 };
1621 1620
1622 1621 /**
1623 1622 * Execute or render cell outputs and insert a new cell below.
1624 1623 *
1625 1624 * @method execute_cell_and_insert_below
1626 1625 */
1627 1626 Notebook.prototype.execute_cell_and_insert_below = function () {
1628 1627 var cell = this.get_selected_cell();
1629 1628 var cell_index = this.find_cell_index(cell);
1630 1629
1631 1630 cell.execute();
1632 1631
1633 1632 // If we are at the end always insert a new cell and return
1634 1633 if (cell_index === (this.ncells()-1)) {
1635 1634 this.command_mode();
1636 1635 this.insert_cell_below();
1637 1636 this.select(cell_index+1);
1638 1637 this.edit_mode();
1639 1638 this.scroll_to_bottom();
1640 1639 this.set_dirty(true);
1641 1640 return;
1642 1641 }
1643 1642
1644 1643 this.command_mode();
1645 1644 this.insert_cell_below();
1646 1645 this.select(cell_index+1);
1647 1646 this.edit_mode();
1648 1647 this.set_dirty(true);
1649 1648 };
1650 1649
1651 1650 /**
1652 1651 * Execute or render cell outputs and select the next cell.
1653 1652 *
1654 1653 * @method execute_cell_and_select_below
1655 1654 */
1656 1655 Notebook.prototype.execute_cell_and_select_below = function () {
1657 1656
1658 1657 var cell = this.get_selected_cell();
1659 1658 var cell_index = this.find_cell_index(cell);
1660 1659
1661 1660 cell.execute();
1662 1661
1663 1662 // If we are at the end always insert a new cell and return
1664 1663 if (cell_index === (this.ncells()-1)) {
1665 1664 this.command_mode();
1666 1665 this.insert_cell_below();
1667 1666 this.select(cell_index+1);
1668 1667 this.edit_mode();
1669 1668 this.scroll_to_bottom();
1670 1669 this.set_dirty(true);
1671 1670 return;
1672 1671 }
1673 1672
1674 1673 this.command_mode();
1675 1674 this.select(cell_index+1);
1676 1675 this.focus_cell();
1677 1676 this.set_dirty(true);
1678 1677 };
1679 1678
1680 1679 /**
1681 1680 * Execute all cells below the selected cell.
1682 1681 *
1683 1682 * @method execute_cells_below
1684 1683 */
1685 1684 Notebook.prototype.execute_cells_below = function () {
1686 1685 this.execute_cell_range(this.get_selected_index(), this.ncells());
1687 1686 this.scroll_to_bottom();
1688 1687 };
1689 1688
1690 1689 /**
1691 1690 * Execute all cells above the selected cell.
1692 1691 *
1693 1692 * @method execute_cells_above
1694 1693 */
1695 1694 Notebook.prototype.execute_cells_above = function () {
1696 1695 this.execute_cell_range(0, this.get_selected_index());
1697 1696 };
1698 1697
1699 1698 /**
1700 1699 * Execute all cells.
1701 1700 *
1702 1701 * @method execute_all_cells
1703 1702 */
1704 1703 Notebook.prototype.execute_all_cells = function () {
1705 1704 this.execute_cell_range(0, this.ncells());
1706 1705 this.scroll_to_bottom();
1707 1706 };
1708 1707
1709 1708 /**
1710 1709 * Execute a contiguous range of cells.
1711 1710 *
1712 1711 * @method execute_cell_range
1713 1712 * @param {Number} start Index of the first cell to execute (inclusive)
1714 1713 * @param {Number} end Index of the last cell to execute (exclusive)
1715 1714 */
1716 1715 Notebook.prototype.execute_cell_range = function (start, end) {
1717 1716 this.command_mode();
1718 1717 for (var i=start; i<end; i++) {
1719 1718 this.select(i);
1720 1719 this.execute_cell();
1721 1720 }
1722 1721 };
1723 1722
1724 1723 // Persistance and loading
1725 1724
1726 1725 /**
1727 1726 * Getter method for this notebook's name.
1728 1727 *
1729 1728 * @method get_notebook_name
1730 1729 * @return {String} This notebook's name (excluding file extension)
1731 1730 */
1732 1731 Notebook.prototype.get_notebook_name = function () {
1733 1732 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1734 1733 return nbname;
1735 1734 };
1736 1735
1737 1736 /**
1738 1737 * Setter method for this notebook's name.
1739 1738 *
1740 1739 * @method set_notebook_name
1741 1740 * @param {String} name A new name for this notebook
1742 1741 */
1743 1742 Notebook.prototype.set_notebook_name = function (name) {
1744 1743 this.notebook_name = name;
1745 1744 };
1746 1745
1747 1746 /**
1748 1747 * Check that a notebook's name is valid.
1749 1748 *
1750 1749 * @method test_notebook_name
1751 1750 * @param {String} nbname A name for this notebook
1752 1751 * @return {Boolean} True if the name is valid, false if invalid
1753 1752 */
1754 1753 Notebook.prototype.test_notebook_name = function (nbname) {
1755 1754 nbname = nbname || '';
1756 1755 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1757 1756 return true;
1758 1757 } else {
1759 1758 return false;
1760 1759 }
1761 1760 };
1762 1761
1763 1762 /**
1764 1763 * Load a notebook from JSON (.ipynb).
1765 1764 *
1766 1765 * This currently handles one worksheet: others are deleted.
1767 1766 *
1768 1767 * @method fromJSON
1769 1768 * @param {Object} data JSON representation of a notebook
1770 1769 */
1771 1770 Notebook.prototype.fromJSON = function (data) {
1772 1771 var content = data.content;
1773 1772 var ncells = this.ncells();
1774 1773 var i;
1775 1774 for (i=0; i<ncells; i++) {
1776 1775 // Always delete cell 0 as they get renumbered as they are deleted.
1777 1776 this.delete_cell(0);
1778 1777 }
1779 1778 // Save the metadata and name.
1780 1779 this.metadata = content.metadata;
1781 1780 this.notebook_name = data.name;
1782 1781 var trusted = true;
1783 1782
1784 1783 // Trigger an event changing the kernel spec - this will set the default
1785 1784 // codemirror mode
1786 1785 if (this.metadata.kernelspec !== undefined) {
1787 1786 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1788 1787 }
1789 1788
1790 1789 // Only handle 1 worksheet for now.
1791 1790 var worksheet = content.worksheets[0];
1792 1791 if (worksheet !== undefined) {
1793 1792 if (worksheet.metadata) {
1794 1793 this.worksheet_metadata = worksheet.metadata;
1795 1794 }
1796 1795 var new_cells = worksheet.cells;
1797 1796 ncells = new_cells.length;
1798 1797 var cell_data = null;
1799 1798 var new_cell = null;
1800 1799 for (i=0; i<ncells; i++) {
1801 1800 cell_data = new_cells[i];
1802 1801 // VERSIONHACK: plaintext -> raw
1803 1802 // handle never-released plaintext name for raw cells
1804 1803 if (cell_data.cell_type === 'plaintext'){
1805 1804 cell_data.cell_type = 'raw';
1806 1805 }
1807 1806
1808 1807 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1809 1808 new_cell.fromJSON(cell_data);
1810 1809 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1811 1810 trusted = false;
1812 1811 }
1813 1812 }
1814 1813 }
1815 1814 if (trusted != this.trusted) {
1816 1815 this.trusted = trusted;
1817 1816 this.events.trigger("trust_changed.Notebook", trusted);
1818 1817 }
1819 1818 if (content.worksheets.length > 1) {
1820 1819 dialog.modal({
1821 1820 notebook: this,
1822 1821 keyboard_manager: this.keyboard_manager,
1823 1822 title : "Multiple worksheets",
1824 1823 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1825 1824 "but this version of IPython can only handle the first. " +
1826 1825 "If you save this notebook, worksheets after the first will be lost.",
1827 1826 buttons : {
1828 1827 OK : {
1829 1828 class : "btn-danger"
1830 1829 }
1831 1830 }
1832 1831 });
1833 1832 }
1834 1833 };
1835 1834
1836 1835 /**
1837 1836 * Dump this notebook into a JSON-friendly object.
1838 1837 *
1839 1838 * @method toJSON
1840 1839 * @return {Object} A JSON-friendly representation of this notebook.
1841 1840 */
1842 1841 Notebook.prototype.toJSON = function () {
1843 1842 var cells = this.get_cells();
1844 1843 var ncells = cells.length;
1845 1844 var cell_array = new Array(ncells);
1846 1845 var trusted = true;
1847 1846 for (var i=0; i<ncells; i++) {
1848 1847 var cell = cells[i];
1849 1848 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1850 1849 trusted = false;
1851 1850 }
1852 1851 cell_array[i] = cell.toJSON();
1853 1852 }
1854 1853 var data = {
1855 1854 // Only handle 1 worksheet for now.
1856 1855 worksheets : [{
1857 1856 cells: cell_array,
1858 1857 metadata: this.worksheet_metadata
1859 1858 }],
1860 1859 metadata : this.metadata
1861 1860 };
1862 1861 if (trusted != this.trusted) {
1863 1862 this.trusted = trusted;
1864 1863 this.events.trigger("trust_changed.Notebook", trusted);
1865 1864 }
1866 1865 return data;
1867 1866 };
1868 1867
1869 1868 /**
1870 1869 * Start an autosave timer, for periodically saving the notebook.
1871 1870 *
1872 1871 * @method set_autosave_interval
1873 1872 * @param {Integer} interval the autosave interval in milliseconds
1874 1873 */
1875 1874 Notebook.prototype.set_autosave_interval = function (interval) {
1876 1875 var that = this;
1877 1876 // clear previous interval, so we don't get simultaneous timers
1878 1877 if (this.autosave_timer) {
1879 1878 clearInterval(this.autosave_timer);
1880 1879 }
1881 1880
1882 1881 this.autosave_interval = this.minimum_autosave_interval = interval;
1883 1882 if (interval) {
1884 1883 this.autosave_timer = setInterval(function() {
1885 1884 if (that.dirty) {
1886 1885 that.save_notebook();
1887 1886 }
1888 1887 }, interval);
1889 1888 this.events.trigger("autosave_enabled.Notebook", interval);
1890 1889 } else {
1891 1890 this.autosave_timer = null;
1892 1891 this.events.trigger("autosave_disabled.Notebook");
1893 1892 }
1894 1893 };
1895 1894
1896 1895 /**
1897 1896 * Save this notebook on the server. This becomes a notebook instance's
1898 1897 * .save_notebook method *after* the entire notebook has been loaded.
1899 1898 *
1900 1899 * @method save_notebook
1901 1900 */
1902 1901 Notebook.prototype.save_notebook = function (extra_settings) {
1903 1902 // Create a JSON model to be sent to the server.
1904 1903 var model = {};
1905 1904 model.name = this.notebook_name;
1906 1905 model.path = this.notebook_path;
1907 1906 model.content = this.toJSON();
1908 1907 model.content.nbformat = this.nbformat;
1909 1908 model.content.nbformat_minor = this.nbformat_minor;
1910 1909 // time the ajax call for autosave tuning purposes.
1911 1910 var start = new Date().getTime();
1912 1911 // We do the call with settings so we can set cache to false.
1913 1912 var settings = {
1914 1913 processData : false,
1915 1914 cache : false,
1916 1915 type : "PUT",
1917 1916 data : JSON.stringify(model),
1918 1917 headers : {'Content-Type': 'application/json'},
1919 1918 success : $.proxy(this.save_notebook_success, this, start),
1920 1919 error : $.proxy(this.save_notebook_error, this)
1921 1920 };
1922 1921 if (extra_settings) {
1923 1922 for (var key in extra_settings) {
1924 1923 settings[key] = extra_settings[key];
1925 1924 }
1926 1925 }
1927 1926 this.events.trigger('notebook_saving.Notebook');
1928 1927 var url = utils.url_join_encode(
1929 1928 this.base_url,
1930 1929 'api/notebooks',
1931 1930 this.notebook_path,
1932 1931 this.notebook_name
1933 1932 );
1934 1933 $.ajax(url, settings);
1935 1934 };
1936 1935
1937 1936 /**
1938 1937 * Success callback for saving a notebook.
1939 1938 *
1940 1939 * @method save_notebook_success
1941 1940 * @param {Integer} start the time when the save request started
1942 1941 * @param {Object} data JSON representation of a notebook
1943 1942 * @param {String} status Description of response status
1944 1943 * @param {jqXHR} xhr jQuery Ajax object
1945 1944 */
1946 1945 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1947 1946 this.set_dirty(false);
1948 1947 this.events.trigger('notebook_saved.Notebook');
1949 1948 this._update_autosave_interval(start);
1950 1949 if (this._checkpoint_after_save) {
1951 1950 this.create_checkpoint();
1952 1951 this._checkpoint_after_save = false;
1953 1952 }
1954 1953 };
1955 1954
1956 1955 /**
1957 1956 * update the autosave interval based on how long the last save took
1958 1957 *
1959 1958 * @method _update_autosave_interval
1960 1959 * @param {Integer} timestamp when the save request started
1961 1960 */
1962 1961 Notebook.prototype._update_autosave_interval = function (start) {
1963 1962 var duration = (new Date().getTime() - start);
1964 1963 if (this.autosave_interval) {
1965 1964 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1966 1965 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1967 1966 // round to 10 seconds, otherwise we will be setting a new interval too often
1968 1967 interval = 10000 * Math.round(interval / 10000);
1969 1968 // set new interval, if it's changed
1970 1969 if (interval != this.autosave_interval) {
1971 1970 this.set_autosave_interval(interval);
1972 1971 }
1973 1972 }
1974 1973 };
1975 1974
1976 1975 /**
1977 1976 * Failure callback for saving a notebook.
1978 1977 *
1979 1978 * @method save_notebook_error
1980 1979 * @param {jqXHR} xhr jQuery Ajax object
1981 1980 * @param {String} status Description of response status
1982 1981 * @param {String} error HTTP error message
1983 1982 */
1984 1983 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1985 1984 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1986 1985 };
1987 1986
1988 1987 /**
1989 1988 * Explicitly trust the output of this notebook.
1990 1989 *
1991 1990 * @method trust_notebook
1992 1991 */
1993 1992 Notebook.prototype.trust_notebook = function (extra_settings) {
1994 1993 var body = $("<div>").append($("<p>")
1995 1994 .text("A trusted IPython notebook may execute hidden malicious code ")
1996 1995 .append($("<strong>")
1997 1996 .append(
1998 1997 $("<em>").text("when you open it")
1999 1998 )
2000 1999 ).append(".").append(
2001 2000 " Selecting trust will immediately reload this notebook in a trusted state."
2002 2001 ).append(
2003 2002 " For more information, see the "
2004 2003 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2005 2004 .text("IPython security documentation")
2006 2005 ).append(".")
2007 2006 );
2008 2007
2009 2008 var nb = this;
2010 2009 dialog.modal({
2011 2010 notebook: this,
2012 2011 keyboard_manager: this.keyboard_manager,
2013 2012 title: "Trust this notebook?",
2014 2013 body: body,
2015 2014
2016 2015 buttons: {
2017 2016 Cancel : {},
2018 2017 Trust : {
2019 2018 class : "btn-danger",
2020 2019 click : function () {
2021 2020 var cells = nb.get_cells();
2022 2021 for (var i = 0; i < cells.length; i++) {
2023 2022 var cell = cells[i];
2024 2023 if (cell.cell_type == 'code') {
2025 2024 cell.output_area.trusted = true;
2026 2025 }
2027 2026 }
2028 2027 this.events.on('notebook_saved.Notebook', function () {
2029 2028 window.location.reload();
2030 2029 });
2031 2030 nb.save_notebook();
2032 2031 }
2033 2032 }
2034 2033 }
2035 2034 });
2036 2035 };
2037 2036
2038 2037 Notebook.prototype.new_notebook = function(){
2039 2038 var path = this.notebook_path;
2040 2039 var base_url = this.base_url;
2041 2040 var settings = {
2042 2041 processData : false,
2043 2042 cache : false,
2044 2043 type : "POST",
2045 2044 dataType : "json",
2046 2045 async : false,
2047 2046 success : function (data, status, xhr){
2048 2047 var notebook_name = data.name;
2049 2048 window.open(
2050 2049 utils.url_join_encode(
2051 2050 base_url,
2052 2051 'notebooks',
2053 2052 path,
2054 2053 notebook_name
2055 2054 ),
2056 2055 '_blank'
2057 2056 );
2058 2057 },
2059 2058 error : utils.log_ajax_error,
2060 2059 };
2061 2060 var url = utils.url_join_encode(
2062 2061 base_url,
2063 2062 'api/notebooks',
2064 2063 path
2065 2064 );
2066 2065 $.ajax(url,settings);
2067 2066 };
2068 2067
2069 2068
2070 2069 Notebook.prototype.copy_notebook = function(){
2071 2070 var path = this.notebook_path;
2072 2071 var base_url = this.base_url;
2073 2072 var settings = {
2074 2073 processData : false,
2075 2074 cache : false,
2076 2075 type : "POST",
2077 2076 dataType : "json",
2078 2077 data : JSON.stringify({copy_from : this.notebook_name}),
2079 2078 async : false,
2080 2079 success : function (data, status, xhr) {
2081 2080 window.open(utils.url_join_encode(
2082 2081 base_url,
2083 2082 'notebooks',
2084 2083 data.path,
2085 2084 data.name
2086 2085 ), '_blank');
2087 2086 },
2088 2087 error : utils.log_ajax_error,
2089 2088 };
2090 2089 var url = utils.url_join_encode(
2091 2090 base_url,
2092 2091 'api/notebooks',
2093 2092 path
2094 2093 );
2095 2094 $.ajax(url,settings);
2096 2095 };
2097 2096
2098 2097 Notebook.prototype.rename = function (nbname) {
2099 2098 var that = this;
2100 2099 if (!nbname.match(/\.ipynb$/)) {
2101 2100 nbname = nbname + ".ipynb";
2102 2101 }
2103 2102 var data = {name: nbname};
2104 2103 var settings = {
2105 2104 processData : false,
2106 2105 cache : false,
2107 2106 type : "PATCH",
2108 2107 data : JSON.stringify(data),
2109 2108 dataType: "json",
2110 2109 headers : {'Content-Type': 'application/json'},
2111 2110 success : $.proxy(that.rename_success, this),
2112 2111 error : $.proxy(that.rename_error, this)
2113 2112 };
2114 2113 this.events.trigger('rename_notebook.Notebook', data);
2115 2114 var url = utils.url_join_encode(
2116 2115 this.base_url,
2117 2116 'api/notebooks',
2118 2117 this.notebook_path,
2119 2118 this.notebook_name
2120 2119 );
2121 2120 $.ajax(url, settings);
2122 2121 };
2123 2122
2124 2123 Notebook.prototype.delete = function () {
2125 2124 var that = this;
2126 2125 var settings = {
2127 2126 processData : false,
2128 2127 cache : false,
2129 2128 type : "DELETE",
2130 2129 dataType: "json",
2131 2130 error : utils.log_ajax_error,
2132 2131 };
2133 2132 var url = utils.url_join_encode(
2134 2133 this.base_url,
2135 2134 'api/notebooks',
2136 2135 this.notebook_path,
2137 2136 this.notebook_name
2138 2137 );
2139 2138 $.ajax(url, settings);
2140 2139 };
2141 2140
2142 2141
2143 2142 Notebook.prototype.rename_success = function (json, status, xhr) {
2144 2143 var name = this.notebook_name = json.name;
2145 2144 var path = json.path;
2146 2145 this.session.rename_notebook(name, path);
2147 2146 this.events.trigger('notebook_renamed.Notebook', json);
2148 2147 };
2149 2148
2150 2149 Notebook.prototype.rename_error = function (xhr, status, error) {
2151 2150 var that = this;
2152 2151 var dialog_body = $('<div/>').append(
2153 2152 $("<p/>").addClass("rename-message")
2154 2153 .text('This notebook name already exists.')
2155 2154 );
2156 2155 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2157 2156 dialog.modal({
2158 2157 notebook: this,
2159 2158 keyboard_manager: this.keyboard_manager,
2160 2159 title: "Notebook Rename Error!",
2161 2160 body: dialog_body,
2162 2161 buttons : {
2163 2162 "Cancel": {},
2164 2163 "OK": {
2165 2164 class: "btn-primary",
2166 2165 click: function () {
2167 2166 this.save_widget.rename_notebook({notebook:that});
2168 2167 }}
2169 2168 },
2170 2169 open : function (event, ui) {
2171 2170 var that = $(this);
2172 2171 // Upon ENTER, click the OK button.
2173 2172 that.find('input[type="text"]').keydown(function (event, ui) {
2174 2173 if (event.which === this.keyboard.keycodes.enter) {
2175 2174 that.find('.btn-primary').first().click();
2176 2175 }
2177 2176 });
2178 2177 that.find('input[type="text"]').focus();
2179 2178 }
2180 2179 });
2181 2180 };
2182 2181
2183 2182 /**
2184 2183 * Request a notebook's data from the server.
2185 2184 *
2186 2185 * @method load_notebook
2187 2186 * @param {String} notebook_name and path A notebook to load
2188 2187 */
2189 2188 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2190 2189 var that = this;
2191 2190 this.notebook_name = notebook_name;
2192 2191 this.notebook_path = notebook_path;
2193 2192 // We do the call with settings so we can set cache to false.
2194 2193 var settings = {
2195 2194 processData : false,
2196 2195 cache : false,
2197 2196 type : "GET",
2198 2197 dataType : "json",
2199 2198 success : $.proxy(this.load_notebook_success,this),
2200 2199 error : $.proxy(this.load_notebook_error,this),
2201 2200 };
2202 2201 this.events.trigger('notebook_loading.Notebook');
2203 2202 var url = utils.url_join_encode(
2204 2203 this.base_url,
2205 2204 'api/notebooks',
2206 2205 this.notebook_path,
2207 2206 this.notebook_name
2208 2207 );
2209 2208 $.ajax(url, settings);
2210 2209 };
2211 2210
2212 2211 /**
2213 2212 * Success callback for loading a notebook from the server.
2214 2213 *
2215 2214 * Load notebook data from the JSON response.
2216 2215 *
2217 2216 * @method load_notebook_success
2218 2217 * @param {Object} data JSON representation of a notebook
2219 2218 * @param {String} status Description of response status
2220 2219 * @param {jqXHR} xhr jQuery Ajax object
2221 2220 */
2222 2221 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2223 2222 this.fromJSON(data);
2224 2223 if (this.ncells() === 0) {
2225 2224 this.insert_cell_below('code');
2226 2225 this.edit_mode(0);
2227 2226 } else {
2228 2227 this.select(0);
2229 2228 this.handle_command_mode(this.get_cell(0));
2230 2229 }
2231 2230 this.set_dirty(false);
2232 2231 this.scroll_to_top();
2233 2232 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2234 2233 var msg = "This notebook has been converted from an older " +
2235 2234 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2236 2235 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2237 2236 "newer notebook format will be used and older versions of IPython " +
2238 2237 "may not be able to read it. To keep the older version, close the " +
2239 2238 "notebook without saving it.";
2240 2239 dialog.modal({
2241 2240 notebook: this,
2242 2241 keyboard_manager: this.keyboard_manager,
2243 2242 title : "Notebook converted",
2244 2243 body : msg,
2245 2244 buttons : {
2246 2245 OK : {
2247 2246 class : "btn-primary"
2248 2247 }
2249 2248 }
2250 2249 });
2251 2250 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2252 2251 var that = this;
2253 2252 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2254 2253 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2255 2254 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2256 2255 this_vs + ". You can still work with this notebook, but some features " +
2257 2256 "introduced in later notebook versions may not be available.";
2258 2257
2259 2258 dialog.modal({
2260 2259 notebook: this,
2261 2260 keyboard_manager: this.keyboard_manager,
2262 2261 title : "Newer Notebook",
2263 2262 body : msg,
2264 2263 buttons : {
2265 2264 OK : {
2266 2265 class : "btn-danger"
2267 2266 }
2268 2267 }
2269 2268 });
2270 2269
2271 2270 }
2272 2271
2273 2272 // Create the session after the notebook is completely loaded to prevent
2274 2273 // code execution upon loading, which is a security risk.
2275 2274 if (this.session === null) {
2276 2275 var kernelspec = this.metadata.kernelspec || {};
2277 2276 var kernel_name = kernelspec.name || this.default_kernel_name;
2278 2277
2279 2278 this.start_session(kernel_name);
2280 2279 }
2281 2280 // load our checkpoint list
2282 2281 this.list_checkpoints();
2283 2282
2284 2283 // load toolbar state
2285 2284 if (this.metadata.celltoolbar) {
2286 2285 celltoolbar.CellToolbar.global_show();
2287 2286 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar, this.events);
2288 2287 } else {
2289 2288 celltoolbar.CellToolbar.global_hide();
2290 2289 }
2291 2290
2292 2291 // now that we're fully loaded, it is safe to restore save functionality
2293 2292 delete(this.save_notebook);
2294 2293 this.events.trigger('notebook_loaded.Notebook');
2295 2294 };
2296 2295
2297 2296 /**
2298 2297 * Failure callback for loading a notebook from the server.
2299 2298 *
2300 2299 * @method load_notebook_error
2301 2300 * @param {jqXHR} xhr jQuery Ajax object
2302 2301 * @param {String} status Description of response status
2303 2302 * @param {String} error HTTP error message
2304 2303 */
2305 2304 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2306 2305 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2307 2306 var msg;
2308 2307 if (xhr.status === 400) {
2309 2308 msg = error;
2310 2309 } else if (xhr.status === 500) {
2311 2310 msg = "An unknown error occurred while loading this notebook. " +
2312 2311 "This version can load notebook formats " +
2313 2312 "v" + this.nbformat + " or earlier.";
2314 2313 }
2315 2314 dialog.modal({
2316 2315 notebook: this,
2317 2316 keyboard_manager: this.keyboard_manager,
2318 2317 title: "Error loading notebook",
2319 2318 body : msg,
2320 2319 buttons : {
2321 2320 "OK": {}
2322 2321 }
2323 2322 });
2324 2323 };
2325 2324
2326 2325 /********************* checkpoint-related *********************/
2327 2326
2328 2327 /**
2329 2328 * Save the notebook then immediately create a checkpoint.
2330 2329 *
2331 2330 * @method save_checkpoint
2332 2331 */
2333 2332 Notebook.prototype.save_checkpoint = function () {
2334 2333 this._checkpoint_after_save = true;
2335 2334 this.save_notebook();
2336 2335 };
2337 2336
2338 2337 /**
2339 2338 * Add a checkpoint for this notebook.
2340 2339 * for use as a callback from checkpoint creation.
2341 2340 *
2342 2341 * @method add_checkpoint
2343 2342 */
2344 2343 Notebook.prototype.add_checkpoint = function (checkpoint) {
2345 2344 var found = false;
2346 2345 for (var i = 0; i < this.checkpoints.length; i++) {
2347 2346 var existing = this.checkpoints[i];
2348 2347 if (existing.id == checkpoint.id) {
2349 2348 found = true;
2350 2349 this.checkpoints[i] = checkpoint;
2351 2350 break;
2352 2351 }
2353 2352 }
2354 2353 if (!found) {
2355 2354 this.checkpoints.push(checkpoint);
2356 2355 }
2357 2356 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2358 2357 };
2359 2358
2360 2359 /**
2361 2360 * List checkpoints for this notebook.
2362 2361 *
2363 2362 * @method list_checkpoints
2364 2363 */
2365 2364 Notebook.prototype.list_checkpoints = function () {
2366 2365 var url = utils.url_join_encode(
2367 2366 this.base_url,
2368 2367 'api/notebooks',
2369 2368 this.notebook_path,
2370 2369 this.notebook_name,
2371 2370 'checkpoints'
2372 2371 );
2373 2372 $.get(url).done(
2374 2373 $.proxy(this.list_checkpoints_success, this)
2375 2374 ).fail(
2376 2375 $.proxy(this.list_checkpoints_error, this)
2377 2376 );
2378 2377 };
2379 2378
2380 2379 /**
2381 2380 * Success callback for listing checkpoints.
2382 2381 *
2383 2382 * @method list_checkpoint_success
2384 2383 * @param {Object} data JSON representation of a checkpoint
2385 2384 * @param {String} status Description of response status
2386 2385 * @param {jqXHR} xhr jQuery Ajax object
2387 2386 */
2388 2387 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2389 2388 data = $.parseJSON(data);
2390 2389 this.checkpoints = data;
2391 2390 if (data.length) {
2392 2391 this.last_checkpoint = data[data.length - 1];
2393 2392 } else {
2394 2393 this.last_checkpoint = null;
2395 2394 }
2396 2395 this.events.trigger('checkpoints_listed.Notebook', [data]);
2397 2396 };
2398 2397
2399 2398 /**
2400 2399 * Failure callback for listing a checkpoint.
2401 2400 *
2402 2401 * @method list_checkpoint_error
2403 2402 * @param {jqXHR} xhr jQuery Ajax object
2404 2403 * @param {String} status Description of response status
2405 2404 * @param {String} error_msg HTTP error message
2406 2405 */
2407 2406 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2408 2407 this.events.trigger('list_checkpoints_failed.Notebook');
2409 2408 };
2410 2409
2411 2410 /**
2412 2411 * Create a checkpoint of this notebook on the server from the most recent save.
2413 2412 *
2414 2413 * @method create_checkpoint
2415 2414 */
2416 2415 Notebook.prototype.create_checkpoint = function () {
2417 2416 var url = utils.url_join_encode(
2418 2417 this.base_url,
2419 2418 'api/notebooks',
2420 2419 this.notebook_path,
2421 2420 this.notebook_name,
2422 2421 'checkpoints'
2423 2422 );
2424 2423 $.post(url).done(
2425 2424 $.proxy(this.create_checkpoint_success, this)
2426 2425 ).fail(
2427 2426 $.proxy(this.create_checkpoint_error, this)
2428 2427 );
2429 2428 };
2430 2429
2431 2430 /**
2432 2431 * Success callback for creating a checkpoint.
2433 2432 *
2434 2433 * @method create_checkpoint_success
2435 2434 * @param {Object} data JSON representation of a checkpoint
2436 2435 * @param {String} status Description of response status
2437 2436 * @param {jqXHR} xhr jQuery Ajax object
2438 2437 */
2439 2438 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2440 2439 data = $.parseJSON(data);
2441 2440 this.add_checkpoint(data);
2442 2441 this.events.trigger('checkpoint_created.Notebook', data);
2443 2442 };
2444 2443
2445 2444 /**
2446 2445 * Failure callback for creating a checkpoint.
2447 2446 *
2448 2447 * @method create_checkpoint_error
2449 2448 * @param {jqXHR} xhr jQuery Ajax object
2450 2449 * @param {String} status Description of response status
2451 2450 * @param {String} error_msg HTTP error message
2452 2451 */
2453 2452 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2454 2453 this.events.trigger('checkpoint_failed.Notebook');
2455 2454 };
2456 2455
2457 2456 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2458 2457 var that = this;
2459 2458 checkpoint = checkpoint || this.last_checkpoint;
2460 2459 if ( ! checkpoint ) {
2461 2460 console.log("restore dialog, but no checkpoint to restore to!");
2462 2461 return;
2463 2462 }
2464 2463 var body = $('<div/>').append(
2465 2464 $('<p/>').addClass("p-space").text(
2466 2465 "Are you sure you want to revert the notebook to " +
2467 2466 "the latest checkpoint?"
2468 2467 ).append(
2469 2468 $("<strong/>").text(
2470 2469 " This cannot be undone."
2471 2470 )
2472 2471 )
2473 2472 ).append(
2474 2473 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2475 2474 ).append(
2476 2475 $('<p/>').addClass("p-space").text(
2477 2476 Date(checkpoint.last_modified)
2478 2477 ).css("text-align", "center")
2479 2478 );
2480 2479
2481 2480 dialog.modal({
2482 2481 notebook: this,
2483 2482 keyboard_manager: this.keyboard_manager,
2484 2483 title : "Revert notebook to checkpoint",
2485 2484 body : body,
2486 2485 buttons : {
2487 2486 Revert : {
2488 2487 class : "btn-danger",
2489 2488 click : function () {
2490 2489 that.restore_checkpoint(checkpoint.id);
2491 2490 }
2492 2491 },
2493 2492 Cancel : {}
2494 2493 }
2495 2494 });
2496 2495 };
2497 2496
2498 2497 /**
2499 2498 * Restore the notebook to a checkpoint state.
2500 2499 *
2501 2500 * @method restore_checkpoint
2502 2501 * @param {String} checkpoint ID
2503 2502 */
2504 2503 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2505 2504 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2506 2505 var url = utils.url_join_encode(
2507 2506 this.base_url,
2508 2507 'api/notebooks',
2509 2508 this.notebook_path,
2510 2509 this.notebook_name,
2511 2510 'checkpoints',
2512 2511 checkpoint
2513 2512 );
2514 2513 $.post(url).done(
2515 2514 $.proxy(this.restore_checkpoint_success, this)
2516 2515 ).fail(
2517 2516 $.proxy(this.restore_checkpoint_error, this)
2518 2517 );
2519 2518 };
2520 2519
2521 2520 /**
2522 2521 * Success callback for restoring a notebook to a checkpoint.
2523 2522 *
2524 2523 * @method restore_checkpoint_success
2525 2524 * @param {Object} data (ignored, should be empty)
2526 2525 * @param {String} status Description of response status
2527 2526 * @param {jqXHR} xhr jQuery Ajax object
2528 2527 */
2529 2528 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2530 2529 this.events.trigger('checkpoint_restored.Notebook');
2531 2530 this.load_notebook(this.notebook_name, this.notebook_path);
2532 2531 };
2533 2532
2534 2533 /**
2535 2534 * Failure callback for restoring a notebook to a checkpoint.
2536 2535 *
2537 2536 * @method restore_checkpoint_error
2538 2537 * @param {jqXHR} xhr jQuery Ajax object
2539 2538 * @param {String} status Description of response status
2540 2539 * @param {String} error_msg HTTP error message
2541 2540 */
2542 2541 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2543 2542 this.events.trigger('checkpoint_restore_failed.Notebook');
2544 2543 };
2545 2544
2546 2545 /**
2547 2546 * Delete a notebook checkpoint.
2548 2547 *
2549 2548 * @method delete_checkpoint
2550 2549 * @param {String} checkpoint ID
2551 2550 */
2552 2551 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2553 2552 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2554 2553 var url = utils.url_join_encode(
2555 2554 this.base_url,
2556 2555 'api/notebooks',
2557 2556 this.notebook_path,
2558 2557 this.notebook_name,
2559 2558 'checkpoints',
2560 2559 checkpoint
2561 2560 );
2562 2561 $.ajax(url, {
2563 2562 type: 'DELETE',
2564 2563 success: $.proxy(this.delete_checkpoint_success, this),
2565 2564 error: $.proxy(this.delete_checkpoint_error, this)
2566 2565 });
2567 2566 };
2568 2567
2569 2568 /**
2570 2569 * Success callback for deleting a notebook checkpoint
2571 2570 *
2572 2571 * @method delete_checkpoint_success
2573 2572 * @param {Object} data (ignored, should be empty)
2574 2573 * @param {String} status Description of response status
2575 2574 * @param {jqXHR} xhr jQuery Ajax object
2576 2575 */
2577 2576 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2578 2577 this.events.trigger('checkpoint_deleted.Notebook', data);
2579 2578 this.load_notebook(this.notebook_name, this.notebook_path);
2580 2579 };
2581 2580
2582 2581 /**
2583 2582 * Failure callback for deleting a notebook checkpoint.
2584 2583 *
2585 2584 * @method delete_checkpoint_error
2586 2585 * @param {jqXHR} xhr jQuery Ajax object
2587 2586 * @param {String} status Description of response status
2588 2587 * @param {String} error_msg HTTP error message
2589 2588 */
2590 2589 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2591 2590 this.events.trigger('checkpoint_delete_failed.Notebook');
2592 2591 };
2593 2592
2594 2593
2595 2594 // For backwards compatability.
2596 2595 IPython.Notebook = Notebook;
2597 2596
2598 2597 return {'Notebook': Notebook};
2599 2598 });
General Comments 0
You need to be logged in to leave comments. Login now