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