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