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