##// END OF EJS Templates
Moving a cell focuses it after the move....
Brian E. Granger -
Show More
@@ -1,2148 +1,2152
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();
570 cell.focus_cell();
569 };
571 };
570 this.set_dirty(true);
572 this.set_dirty(true);
571 };
573 };
572 return this;
574 return this;
573 };
575 };
574
576
575
577
576 /**
578 /**
577 * Move given (or selected) cell down and select it
579 * Move given (or selected) cell down and select it
578 *
580 *
579 * @method move_cell_down
581 * @method move_cell_down
580 * @param [index] {integer} cell index
582 * @param [index] {integer} cell index
581 * @return {Notebook} This notebook
583 * @return {Notebook} This notebook
582 **/
584 **/
583 Notebook.prototype.move_cell_down = function (index) {
585 Notebook.prototype.move_cell_down = function (index) {
584 var i = this.index_or_selected(index);
586 var i = this.index_or_selected(index);
585 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)) {
586 var pivot = this.get_cell_element(i+1);
588 var pivot = this.get_cell_element(i+1);
587 var tomove = this.get_cell_element(i);
589 var tomove = this.get_cell_element(i);
588 if (pivot !== null && tomove !== null) {
590 if (pivot !== null && tomove !== null) {
589 tomove.detach();
591 tomove.detach();
590 pivot.after(tomove);
592 pivot.after(tomove);
591 this.select(i+1);
593 this.select(i+1);
594 var cell = this.get_selected_cell();
595 cell.focus_cell();
592 };
596 };
593 };
597 };
594 this.set_dirty();
598 this.set_dirty();
595 return this;
599 return this;
596 };
600 };
597
601
598
602
599 // Insertion, deletion.
603 // Insertion, deletion.
600
604
601 /**
605 /**
602 * Delete a cell from the notebook.
606 * Delete a cell from the notebook.
603 *
607 *
604 * @method delete_cell
608 * @method delete_cell
605 * @param [index] A cell's numeric index
609 * @param [index] A cell's numeric index
606 * @return {Notebook} This notebook
610 * @return {Notebook} This notebook
607 */
611 */
608 Notebook.prototype.delete_cell = function (index) {
612 Notebook.prototype.delete_cell = function (index) {
609 var i = this.index_or_selected(index);
613 var i = this.index_or_selected(index);
610 var cell = this.get_selected_cell();
614 var cell = this.get_selected_cell();
611 this.undelete_backup = cell.toJSON();
615 this.undelete_backup = cell.toJSON();
612 $('#undelete_cell').removeClass('disabled');
616 $('#undelete_cell').removeClass('disabled');
613 if (this.is_valid_cell_index(i)) {
617 if (this.is_valid_cell_index(i)) {
614 var ce = this.get_cell_element(i);
618 var ce = this.get_cell_element(i);
615 ce.remove();
619 ce.remove();
616 if (i === (this.ncells())) {
620 if (i === (this.ncells())) {
617 this.select(i-1);
621 this.select(i-1);
618 this.undelete_index = i - 1;
622 this.undelete_index = i - 1;
619 this.undelete_below = true;
623 this.undelete_below = true;
620 } else {
624 } else {
621 this.select(i);
625 this.select(i);
622 this.undelete_index = i;
626 this.undelete_index = i;
623 this.undelete_below = false;
627 this.undelete_below = false;
624 };
628 };
625 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
629 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
626 this.set_dirty(true);
630 this.set_dirty(true);
627 };
631 };
628 return this;
632 return this;
629 };
633 };
630
634
631 /**
635 /**
632 * Insert a cell so that after insertion the cell is at given index.
636 * Insert a cell so that after insertion the cell is at given index.
633 *
637 *
634 * Similar to insert_above, but index parameter is mandatory
638 * Similar to insert_above, but index parameter is mandatory
635 *
639 *
636 * Index will be brought back into the accissible range [0,n]
640 * Index will be brought back into the accissible range [0,n]
637 *
641 *
638 * @method insert_cell_at_index
642 * @method insert_cell_at_index
639 * @param type {string} in ['code','markdown','heading']
643 * @param type {string} in ['code','markdown','heading']
640 * @param [index] {int} a valid index where to inser cell
644 * @param [index] {int} a valid index where to inser cell
641 *
645 *
642 * @return cell {cell|null} created cell or null
646 * @return cell {cell|null} created cell or null
643 **/
647 **/
644 Notebook.prototype.insert_cell_at_index = function(type, index){
648 Notebook.prototype.insert_cell_at_index = function(type, index){
645
649
646 var ncells = this.ncells();
650 var ncells = this.ncells();
647 var index = Math.min(index,ncells);
651 var index = Math.min(index,ncells);
648 index = Math.max(index,0);
652 index = Math.max(index,0);
649 var cell = null;
653 var cell = null;
650
654
651 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
655 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
652 if (type === 'code') {
656 if (type === 'code') {
653 cell = new IPython.CodeCell(this.kernel);
657 cell = new IPython.CodeCell(this.kernel);
654 cell.set_input_prompt();
658 cell.set_input_prompt();
655 } else if (type === 'markdown') {
659 } else if (type === 'markdown') {
656 cell = new IPython.MarkdownCell();
660 cell = new IPython.MarkdownCell();
657 } else if (type === 'raw') {
661 } else if (type === 'raw') {
658 cell = new IPython.RawCell();
662 cell = new IPython.RawCell();
659 } else if (type === 'heading') {
663 } else if (type === 'heading') {
660 cell = new IPython.HeadingCell();
664 cell = new IPython.HeadingCell();
661 }
665 }
662
666
663 if(this._insert_element_at_index(cell.element,index)) {
667 if(this._insert_element_at_index(cell.element,index)) {
664 cell.render();
668 cell.render();
665 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
669 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
666 cell.refresh();
670 cell.refresh();
667 // TODO: should we really get rid of this?
671 // TODO: should we really get rid of this?
668 //this.select(this.find_cell_index(cell));
672 //this.select(this.find_cell_index(cell));
669 this.set_dirty(true);
673 this.set_dirty(true);
670 }
674 }
671 }
675 }
672 return cell;
676 return cell;
673
677
674 };
678 };
675
679
676 /**
680 /**
677 * Insert an element at given cell index.
681 * Insert an element at given cell index.
678 *
682 *
679 * @method _insert_element_at_index
683 * @method _insert_element_at_index
680 * @param element {dom element} a cell element
684 * @param element {dom element} a cell element
681 * @param [index] {int} a valid index where to inser cell
685 * @param [index] {int} a valid index where to inser cell
682 * @private
686 * @private
683 *
687 *
684 * return true if everything whent fine.
688 * return true if everything whent fine.
685 **/
689 **/
686 Notebook.prototype._insert_element_at_index = function(element, index){
690 Notebook.prototype._insert_element_at_index = function(element, index){
687 if (element === undefined){
691 if (element === undefined){
688 return false;
692 return false;
689 }
693 }
690
694
691 var ncells = this.ncells();
695 var ncells = this.ncells();
692
696
693 if (ncells === 0) {
697 if (ncells === 0) {
694 // special case append if empty
698 // special case append if empty
695 this.element.find('div.end_space').before(element);
699 this.element.find('div.end_space').before(element);
696 } else if ( ncells === index ) {
700 } else if ( ncells === index ) {
697 // special case append it the end, but not empty
701 // special case append it the end, but not empty
698 this.get_cell_element(index-1).after(element);
702 this.get_cell_element(index-1).after(element);
699 } else if (this.is_valid_cell_index(index)) {
703 } else if (this.is_valid_cell_index(index)) {
700 // otherwise always somewhere to append to
704 // otherwise always somewhere to append to
701 this.get_cell_element(index).before(element);
705 this.get_cell_element(index).before(element);
702 } else {
706 } else {
703 return false;
707 return false;
704 }
708 }
705
709
706 if (this.undelete_index !== null && index <= this.undelete_index) {
710 if (this.undelete_index !== null && index <= this.undelete_index) {
707 this.undelete_index = this.undelete_index + 1;
711 this.undelete_index = this.undelete_index + 1;
708 this.set_dirty(true);
712 this.set_dirty(true);
709 }
713 }
710 return true;
714 return true;
711 };
715 };
712
716
713 /**
717 /**
714 * Insert a cell of given type above given index, or at top
718 * Insert a cell of given type above given index, or at top
715 * of notebook if index smaller than 0.
719 * of notebook if index smaller than 0.
716 *
720 *
717 * default index value is the one of currently selected cell
721 * default index value is the one of currently selected cell
718 *
722 *
719 * @method insert_cell_above
723 * @method insert_cell_above
720 * @param type {string} cell type
724 * @param type {string} cell type
721 * @param [index] {integer}
725 * @param [index] {integer}
722 *
726 *
723 * @return handle to created cell or null
727 * @return handle to created cell or null
724 **/
728 **/
725 Notebook.prototype.insert_cell_above = function (type, index) {
729 Notebook.prototype.insert_cell_above = function (type, index) {
726 index = this.index_or_selected(index);
730 index = this.index_or_selected(index);
727 return this.insert_cell_at_index(type, index);
731 return this.insert_cell_at_index(type, index);
728 };
732 };
729
733
730 /**
734 /**
731 * Insert a cell of given type below given index, or at bottom
735 * Insert a cell of given type below given index, or at bottom
732 * of notebook if index greater thatn number of cell
736 * of notebook if index greater thatn number of cell
733 *
737 *
734 * default index value is the one of currently selected cell
738 * default index value is the one of currently selected cell
735 *
739 *
736 * @method insert_cell_below
740 * @method insert_cell_below
737 * @param type {string} cell type
741 * @param type {string} cell type
738 * @param [index] {integer}
742 * @param [index] {integer}
739 *
743 *
740 * @return handle to created cell or null
744 * @return handle to created cell or null
741 *
745 *
742 **/
746 **/
743 Notebook.prototype.insert_cell_below = function (type, index) {
747 Notebook.prototype.insert_cell_below = function (type, index) {
744 index = this.index_or_selected(index);
748 index = this.index_or_selected(index);
745 return this.insert_cell_at_index(type, index+1);
749 return this.insert_cell_at_index(type, index+1);
746 };
750 };
747
751
748
752
749 /**
753 /**
750 * Insert cell at end of notebook
754 * Insert cell at end of notebook
751 *
755 *
752 * @method insert_cell_at_bottom
756 * @method insert_cell_at_bottom
753 * @param {String} type cell type
757 * @param {String} type cell type
754 *
758 *
755 * @return the added cell; or null
759 * @return the added cell; or null
756 **/
760 **/
757 Notebook.prototype.insert_cell_at_bottom = function (type){
761 Notebook.prototype.insert_cell_at_bottom = function (type){
758 var len = this.ncells();
762 var len = this.ncells();
759 return this.insert_cell_below(type,len-1);
763 return this.insert_cell_below(type,len-1);
760 };
764 };
761
765
762 /**
766 /**
763 * Turn a cell into a code cell.
767 * Turn a cell into a code cell.
764 *
768 *
765 * @method to_code
769 * @method to_code
766 * @param {Number} [index] A cell's index
770 * @param {Number} [index] A cell's index
767 */
771 */
768 Notebook.prototype.to_code = function (index) {
772 Notebook.prototype.to_code = function (index) {
769 var i = this.index_or_selected(index);
773 var i = this.index_or_selected(index);
770 if (this.is_valid_cell_index(i)) {
774 if (this.is_valid_cell_index(i)) {
771 var source_element = this.get_cell_element(i);
775 var source_element = this.get_cell_element(i);
772 var source_cell = source_element.data("cell");
776 var source_cell = source_element.data("cell");
773 if (!(source_cell instanceof IPython.CodeCell)) {
777 if (!(source_cell instanceof IPython.CodeCell)) {
774 var target_cell = this.insert_cell_below('code',i);
778 var target_cell = this.insert_cell_below('code',i);
775 var text = source_cell.get_text();
779 var text = source_cell.get_text();
776 if (text === source_cell.placeholder) {
780 if (text === source_cell.placeholder) {
777 text = '';
781 text = '';
778 }
782 }
779 target_cell.set_text(text);
783 target_cell.set_text(text);
780 // make this value the starting point, so that we can only undo
784 // make this value the starting point, so that we can only undo
781 // to this state, instead of a blank cell
785 // to this state, instead of a blank cell
782 target_cell.code_mirror.clearHistory();
786 target_cell.code_mirror.clearHistory();
783 source_element.remove();
787 source_element.remove();
784 this.select(i);
788 this.select(i);
785 this.edit_mode();
789 this.edit_mode();
786 this.set_dirty(true);
790 this.set_dirty(true);
787 };
791 };
788 };
792 };
789 };
793 };
790
794
791 /**
795 /**
792 * Turn a cell into a Markdown cell.
796 * Turn a cell into a Markdown cell.
793 *
797 *
794 * @method to_markdown
798 * @method to_markdown
795 * @param {Number} [index] A cell's index
799 * @param {Number} [index] A cell's index
796 */
800 */
797 Notebook.prototype.to_markdown = function (index) {
801 Notebook.prototype.to_markdown = function (index) {
798 var i = this.index_or_selected(index);
802 var i = this.index_or_selected(index);
799 if (this.is_valid_cell_index(i)) {
803 if (this.is_valid_cell_index(i)) {
800 var source_element = this.get_cell_element(i);
804 var source_element = this.get_cell_element(i);
801 var source_cell = source_element.data("cell");
805 var source_cell = source_element.data("cell");
802 if (!(source_cell instanceof IPython.MarkdownCell)) {
806 if (!(source_cell instanceof IPython.MarkdownCell)) {
803 var target_cell = this.insert_cell_below('markdown',i);
807 var target_cell = this.insert_cell_below('markdown',i);
804 var text = source_cell.get_text();
808 var text = source_cell.get_text();
805 if (text === source_cell.placeholder) {
809 if (text === source_cell.placeholder) {
806 text = '';
810 text = '';
807 };
811 };
808 // We must show the editor before setting its contents
812 // We must show the editor before setting its contents
809 target_cell.unrender();
813 target_cell.unrender();
810 target_cell.set_text(text);
814 target_cell.set_text(text);
811 // make this value the starting point, so that we can only undo
815 // make this value the starting point, so that we can only undo
812 // to this state, instead of a blank cell
816 // to this state, instead of a blank cell
813 target_cell.code_mirror.clearHistory();
817 target_cell.code_mirror.clearHistory();
814 source_element.remove();
818 source_element.remove();
815 this.select(i);
819 this.select(i);
816 this.edit_mode();
820 this.edit_mode();
817 this.set_dirty(true);
821 this.set_dirty(true);
818 };
822 };
819 };
823 };
820 };
824 };
821
825
822 /**
826 /**
823 * Turn a cell into a raw text cell.
827 * Turn a cell into a raw text cell.
824 *
828 *
825 * @method to_raw
829 * @method to_raw
826 * @param {Number} [index] A cell's index
830 * @param {Number} [index] A cell's index
827 */
831 */
828 Notebook.prototype.to_raw = function (index) {
832 Notebook.prototype.to_raw = function (index) {
829 var i = this.index_or_selected(index);
833 var i = this.index_or_selected(index);
830 if (this.is_valid_cell_index(i)) {
834 if (this.is_valid_cell_index(i)) {
831 var source_element = this.get_cell_element(i);
835 var source_element = this.get_cell_element(i);
832 var source_cell = source_element.data("cell");
836 var source_cell = source_element.data("cell");
833 var target_cell = null;
837 var target_cell = null;
834 if (!(source_cell instanceof IPython.RawCell)) {
838 if (!(source_cell instanceof IPython.RawCell)) {
835 target_cell = this.insert_cell_below('raw',i);
839 target_cell = this.insert_cell_below('raw',i);
836 var text = source_cell.get_text();
840 var text = source_cell.get_text();
837 if (text === source_cell.placeholder) {
841 if (text === source_cell.placeholder) {
838 text = '';
842 text = '';
839 };
843 };
840 // We must show the editor before setting its contents
844 // We must show the editor before setting its contents
841 target_cell.unrender();
845 target_cell.unrender();
842 target_cell.set_text(text);
846 target_cell.set_text(text);
843 // make this value the starting point, so that we can only undo
847 // make this value the starting point, so that we can only undo
844 // to this state, instead of a blank cell
848 // to this state, instead of a blank cell
845 target_cell.code_mirror.clearHistory();
849 target_cell.code_mirror.clearHistory();
846 source_element.remove();
850 source_element.remove();
847 this.select(i);
851 this.select(i);
848 this.edit_mode();
852 this.edit_mode();
849 this.set_dirty(true);
853 this.set_dirty(true);
850 };
854 };
851 };
855 };
852 };
856 };
853
857
854 /**
858 /**
855 * Turn a cell into a heading cell.
859 * Turn a cell into a heading cell.
856 *
860 *
857 * @method to_heading
861 * @method to_heading
858 * @param {Number} [index] A cell's index
862 * @param {Number} [index] A cell's index
859 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
863 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
860 */
864 */
861 Notebook.prototype.to_heading = function (index, level) {
865 Notebook.prototype.to_heading = function (index, level) {
862 level = level || 1;
866 level = level || 1;
863 var i = this.index_or_selected(index);
867 var i = this.index_or_selected(index);
864 if (this.is_valid_cell_index(i)) {
868 if (this.is_valid_cell_index(i)) {
865 var source_element = this.get_cell_element(i);
869 var source_element = this.get_cell_element(i);
866 var source_cell = source_element.data("cell");
870 var source_cell = source_element.data("cell");
867 var target_cell = null;
871 var target_cell = null;
868 if (source_cell instanceof IPython.HeadingCell) {
872 if (source_cell instanceof IPython.HeadingCell) {
869 source_cell.set_level(level);
873 source_cell.set_level(level);
870 } else {
874 } else {
871 target_cell = this.insert_cell_below('heading',i);
875 target_cell = this.insert_cell_below('heading',i);
872 var text = source_cell.get_text();
876 var text = source_cell.get_text();
873 if (text === source_cell.placeholder) {
877 if (text === source_cell.placeholder) {
874 text = '';
878 text = '';
875 };
879 };
876 // We must show the editor before setting its contents
880 // We must show the editor before setting its contents
877 target_cell.set_level(level);
881 target_cell.set_level(level);
878 target_cell.unrender();
882 target_cell.unrender();
879 target_cell.set_text(text);
883 target_cell.set_text(text);
880 // make this value the starting point, so that we can only undo
884 // make this value the starting point, so that we can only undo
881 // to this state, instead of a blank cell
885 // to this state, instead of a blank cell
882 target_cell.code_mirror.clearHistory();
886 target_cell.code_mirror.clearHistory();
883 source_element.remove();
887 source_element.remove();
884 this.select(i);
888 this.select(i);
885 this.edit_mode();
889 this.edit_mode();
886 this.set_dirty(true);
890 this.set_dirty(true);
887 };
891 };
888 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
892 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
889 {'cell_type':'heading',level:level}
893 {'cell_type':'heading',level:level}
890 );
894 );
891 };
895 };
892 };
896 };
893
897
894
898
895 // Cut/Copy/Paste
899 // Cut/Copy/Paste
896
900
897 /**
901 /**
898 * Enable UI elements for pasting cells.
902 * Enable UI elements for pasting cells.
899 *
903 *
900 * @method enable_paste
904 * @method enable_paste
901 */
905 */
902 Notebook.prototype.enable_paste = function () {
906 Notebook.prototype.enable_paste = function () {
903 var that = this;
907 var that = this;
904 if (!this.paste_enabled) {
908 if (!this.paste_enabled) {
905 $('#paste_cell_replace').removeClass('disabled')
909 $('#paste_cell_replace').removeClass('disabled')
906 .on('click', function () {that.paste_cell_replace();});
910 .on('click', function () {that.paste_cell_replace();});
907 $('#paste_cell_above').removeClass('disabled')
911 $('#paste_cell_above').removeClass('disabled')
908 .on('click', function () {that.paste_cell_above();});
912 .on('click', function () {that.paste_cell_above();});
909 $('#paste_cell_below').removeClass('disabled')
913 $('#paste_cell_below').removeClass('disabled')
910 .on('click', function () {that.paste_cell_below();});
914 .on('click', function () {that.paste_cell_below();});
911 this.paste_enabled = true;
915 this.paste_enabled = true;
912 };
916 };
913 };
917 };
914
918
915 /**
919 /**
916 * Disable UI elements for pasting cells.
920 * Disable UI elements for pasting cells.
917 *
921 *
918 * @method disable_paste
922 * @method disable_paste
919 */
923 */
920 Notebook.prototype.disable_paste = function () {
924 Notebook.prototype.disable_paste = function () {
921 if (this.paste_enabled) {
925 if (this.paste_enabled) {
922 $('#paste_cell_replace').addClass('disabled').off('click');
926 $('#paste_cell_replace').addClass('disabled').off('click');
923 $('#paste_cell_above').addClass('disabled').off('click');
927 $('#paste_cell_above').addClass('disabled').off('click');
924 $('#paste_cell_below').addClass('disabled').off('click');
928 $('#paste_cell_below').addClass('disabled').off('click');
925 this.paste_enabled = false;
929 this.paste_enabled = false;
926 };
930 };
927 };
931 };
928
932
929 /**
933 /**
930 * Cut a cell.
934 * Cut a cell.
931 *
935 *
932 * @method cut_cell
936 * @method cut_cell
933 */
937 */
934 Notebook.prototype.cut_cell = function () {
938 Notebook.prototype.cut_cell = function () {
935 this.copy_cell();
939 this.copy_cell();
936 this.delete_cell();
940 this.delete_cell();
937 }
941 }
938
942
939 /**
943 /**
940 * Copy a cell.
944 * Copy a cell.
941 *
945 *
942 * @method copy_cell
946 * @method copy_cell
943 */
947 */
944 Notebook.prototype.copy_cell = function () {
948 Notebook.prototype.copy_cell = function () {
945 var cell = this.get_selected_cell();
949 var cell = this.get_selected_cell();
946 this.clipboard = cell.toJSON();
950 this.clipboard = cell.toJSON();
947 this.enable_paste();
951 this.enable_paste();
948 };
952 };
949
953
950 /**
954 /**
951 * Replace the selected cell with a cell in the clipboard.
955 * Replace the selected cell with a cell in the clipboard.
952 *
956 *
953 * @method paste_cell_replace
957 * @method paste_cell_replace
954 */
958 */
955 Notebook.prototype.paste_cell_replace = function () {
959 Notebook.prototype.paste_cell_replace = function () {
956 if (this.clipboard !== null && this.paste_enabled) {
960 if (this.clipboard !== null && this.paste_enabled) {
957 var cell_data = this.clipboard;
961 var cell_data = this.clipboard;
958 var new_cell = this.insert_cell_above(cell_data.cell_type);
962 var new_cell = this.insert_cell_above(cell_data.cell_type);
959 new_cell.fromJSON(cell_data);
963 new_cell.fromJSON(cell_data);
960 var old_cell = this.get_next_cell(new_cell);
964 var old_cell = this.get_next_cell(new_cell);
961 this.delete_cell(this.find_cell_index(old_cell));
965 this.delete_cell(this.find_cell_index(old_cell));
962 this.select(this.find_cell_index(new_cell));
966 this.select(this.find_cell_index(new_cell));
963 };
967 };
964 };
968 };
965
969
966 /**
970 /**
967 * Paste a cell from the clipboard above the selected cell.
971 * Paste a cell from the clipboard above the selected cell.
968 *
972 *
969 * @method paste_cell_above
973 * @method paste_cell_above
970 */
974 */
971 Notebook.prototype.paste_cell_above = function () {
975 Notebook.prototype.paste_cell_above = function () {
972 if (this.clipboard !== null && this.paste_enabled) {
976 if (this.clipboard !== null && this.paste_enabled) {
973 var cell_data = this.clipboard;
977 var cell_data = this.clipboard;
974 var new_cell = this.insert_cell_above(cell_data.cell_type);
978 var new_cell = this.insert_cell_above(cell_data.cell_type);
975 new_cell.fromJSON(cell_data);
979 new_cell.fromJSON(cell_data);
976 };
980 };
977 };
981 };
978
982
979 /**
983 /**
980 * Paste a cell from the clipboard below the selected cell.
984 * Paste a cell from the clipboard below the selected cell.
981 *
985 *
982 * @method paste_cell_below
986 * @method paste_cell_below
983 */
987 */
984 Notebook.prototype.paste_cell_below = function () {
988 Notebook.prototype.paste_cell_below = function () {
985 if (this.clipboard !== null && this.paste_enabled) {
989 if (this.clipboard !== null && this.paste_enabled) {
986 var cell_data = this.clipboard;
990 var cell_data = this.clipboard;
987 var new_cell = this.insert_cell_below(cell_data.cell_type);
991 var new_cell = this.insert_cell_below(cell_data.cell_type);
988 new_cell.fromJSON(cell_data);
992 new_cell.fromJSON(cell_data);
989 };
993 };
990 };
994 };
991
995
992 // Cell undelete
996 // Cell undelete
993
997
994 /**
998 /**
995 * Restore the most recently deleted cell.
999 * Restore the most recently deleted cell.
996 *
1000 *
997 * @method undelete
1001 * @method undelete
998 */
1002 */
999 Notebook.prototype.undelete = function() {
1003 Notebook.prototype.undelete = function() {
1000 if (this.undelete_backup !== null && this.undelete_index !== null) {
1004 if (this.undelete_backup !== null && this.undelete_index !== null) {
1001 var current_index = this.get_selected_index();
1005 var current_index = this.get_selected_index();
1002 if (this.undelete_index < current_index) {
1006 if (this.undelete_index < current_index) {
1003 current_index = current_index + 1;
1007 current_index = current_index + 1;
1004 }
1008 }
1005 if (this.undelete_index >= this.ncells()) {
1009 if (this.undelete_index >= this.ncells()) {
1006 this.select(this.ncells() - 1);
1010 this.select(this.ncells() - 1);
1007 }
1011 }
1008 else {
1012 else {
1009 this.select(this.undelete_index);
1013 this.select(this.undelete_index);
1010 }
1014 }
1011 var cell_data = this.undelete_backup;
1015 var cell_data = this.undelete_backup;
1012 var new_cell = null;
1016 var new_cell = null;
1013 if (this.undelete_below) {
1017 if (this.undelete_below) {
1014 new_cell = this.insert_cell_below(cell_data.cell_type);
1018 new_cell = this.insert_cell_below(cell_data.cell_type);
1015 } else {
1019 } else {
1016 new_cell = this.insert_cell_above(cell_data.cell_type);
1020 new_cell = this.insert_cell_above(cell_data.cell_type);
1017 }
1021 }
1018 new_cell.fromJSON(cell_data);
1022 new_cell.fromJSON(cell_data);
1019 this.select(current_index);
1023 this.select(current_index);
1020 this.undelete_backup = null;
1024 this.undelete_backup = null;
1021 this.undelete_index = null;
1025 this.undelete_index = null;
1022 }
1026 }
1023 $('#undelete_cell').addClass('disabled');
1027 $('#undelete_cell').addClass('disabled');
1024 }
1028 }
1025
1029
1026 // Split/merge
1030 // Split/merge
1027
1031
1028 /**
1032 /**
1029 * Split the selected cell into two, at the cursor.
1033 * Split the selected cell into two, at the cursor.
1030 *
1034 *
1031 * @method split_cell
1035 * @method split_cell
1032 */
1036 */
1033 Notebook.prototype.split_cell = function () {
1037 Notebook.prototype.split_cell = function () {
1034 // Todo: implement spliting for other cell types.
1038 // Todo: implement spliting for other cell types.
1035 var cell = this.get_selected_cell();
1039 var cell = this.get_selected_cell();
1036 if (cell.is_splittable()) {
1040 if (cell.is_splittable()) {
1037 var texta = cell.get_pre_cursor();
1041 var texta = cell.get_pre_cursor();
1038 var textb = cell.get_post_cursor();
1042 var textb = cell.get_post_cursor();
1039 if (cell instanceof IPython.CodeCell) {
1043 if (cell instanceof IPython.CodeCell) {
1040 cell.set_text(textb);
1044 cell.set_text(textb);
1041 var new_cell = this.insert_cell_above('code');
1045 var new_cell = this.insert_cell_above('code');
1042 new_cell.set_text(texta);
1046 new_cell.set_text(texta);
1043 this.select_next();
1047 this.select_next();
1044 } else if (cell instanceof IPython.MarkdownCell) {
1048 } else if (cell instanceof IPython.MarkdownCell) {
1045 cell.set_text(textb);
1049 cell.set_text(textb);
1046 cell.render();
1050 cell.render();
1047 var new_cell = this.insert_cell_above('markdown');
1051 var new_cell = this.insert_cell_above('markdown');
1048 new_cell.unrender(); // editor must be visible to call set_text
1052 new_cell.unrender(); // editor must be visible to call set_text
1049 new_cell.set_text(texta);
1053 new_cell.set_text(texta);
1050 new_cell.render();
1054 new_cell.render();
1051 this.select_next();
1055 this.select_next();
1052 }
1056 }
1053 };
1057 };
1054 };
1058 };
1055
1059
1056 /**
1060 /**
1057 * Combine the selected cell into the cell above it.
1061 * Combine the selected cell into the cell above it.
1058 *
1062 *
1059 * @method merge_cell_above
1063 * @method merge_cell_above
1060 */
1064 */
1061 Notebook.prototype.merge_cell_above = function () {
1065 Notebook.prototype.merge_cell_above = function () {
1062 var index = this.get_selected_index();
1066 var index = this.get_selected_index();
1063 var cell = this.get_cell(index);
1067 var cell = this.get_cell(index);
1064 if (!cell.is_mergeable()) {
1068 if (!cell.is_mergeable()) {
1065 return;
1069 return;
1066 }
1070 }
1067 if (index > 0) {
1071 if (index > 0) {
1068 var upper_cell = this.get_cell(index-1);
1072 var upper_cell = this.get_cell(index-1);
1069 if (!upper_cell.is_mergeable()) {
1073 if (!upper_cell.is_mergeable()) {
1070 return;
1074 return;
1071 }
1075 }
1072 var upper_text = upper_cell.get_text();
1076 var upper_text = upper_cell.get_text();
1073 var text = cell.get_text();
1077 var text = cell.get_text();
1074 if (cell instanceof IPython.CodeCell) {
1078 if (cell instanceof IPython.CodeCell) {
1075 cell.set_text(upper_text+'\n'+text);
1079 cell.set_text(upper_text+'\n'+text);
1076 } else if (cell instanceof IPython.MarkdownCell) {
1080 } else if (cell instanceof IPython.MarkdownCell) {
1077 cell.unrender();
1081 cell.unrender();
1078 cell.set_text(upper_text+'\n'+text);
1082 cell.set_text(upper_text+'\n'+text);
1079 cell.render();
1083 cell.render();
1080 };
1084 };
1081 this.delete_cell(index-1);
1085 this.delete_cell(index-1);
1082 this.select(this.find_cell_index(cell));
1086 this.select(this.find_cell_index(cell));
1083 };
1087 };
1084 };
1088 };
1085
1089
1086 /**
1090 /**
1087 * Combine the selected cell into the cell below it.
1091 * Combine the selected cell into the cell below it.
1088 *
1092 *
1089 * @method merge_cell_below
1093 * @method merge_cell_below
1090 */
1094 */
1091 Notebook.prototype.merge_cell_below = function () {
1095 Notebook.prototype.merge_cell_below = function () {
1092 var index = this.get_selected_index();
1096 var index = this.get_selected_index();
1093 var cell = this.get_cell(index);
1097 var cell = this.get_cell(index);
1094 if (!cell.is_mergeable()) {
1098 if (!cell.is_mergeable()) {
1095 return;
1099 return;
1096 }
1100 }
1097 if (index < this.ncells()-1) {
1101 if (index < this.ncells()-1) {
1098 var lower_cell = this.get_cell(index+1);
1102 var lower_cell = this.get_cell(index+1);
1099 if (!lower_cell.is_mergeable()) {
1103 if (!lower_cell.is_mergeable()) {
1100 return;
1104 return;
1101 }
1105 }
1102 var lower_text = lower_cell.get_text();
1106 var lower_text = lower_cell.get_text();
1103 var text = cell.get_text();
1107 var text = cell.get_text();
1104 if (cell instanceof IPython.CodeCell) {
1108 if (cell instanceof IPython.CodeCell) {
1105 cell.set_text(text+'\n'+lower_text);
1109 cell.set_text(text+'\n'+lower_text);
1106 } else if (cell instanceof IPython.MarkdownCell) {
1110 } else if (cell instanceof IPython.MarkdownCell) {
1107 cell.unrender();
1111 cell.unrender();
1108 cell.set_text(text+'\n'+lower_text);
1112 cell.set_text(text+'\n'+lower_text);
1109 cell.render();
1113 cell.render();
1110 };
1114 };
1111 this.delete_cell(index+1);
1115 this.delete_cell(index+1);
1112 this.select(this.find_cell_index(cell));
1116 this.select(this.find_cell_index(cell));
1113 };
1117 };
1114 };
1118 };
1115
1119
1116
1120
1117 // Cell collapsing and output clearing
1121 // Cell collapsing and output clearing
1118
1122
1119 /**
1123 /**
1120 * Hide a cell's output.
1124 * Hide a cell's output.
1121 *
1125 *
1122 * @method collapse
1126 * @method collapse
1123 * @param {Number} index A cell's numeric index
1127 * @param {Number} index A cell's numeric index
1124 */
1128 */
1125 Notebook.prototype.collapse = function (index) {
1129 Notebook.prototype.collapse = function (index) {
1126 var i = this.index_or_selected(index);
1130 var i = this.index_or_selected(index);
1127 this.get_cell(i).collapse();
1131 this.get_cell(i).collapse();
1128 this.set_dirty(true);
1132 this.set_dirty(true);
1129 };
1133 };
1130
1134
1131 /**
1135 /**
1132 * Show a cell's output.
1136 * Show a cell's output.
1133 *
1137 *
1134 * @method expand
1138 * @method expand
1135 * @param {Number} index A cell's numeric index
1139 * @param {Number} index A cell's numeric index
1136 */
1140 */
1137 Notebook.prototype.expand = function (index) {
1141 Notebook.prototype.expand = function (index) {
1138 var i = this.index_or_selected(index);
1142 var i = this.index_or_selected(index);
1139 this.get_cell(i).expand();
1143 this.get_cell(i).expand();
1140 this.set_dirty(true);
1144 this.set_dirty(true);
1141 };
1145 };
1142
1146
1143 /** Toggle whether a cell's output is collapsed or expanded.
1147 /** Toggle whether a cell's output is collapsed or expanded.
1144 *
1148 *
1145 * @method toggle_output
1149 * @method toggle_output
1146 * @param {Number} index A cell's numeric index
1150 * @param {Number} index A cell's numeric index
1147 */
1151 */
1148 Notebook.prototype.toggle_output = function (index) {
1152 Notebook.prototype.toggle_output = function (index) {
1149 var i = this.index_or_selected(index);
1153 var i = this.index_or_selected(index);
1150 this.get_cell(i).toggle_output();
1154 this.get_cell(i).toggle_output();
1151 this.set_dirty(true);
1155 this.set_dirty(true);
1152 };
1156 };
1153
1157
1154 /**
1158 /**
1155 * Toggle a scrollbar for long cell outputs.
1159 * Toggle a scrollbar for long cell outputs.
1156 *
1160 *
1157 * @method toggle_output_scroll
1161 * @method toggle_output_scroll
1158 * @param {Number} index A cell's numeric index
1162 * @param {Number} index A cell's numeric index
1159 */
1163 */
1160 Notebook.prototype.toggle_output_scroll = function (index) {
1164 Notebook.prototype.toggle_output_scroll = function (index) {
1161 var i = this.index_or_selected(index);
1165 var i = this.index_or_selected(index);
1162 this.get_cell(i).toggle_output_scroll();
1166 this.get_cell(i).toggle_output_scroll();
1163 };
1167 };
1164
1168
1165 /**
1169 /**
1166 * Hide each code cell's output area.
1170 * Hide each code cell's output area.
1167 *
1171 *
1168 * @method collapse_all_output
1172 * @method collapse_all_output
1169 */
1173 */
1170 Notebook.prototype.collapse_all_output = function () {
1174 Notebook.prototype.collapse_all_output = function () {
1171 var ncells = this.ncells();
1175 var ncells = this.ncells();
1172 var cells = this.get_cells();
1176 var cells = this.get_cells();
1173 for (var i=0; i<ncells; i++) {
1177 for (var i=0; i<ncells; i++) {
1174 if (cells[i] instanceof IPython.CodeCell) {
1178 if (cells[i] instanceof IPython.CodeCell) {
1175 cells[i].output_area.collapse();
1179 cells[i].output_area.collapse();
1176 }
1180 }
1177 };
1181 };
1178 // this should not be set if the `collapse` key is removed from nbformat
1182 // this should not be set if the `collapse` key is removed from nbformat
1179 this.set_dirty(true);
1183 this.set_dirty(true);
1180 };
1184 };
1181
1185
1182 /**
1186 /**
1183 * Expand each code cell's output area, and add a scrollbar for long output.
1187 * Expand each code cell's output area, and add a scrollbar for long output.
1184 *
1188 *
1185 * @method scroll_all_output
1189 * @method scroll_all_output
1186 */
1190 */
1187 Notebook.prototype.scroll_all_output = function () {
1191 Notebook.prototype.scroll_all_output = function () {
1188 var ncells = this.ncells();
1192 var ncells = this.ncells();
1189 var cells = this.get_cells();
1193 var cells = this.get_cells();
1190 for (var i=0; i<ncells; i++) {
1194 for (var i=0; i<ncells; i++) {
1191 if (cells[i] instanceof IPython.CodeCell) {
1195 if (cells[i] instanceof IPython.CodeCell) {
1192 cells[i].output_area.expand();
1196 cells[i].output_area.expand();
1193 cells[i].output_area.scroll_if_long();
1197 cells[i].output_area.scroll_if_long();
1194 }
1198 }
1195 };
1199 };
1196 // this should not be set if the `collapse` key is removed from nbformat
1200 // this should not be set if the `collapse` key is removed from nbformat
1197 this.set_dirty(true);
1201 this.set_dirty(true);
1198 };
1202 };
1199
1203
1200 /**
1204 /**
1201 * Expand each code cell's output area, and remove scrollbars.
1205 * Expand each code cell's output area, and remove scrollbars.
1202 *
1206 *
1203 * @method expand_all_output
1207 * @method expand_all_output
1204 */
1208 */
1205 Notebook.prototype.expand_all_output = function () {
1209 Notebook.prototype.expand_all_output = function () {
1206 var ncells = this.ncells();
1210 var ncells = this.ncells();
1207 var cells = this.get_cells();
1211 var cells = this.get_cells();
1208 for (var i=0; i<ncells; i++) {
1212 for (var i=0; i<ncells; i++) {
1209 if (cells[i] instanceof IPython.CodeCell) {
1213 if (cells[i] instanceof IPython.CodeCell) {
1210 cells[i].output_area.expand();
1214 cells[i].output_area.expand();
1211 cells[i].output_area.unscroll_area();
1215 cells[i].output_area.unscroll_area();
1212 }
1216 }
1213 };
1217 };
1214 // this should not be set if the `collapse` key is removed from nbformat
1218 // this should not be set if the `collapse` key is removed from nbformat
1215 this.set_dirty(true);
1219 this.set_dirty(true);
1216 };
1220 };
1217
1221
1218 /**
1222 /**
1219 * Clear each code cell's output area.
1223 * Clear each code cell's output area.
1220 *
1224 *
1221 * @method clear_all_output
1225 * @method clear_all_output
1222 */
1226 */
1223 Notebook.prototype.clear_all_output = function () {
1227 Notebook.prototype.clear_all_output = function () {
1224 var ncells = this.ncells();
1228 var ncells = this.ncells();
1225 var cells = this.get_cells();
1229 var cells = this.get_cells();
1226 for (var i=0; i<ncells; i++) {
1230 for (var i=0; i<ncells; i++) {
1227 if (cells[i] instanceof IPython.CodeCell) {
1231 if (cells[i] instanceof IPython.CodeCell) {
1228 cells[i].clear_output();
1232 cells[i].clear_output();
1229 // Make all In[] prompts blank, as well
1233 // Make all In[] prompts blank, as well
1230 // TODO: make this configurable (via checkbox?)
1234 // TODO: make this configurable (via checkbox?)
1231 cells[i].set_input_prompt();
1235 cells[i].set_input_prompt();
1232 }
1236 }
1233 };
1237 };
1234 this.set_dirty(true);
1238 this.set_dirty(true);
1235 };
1239 };
1236
1240
1237
1241
1238 // Other cell functions: line numbers, ...
1242 // Other cell functions: line numbers, ...
1239
1243
1240 /**
1244 /**
1241 * Toggle line numbers in the selected cell's input area.
1245 * Toggle line numbers in the selected cell's input area.
1242 *
1246 *
1243 * @method cell_toggle_line_numbers
1247 * @method cell_toggle_line_numbers
1244 */
1248 */
1245 Notebook.prototype.cell_toggle_line_numbers = function() {
1249 Notebook.prototype.cell_toggle_line_numbers = function() {
1246 this.get_selected_cell().toggle_line_numbers();
1250 this.get_selected_cell().toggle_line_numbers();
1247 };
1251 };
1248
1252
1249 // Session related things
1253 // Session related things
1250
1254
1251 /**
1255 /**
1252 * Start a new session and set it on each code cell.
1256 * Start a new session and set it on each code cell.
1253 *
1257 *
1254 * @method start_session
1258 * @method start_session
1255 */
1259 */
1256 Notebook.prototype.start_session = function () {
1260 Notebook.prototype.start_session = function () {
1257 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1261 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1258 this.session.start($.proxy(this._session_started, this));
1262 this.session.start($.proxy(this._session_started, this));
1259 };
1263 };
1260
1264
1261
1265
1262 /**
1266 /**
1263 * Once a session is started, link the code cells to the kernel
1267 * Once a session is started, link the code cells to the kernel
1264 *
1268 *
1265 */
1269 */
1266 Notebook.prototype._session_started = function(){
1270 Notebook.prototype._session_started = function(){
1267 this.kernel = this.session.kernel;
1271 this.kernel = this.session.kernel;
1268 var ncells = this.ncells();
1272 var ncells = this.ncells();
1269 for (var i=0; i<ncells; i++) {
1273 for (var i=0; i<ncells; i++) {
1270 var cell = this.get_cell(i);
1274 var cell = this.get_cell(i);
1271 if (cell instanceof IPython.CodeCell) {
1275 if (cell instanceof IPython.CodeCell) {
1272 cell.set_kernel(this.session.kernel);
1276 cell.set_kernel(this.session.kernel);
1273 };
1277 };
1274 };
1278 };
1275 };
1279 };
1276
1280
1277 /**
1281 /**
1278 * Prompt the user to restart the IPython kernel.
1282 * Prompt the user to restart the IPython kernel.
1279 *
1283 *
1280 * @method restart_kernel
1284 * @method restart_kernel
1281 */
1285 */
1282 Notebook.prototype.restart_kernel = function () {
1286 Notebook.prototype.restart_kernel = function () {
1283 var that = this;
1287 var that = this;
1284 IPython.dialog.modal({
1288 IPython.dialog.modal({
1285 title : "Restart kernel or continue running?",
1289 title : "Restart kernel or continue running?",
1286 body : $("<p/>").html(
1290 body : $("<p/>").html(
1287 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1291 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1288 ),
1292 ),
1289 buttons : {
1293 buttons : {
1290 "Continue running" : {},
1294 "Continue running" : {},
1291 "Restart" : {
1295 "Restart" : {
1292 "class" : "btn-danger",
1296 "class" : "btn-danger",
1293 "click" : function() {
1297 "click" : function() {
1294 that.session.restart_kernel();
1298 that.session.restart_kernel();
1295 }
1299 }
1296 }
1300 }
1297 }
1301 }
1298 });
1302 });
1299 };
1303 };
1300
1304
1301 /**
1305 /**
1302 * Run the selected cell.
1306 * Run the selected cell.
1303 *
1307 *
1304 * Execute or render cell outputs.
1308 * Execute or render cell outputs.
1305 *
1309 *
1306 * @method execute_selected_cell
1310 * @method execute_selected_cell
1307 * @param {Object} options Customize post-execution behavior
1311 * @param {Object} options Customize post-execution behavior
1308 */
1312 */
1309 Notebook.prototype.execute_selected_cell = function (mode) {
1313 Notebook.prototype.execute_selected_cell = function (mode) {
1310 // mode = shift, ctrl, alt
1314 // mode = shift, ctrl, alt
1311 mode = mode || 'shift'
1315 mode = mode || 'shift'
1312 var cell = this.get_selected_cell();
1316 var cell = this.get_selected_cell();
1313 var cell_index = this.find_cell_index(cell);
1317 var cell_index = this.find_cell_index(cell);
1314
1318
1315 cell.execute();
1319 cell.execute();
1316
1320
1317 // If we are at the end always insert a new cell and return
1321 // If we are at the end always insert a new cell and return
1318 if (cell_index === (this.ncells()-1)) {
1322 if (cell_index === (this.ncells()-1)) {
1319 this.insert_cell_below('code');
1323 this.insert_cell_below('code');
1320 this.select(cell_index+1);
1324 this.select(cell_index+1);
1321 this.edit_mode();
1325 this.edit_mode();
1322 this.scroll_to_bottom();
1326 this.scroll_to_bottom();
1323 this.set_dirty(true);
1327 this.set_dirty(true);
1324 return;
1328 return;
1325 }
1329 }
1326
1330
1327 if (mode === 'shift') {
1331 if (mode === 'shift') {
1328 this.command_mode();
1332 this.command_mode();
1329 } else if (mode === 'ctrl') {
1333 } else if (mode === 'ctrl') {
1330 this.select(cell_index+1);
1334 this.select(cell_index+1);
1331 this.get_cell(cell_index+1).focus_cell();
1335 this.get_cell(cell_index+1).focus_cell();
1332 } else if (mode === 'alt') {
1336 } else if (mode === 'alt') {
1333 // Only insert a new cell, if we ended up in an already populated cell
1337 // Only insert a new cell, if we ended up in an already populated cell
1334 var next_text = this.get_cell(cell_index+1).get_text();
1338 var next_text = this.get_cell(cell_index+1).get_text();
1335 if (/\S/.test(next_text) === true) {
1339 if (/\S/.test(next_text) === true) {
1336 this.insert_cell_below('code');
1340 this.insert_cell_below('code');
1337 }
1341 }
1338 this.select(cell_index+1);
1342 this.select(cell_index+1);
1339 this.edit_mode();
1343 this.edit_mode();
1340 }
1344 }
1341 this.set_dirty(true);
1345 this.set_dirty(true);
1342 };
1346 };
1343
1347
1344
1348
1345 /**
1349 /**
1346 * Execute all cells below the selected cell.
1350 * Execute all cells below the selected cell.
1347 *
1351 *
1348 * @method execute_cells_below
1352 * @method execute_cells_below
1349 */
1353 */
1350 Notebook.prototype.execute_cells_below = function () {
1354 Notebook.prototype.execute_cells_below = function () {
1351 this.execute_cell_range(this.get_selected_index(), this.ncells());
1355 this.execute_cell_range(this.get_selected_index(), this.ncells());
1352 this.scroll_to_bottom();
1356 this.scroll_to_bottom();
1353 };
1357 };
1354
1358
1355 /**
1359 /**
1356 * Execute all cells above the selected cell.
1360 * Execute all cells above the selected cell.
1357 *
1361 *
1358 * @method execute_cells_above
1362 * @method execute_cells_above
1359 */
1363 */
1360 Notebook.prototype.execute_cells_above = function () {
1364 Notebook.prototype.execute_cells_above = function () {
1361 this.execute_cell_range(0, this.get_selected_index());
1365 this.execute_cell_range(0, this.get_selected_index());
1362 };
1366 };
1363
1367
1364 /**
1368 /**
1365 * Execute all cells.
1369 * Execute all cells.
1366 *
1370 *
1367 * @method execute_all_cells
1371 * @method execute_all_cells
1368 */
1372 */
1369 Notebook.prototype.execute_all_cells = function () {
1373 Notebook.prototype.execute_all_cells = function () {
1370 this.execute_cell_range(0, this.ncells());
1374 this.execute_cell_range(0, this.ncells());
1371 this.scroll_to_bottom();
1375 this.scroll_to_bottom();
1372 };
1376 };
1373
1377
1374 /**
1378 /**
1375 * Execute a contiguous range of cells.
1379 * Execute a contiguous range of cells.
1376 *
1380 *
1377 * @method execute_cell_range
1381 * @method execute_cell_range
1378 * @param {Number} start Index of the first cell to execute (inclusive)
1382 * @param {Number} start Index of the first cell to execute (inclusive)
1379 * @param {Number} end Index of the last cell to execute (exclusive)
1383 * @param {Number} end Index of the last cell to execute (exclusive)
1380 */
1384 */
1381 Notebook.prototype.execute_cell_range = function (start, end) {
1385 Notebook.prototype.execute_cell_range = function (start, end) {
1382 for (var i=start; i<end; i++) {
1386 for (var i=start; i<end; i++) {
1383 this.select(i);
1387 this.select(i);
1384 this.execute_selected_cell({add_new:false});
1388 this.execute_selected_cell({add_new:false});
1385 };
1389 };
1386 };
1390 };
1387
1391
1388 // Persistance and loading
1392 // Persistance and loading
1389
1393
1390 /**
1394 /**
1391 * Getter method for this notebook's name.
1395 * Getter method for this notebook's name.
1392 *
1396 *
1393 * @method get_notebook_name
1397 * @method get_notebook_name
1394 * @return {String} This notebook's name
1398 * @return {String} This notebook's name
1395 */
1399 */
1396 Notebook.prototype.get_notebook_name = function () {
1400 Notebook.prototype.get_notebook_name = function () {
1397 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1401 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1398 return nbname;
1402 return nbname;
1399 };
1403 };
1400
1404
1401 /**
1405 /**
1402 * Setter method for this notebook's name.
1406 * Setter method for this notebook's name.
1403 *
1407 *
1404 * @method set_notebook_name
1408 * @method set_notebook_name
1405 * @param {String} name A new name for this notebook
1409 * @param {String} name A new name for this notebook
1406 */
1410 */
1407 Notebook.prototype.set_notebook_name = function (name) {
1411 Notebook.prototype.set_notebook_name = function (name) {
1408 this.notebook_name = name;
1412 this.notebook_name = name;
1409 };
1413 };
1410
1414
1411 /**
1415 /**
1412 * Check that a notebook's name is valid.
1416 * Check that a notebook's name is valid.
1413 *
1417 *
1414 * @method test_notebook_name
1418 * @method test_notebook_name
1415 * @param {String} nbname A name for this notebook
1419 * @param {String} nbname A name for this notebook
1416 * @return {Boolean} True if the name is valid, false if invalid
1420 * @return {Boolean} True if the name is valid, false if invalid
1417 */
1421 */
1418 Notebook.prototype.test_notebook_name = function (nbname) {
1422 Notebook.prototype.test_notebook_name = function (nbname) {
1419 nbname = nbname || '';
1423 nbname = nbname || '';
1420 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1424 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1421 return true;
1425 return true;
1422 } else {
1426 } else {
1423 return false;
1427 return false;
1424 };
1428 };
1425 };
1429 };
1426
1430
1427 /**
1431 /**
1428 * Load a notebook from JSON (.ipynb).
1432 * Load a notebook from JSON (.ipynb).
1429 *
1433 *
1430 * This currently handles one worksheet: others are deleted.
1434 * This currently handles one worksheet: others are deleted.
1431 *
1435 *
1432 * @method fromJSON
1436 * @method fromJSON
1433 * @param {Object} data JSON representation of a notebook
1437 * @param {Object} data JSON representation of a notebook
1434 */
1438 */
1435 Notebook.prototype.fromJSON = function (data) {
1439 Notebook.prototype.fromJSON = function (data) {
1436 var content = data.content;
1440 var content = data.content;
1437 var ncells = this.ncells();
1441 var ncells = this.ncells();
1438 var i;
1442 var i;
1439 for (i=0; i<ncells; i++) {
1443 for (i=0; i<ncells; i++) {
1440 // Always delete cell 0 as they get renumbered as they are deleted.
1444 // Always delete cell 0 as they get renumbered as they are deleted.
1441 this.delete_cell(0);
1445 this.delete_cell(0);
1442 };
1446 };
1443 // Save the metadata and name.
1447 // Save the metadata and name.
1444 this.metadata = content.metadata;
1448 this.metadata = content.metadata;
1445 this.notebook_name = data.name;
1449 this.notebook_name = data.name;
1446 // Only handle 1 worksheet for now.
1450 // Only handle 1 worksheet for now.
1447 var worksheet = content.worksheets[0];
1451 var worksheet = content.worksheets[0];
1448 if (worksheet !== undefined) {
1452 if (worksheet !== undefined) {
1449 if (worksheet.metadata) {
1453 if (worksheet.metadata) {
1450 this.worksheet_metadata = worksheet.metadata;
1454 this.worksheet_metadata = worksheet.metadata;
1451 }
1455 }
1452 var new_cells = worksheet.cells;
1456 var new_cells = worksheet.cells;
1453 ncells = new_cells.length;
1457 ncells = new_cells.length;
1454 var cell_data = null;
1458 var cell_data = null;
1455 var new_cell = null;
1459 var new_cell = null;
1456 for (i=0; i<ncells; i++) {
1460 for (i=0; i<ncells; i++) {
1457 cell_data = new_cells[i];
1461 cell_data = new_cells[i];
1458 // VERSIONHACK: plaintext -> raw
1462 // VERSIONHACK: plaintext -> raw
1459 // handle never-released plaintext name for raw cells
1463 // handle never-released plaintext name for raw cells
1460 if (cell_data.cell_type === 'plaintext'){
1464 if (cell_data.cell_type === 'plaintext'){
1461 cell_data.cell_type = 'raw';
1465 cell_data.cell_type = 'raw';
1462 }
1466 }
1463
1467
1464 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1468 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1465 new_cell.fromJSON(cell_data);
1469 new_cell.fromJSON(cell_data);
1466 };
1470 };
1467 };
1471 };
1468 if (content.worksheets.length > 1) {
1472 if (content.worksheets.length > 1) {
1469 IPython.dialog.modal({
1473 IPython.dialog.modal({
1470 title : "Multiple worksheets",
1474 title : "Multiple worksheets",
1471 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1475 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1472 "but this version of IPython can only handle the first. " +
1476 "but this version of IPython can only handle the first. " +
1473 "If you save this notebook, worksheets after the first will be lost.",
1477 "If you save this notebook, worksheets after the first will be lost.",
1474 buttons : {
1478 buttons : {
1475 OK : {
1479 OK : {
1476 class : "btn-danger"
1480 class : "btn-danger"
1477 }
1481 }
1478 }
1482 }
1479 });
1483 });
1480 }
1484 }
1481 };
1485 };
1482
1486
1483 /**
1487 /**
1484 * Dump this notebook into a JSON-friendly object.
1488 * Dump this notebook into a JSON-friendly object.
1485 *
1489 *
1486 * @method toJSON
1490 * @method toJSON
1487 * @return {Object} A JSON-friendly representation of this notebook.
1491 * @return {Object} A JSON-friendly representation of this notebook.
1488 */
1492 */
1489 Notebook.prototype.toJSON = function () {
1493 Notebook.prototype.toJSON = function () {
1490 var cells = this.get_cells();
1494 var cells = this.get_cells();
1491 var ncells = cells.length;
1495 var ncells = cells.length;
1492 var cell_array = new Array(ncells);
1496 var cell_array = new Array(ncells);
1493 for (var i=0; i<ncells; i++) {
1497 for (var i=0; i<ncells; i++) {
1494 cell_array[i] = cells[i].toJSON();
1498 cell_array[i] = cells[i].toJSON();
1495 };
1499 };
1496 var data = {
1500 var data = {
1497 // Only handle 1 worksheet for now.
1501 // Only handle 1 worksheet for now.
1498 worksheets : [{
1502 worksheets : [{
1499 cells: cell_array,
1503 cells: cell_array,
1500 metadata: this.worksheet_metadata
1504 metadata: this.worksheet_metadata
1501 }],
1505 }],
1502 metadata : this.metadata
1506 metadata : this.metadata
1503 };
1507 };
1504 return data;
1508 return data;
1505 };
1509 };
1506
1510
1507 /**
1511 /**
1508 * Start an autosave timer, for periodically saving the notebook.
1512 * Start an autosave timer, for periodically saving the notebook.
1509 *
1513 *
1510 * @method set_autosave_interval
1514 * @method set_autosave_interval
1511 * @param {Integer} interval the autosave interval in milliseconds
1515 * @param {Integer} interval the autosave interval in milliseconds
1512 */
1516 */
1513 Notebook.prototype.set_autosave_interval = function (interval) {
1517 Notebook.prototype.set_autosave_interval = function (interval) {
1514 var that = this;
1518 var that = this;
1515 // clear previous interval, so we don't get simultaneous timers
1519 // clear previous interval, so we don't get simultaneous timers
1516 if (this.autosave_timer) {
1520 if (this.autosave_timer) {
1517 clearInterval(this.autosave_timer);
1521 clearInterval(this.autosave_timer);
1518 }
1522 }
1519
1523
1520 this.autosave_interval = this.minimum_autosave_interval = interval;
1524 this.autosave_interval = this.minimum_autosave_interval = interval;
1521 if (interval) {
1525 if (interval) {
1522 this.autosave_timer = setInterval(function() {
1526 this.autosave_timer = setInterval(function() {
1523 if (that.dirty) {
1527 if (that.dirty) {
1524 that.save_notebook();
1528 that.save_notebook();
1525 }
1529 }
1526 }, interval);
1530 }, interval);
1527 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1531 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1528 } else {
1532 } else {
1529 this.autosave_timer = null;
1533 this.autosave_timer = null;
1530 $([IPython.events]).trigger("autosave_disabled.Notebook");
1534 $([IPython.events]).trigger("autosave_disabled.Notebook");
1531 };
1535 };
1532 };
1536 };
1533
1537
1534 /**
1538 /**
1535 * Save this notebook on the server.
1539 * Save this notebook on the server.
1536 *
1540 *
1537 * @method save_notebook
1541 * @method save_notebook
1538 */
1542 */
1539 Notebook.prototype.save_notebook = function (extra_settings) {
1543 Notebook.prototype.save_notebook = function (extra_settings) {
1540 // Create a JSON model to be sent to the server.
1544 // Create a JSON model to be sent to the server.
1541 var model = {};
1545 var model = {};
1542 model.name = this.notebook_name;
1546 model.name = this.notebook_name;
1543 model.path = this.notebook_path;
1547 model.path = this.notebook_path;
1544 model.content = this.toJSON();
1548 model.content = this.toJSON();
1545 model.content.nbformat = this.nbformat;
1549 model.content.nbformat = this.nbformat;
1546 model.content.nbformat_minor = this.nbformat_minor;
1550 model.content.nbformat_minor = this.nbformat_minor;
1547 // time the ajax call for autosave tuning purposes.
1551 // time the ajax call for autosave tuning purposes.
1548 var start = new Date().getTime();
1552 var start = new Date().getTime();
1549 // We do the call with settings so we can set cache to false.
1553 // We do the call with settings so we can set cache to false.
1550 var settings = {
1554 var settings = {
1551 processData : false,
1555 processData : false,
1552 cache : false,
1556 cache : false,
1553 type : "PUT",
1557 type : "PUT",
1554 data : JSON.stringify(model),
1558 data : JSON.stringify(model),
1555 headers : {'Content-Type': 'application/json'},
1559 headers : {'Content-Type': 'application/json'},
1556 success : $.proxy(this.save_notebook_success, this, start),
1560 success : $.proxy(this.save_notebook_success, this, start),
1557 error : $.proxy(this.save_notebook_error, this)
1561 error : $.proxy(this.save_notebook_error, this)
1558 };
1562 };
1559 if (extra_settings) {
1563 if (extra_settings) {
1560 for (var key in extra_settings) {
1564 for (var key in extra_settings) {
1561 settings[key] = extra_settings[key];
1565 settings[key] = extra_settings[key];
1562 }
1566 }
1563 }
1567 }
1564 $([IPython.events]).trigger('notebook_saving.Notebook');
1568 $([IPython.events]).trigger('notebook_saving.Notebook');
1565 var url = utils.url_join_encode(
1569 var url = utils.url_join_encode(
1566 this._baseProjectUrl,
1570 this._baseProjectUrl,
1567 'api/notebooks',
1571 'api/notebooks',
1568 this.notebook_path,
1572 this.notebook_path,
1569 this.notebook_name
1573 this.notebook_name
1570 );
1574 );
1571 $.ajax(url, settings);
1575 $.ajax(url, settings);
1572 };
1576 };
1573
1577
1574 /**
1578 /**
1575 * Success callback for saving a notebook.
1579 * Success callback for saving a notebook.
1576 *
1580 *
1577 * @method save_notebook_success
1581 * @method save_notebook_success
1578 * @param {Integer} start the time when the save request started
1582 * @param {Integer} start the time when the save request started
1579 * @param {Object} data JSON representation of a notebook
1583 * @param {Object} data JSON representation of a notebook
1580 * @param {String} status Description of response status
1584 * @param {String} status Description of response status
1581 * @param {jqXHR} xhr jQuery Ajax object
1585 * @param {jqXHR} xhr jQuery Ajax object
1582 */
1586 */
1583 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1587 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1584 this.set_dirty(false);
1588 this.set_dirty(false);
1585 $([IPython.events]).trigger('notebook_saved.Notebook');
1589 $([IPython.events]).trigger('notebook_saved.Notebook');
1586 this._update_autosave_interval(start);
1590 this._update_autosave_interval(start);
1587 if (this._checkpoint_after_save) {
1591 if (this._checkpoint_after_save) {
1588 this.create_checkpoint();
1592 this.create_checkpoint();
1589 this._checkpoint_after_save = false;
1593 this._checkpoint_after_save = false;
1590 };
1594 };
1591 };
1595 };
1592
1596
1593 /**
1597 /**
1594 * update the autosave interval based on how long the last save took
1598 * update the autosave interval based on how long the last save took
1595 *
1599 *
1596 * @method _update_autosave_interval
1600 * @method _update_autosave_interval
1597 * @param {Integer} timestamp when the save request started
1601 * @param {Integer} timestamp when the save request started
1598 */
1602 */
1599 Notebook.prototype._update_autosave_interval = function (start) {
1603 Notebook.prototype._update_autosave_interval = function (start) {
1600 var duration = (new Date().getTime() - start);
1604 var duration = (new Date().getTime() - start);
1601 if (this.autosave_interval) {
1605 if (this.autosave_interval) {
1602 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1606 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1603 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1607 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1604 // round to 10 seconds, otherwise we will be setting a new interval too often
1608 // round to 10 seconds, otherwise we will be setting a new interval too often
1605 interval = 10000 * Math.round(interval / 10000);
1609 interval = 10000 * Math.round(interval / 10000);
1606 // set new interval, if it's changed
1610 // set new interval, if it's changed
1607 if (interval != this.autosave_interval) {
1611 if (interval != this.autosave_interval) {
1608 this.set_autosave_interval(interval);
1612 this.set_autosave_interval(interval);
1609 }
1613 }
1610 }
1614 }
1611 };
1615 };
1612
1616
1613 /**
1617 /**
1614 * Failure callback for saving a notebook.
1618 * Failure callback for saving a notebook.
1615 *
1619 *
1616 * @method save_notebook_error
1620 * @method save_notebook_error
1617 * @param {jqXHR} xhr jQuery Ajax object
1621 * @param {jqXHR} xhr jQuery Ajax object
1618 * @param {String} status Description of response status
1622 * @param {String} status Description of response status
1619 * @param {String} error HTTP error message
1623 * @param {String} error HTTP error message
1620 */
1624 */
1621 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1625 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1622 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1626 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1623 };
1627 };
1624
1628
1625 Notebook.prototype.new_notebook = function(){
1629 Notebook.prototype.new_notebook = function(){
1626 var path = this.notebook_path;
1630 var path = this.notebook_path;
1627 var base_project_url = this._baseProjectUrl;
1631 var base_project_url = this._baseProjectUrl;
1628 var settings = {
1632 var settings = {
1629 processData : false,
1633 processData : false,
1630 cache : false,
1634 cache : false,
1631 type : "POST",
1635 type : "POST",
1632 dataType : "json",
1636 dataType : "json",
1633 async : false,
1637 async : false,
1634 success : function (data, status, xhr){
1638 success : function (data, status, xhr){
1635 var notebook_name = data.name;
1639 var notebook_name = data.name;
1636 window.open(
1640 window.open(
1637 utils.url_join_encode(
1641 utils.url_join_encode(
1638 base_project_url,
1642 base_project_url,
1639 'notebooks',
1643 'notebooks',
1640 path,
1644 path,
1641 notebook_name
1645 notebook_name
1642 ),
1646 ),
1643 '_blank'
1647 '_blank'
1644 );
1648 );
1645 }
1649 }
1646 };
1650 };
1647 var url = utils.url_join_encode(
1651 var url = utils.url_join_encode(
1648 base_project_url,
1652 base_project_url,
1649 'api/notebooks',
1653 'api/notebooks',
1650 path
1654 path
1651 );
1655 );
1652 $.ajax(url,settings);
1656 $.ajax(url,settings);
1653 };
1657 };
1654
1658
1655
1659
1656 Notebook.prototype.copy_notebook = function(){
1660 Notebook.prototype.copy_notebook = function(){
1657 var path = this.notebook_path;
1661 var path = this.notebook_path;
1658 var base_project_url = this._baseProjectUrl;
1662 var base_project_url = this._baseProjectUrl;
1659 var settings = {
1663 var settings = {
1660 processData : false,
1664 processData : false,
1661 cache : false,
1665 cache : false,
1662 type : "POST",
1666 type : "POST",
1663 dataType : "json",
1667 dataType : "json",
1664 data : JSON.stringify({copy_from : this.notebook_name}),
1668 data : JSON.stringify({copy_from : this.notebook_name}),
1665 async : false,
1669 async : false,
1666 success : function (data, status, xhr) {
1670 success : function (data, status, xhr) {
1667 window.open(utils.url_join_encode(
1671 window.open(utils.url_join_encode(
1668 base_project_url,
1672 base_project_url,
1669 'notebooks',
1673 'notebooks',
1670 data.path,
1674 data.path,
1671 data.name
1675 data.name
1672 ), '_blank');
1676 ), '_blank');
1673 }
1677 }
1674 };
1678 };
1675 var url = utils.url_join_encode(
1679 var url = utils.url_join_encode(
1676 base_project_url,
1680 base_project_url,
1677 'api/notebooks',
1681 'api/notebooks',
1678 path
1682 path
1679 );
1683 );
1680 $.ajax(url,settings);
1684 $.ajax(url,settings);
1681 };
1685 };
1682
1686
1683 Notebook.prototype.rename = function (nbname) {
1687 Notebook.prototype.rename = function (nbname) {
1684 var that = this;
1688 var that = this;
1685 var data = {name: nbname + '.ipynb'};
1689 var data = {name: nbname + '.ipynb'};
1686 var settings = {
1690 var settings = {
1687 processData : false,
1691 processData : false,
1688 cache : false,
1692 cache : false,
1689 type : "PATCH",
1693 type : "PATCH",
1690 data : JSON.stringify(data),
1694 data : JSON.stringify(data),
1691 dataType: "json",
1695 dataType: "json",
1692 headers : {'Content-Type': 'application/json'},
1696 headers : {'Content-Type': 'application/json'},
1693 success : $.proxy(that.rename_success, this),
1697 success : $.proxy(that.rename_success, this),
1694 error : $.proxy(that.rename_error, this)
1698 error : $.proxy(that.rename_error, this)
1695 };
1699 };
1696 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1700 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1697 var url = utils.url_join_encode(
1701 var url = utils.url_join_encode(
1698 this._baseProjectUrl,
1702 this._baseProjectUrl,
1699 'api/notebooks',
1703 'api/notebooks',
1700 this.notebook_path,
1704 this.notebook_path,
1701 this.notebook_name
1705 this.notebook_name
1702 );
1706 );
1703 $.ajax(url, settings);
1707 $.ajax(url, settings);
1704 };
1708 };
1705
1709
1706
1710
1707 Notebook.prototype.rename_success = function (json, status, xhr) {
1711 Notebook.prototype.rename_success = function (json, status, xhr) {
1708 this.notebook_name = json.name;
1712 this.notebook_name = json.name;
1709 var name = this.notebook_name;
1713 var name = this.notebook_name;
1710 var path = json.path;
1714 var path = json.path;
1711 this.session.rename_notebook(name, path);
1715 this.session.rename_notebook(name, path);
1712 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1716 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1713 }
1717 }
1714
1718
1715 Notebook.prototype.rename_error = function (xhr, status, error) {
1719 Notebook.prototype.rename_error = function (xhr, status, error) {
1716 var that = this;
1720 var that = this;
1717 var dialog = $('<div/>').append(
1721 var dialog = $('<div/>').append(
1718 $("<p/>").addClass("rename-message")
1722 $("<p/>").addClass("rename-message")
1719 .html('This notebook name already exists.')
1723 .html('This notebook name already exists.')
1720 )
1724 )
1721 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1725 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1722 IPython.dialog.modal({
1726 IPython.dialog.modal({
1723 title: "Notebook Rename Error!",
1727 title: "Notebook Rename Error!",
1724 body: dialog,
1728 body: dialog,
1725 buttons : {
1729 buttons : {
1726 "Cancel": {},
1730 "Cancel": {},
1727 "OK": {
1731 "OK": {
1728 class: "btn-primary",
1732 class: "btn-primary",
1729 click: function () {
1733 click: function () {
1730 IPython.save_widget.rename_notebook();
1734 IPython.save_widget.rename_notebook();
1731 }}
1735 }}
1732 },
1736 },
1733 open : function (event, ui) {
1737 open : function (event, ui) {
1734 var that = $(this);
1738 var that = $(this);
1735 // Upon ENTER, click the OK button.
1739 // Upon ENTER, click the OK button.
1736 that.find('input[type="text"]').keydown(function (event, ui) {
1740 that.find('input[type="text"]').keydown(function (event, ui) {
1737 if (event.which === utils.keycodes.ENTER) {
1741 if (event.which === utils.keycodes.ENTER) {
1738 that.find('.btn-primary').first().click();
1742 that.find('.btn-primary').first().click();
1739 }
1743 }
1740 });
1744 });
1741 that.find('input[type="text"]').focus();
1745 that.find('input[type="text"]').focus();
1742 }
1746 }
1743 });
1747 });
1744 }
1748 }
1745
1749
1746 /**
1750 /**
1747 * Request a notebook's data from the server.
1751 * Request a notebook's data from the server.
1748 *
1752 *
1749 * @method load_notebook
1753 * @method load_notebook
1750 * @param {String} notebook_name and path A notebook to load
1754 * @param {String} notebook_name and path A notebook to load
1751 */
1755 */
1752 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1756 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1753 var that = this;
1757 var that = this;
1754 this.notebook_name = notebook_name;
1758 this.notebook_name = notebook_name;
1755 this.notebook_path = notebook_path;
1759 this.notebook_path = notebook_path;
1756 // We do the call with settings so we can set cache to false.
1760 // We do the call with settings so we can set cache to false.
1757 var settings = {
1761 var settings = {
1758 processData : false,
1762 processData : false,
1759 cache : false,
1763 cache : false,
1760 type : "GET",
1764 type : "GET",
1761 dataType : "json",
1765 dataType : "json",
1762 success : $.proxy(this.load_notebook_success,this),
1766 success : $.proxy(this.load_notebook_success,this),
1763 error : $.proxy(this.load_notebook_error,this),
1767 error : $.proxy(this.load_notebook_error,this),
1764 };
1768 };
1765 $([IPython.events]).trigger('notebook_loading.Notebook');
1769 $([IPython.events]).trigger('notebook_loading.Notebook');
1766 var url = utils.url_join_encode(
1770 var url = utils.url_join_encode(
1767 this._baseProjectUrl,
1771 this._baseProjectUrl,
1768 'api/notebooks',
1772 'api/notebooks',
1769 this.notebook_path,
1773 this.notebook_path,
1770 this.notebook_name
1774 this.notebook_name
1771 );
1775 );
1772 $.ajax(url, settings);
1776 $.ajax(url, settings);
1773 };
1777 };
1774
1778
1775 /**
1779 /**
1776 * Success callback for loading a notebook from the server.
1780 * Success callback for loading a notebook from the server.
1777 *
1781 *
1778 * Load notebook data from the JSON response.
1782 * Load notebook data from the JSON response.
1779 *
1783 *
1780 * @method load_notebook_success
1784 * @method load_notebook_success
1781 * @param {Object} data JSON representation of a notebook
1785 * @param {Object} data JSON representation of a notebook
1782 * @param {String} status Description of response status
1786 * @param {String} status Description of response status
1783 * @param {jqXHR} xhr jQuery Ajax object
1787 * @param {jqXHR} xhr jQuery Ajax object
1784 */
1788 */
1785 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1789 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1786 this.fromJSON(data);
1790 this.fromJSON(data);
1787 if (this.ncells() === 0) {
1791 if (this.ncells() === 0) {
1788 this.insert_cell_below('code');
1792 this.insert_cell_below('code');
1789 this.select(0);
1793 this.select(0);
1790 this.edit_mode();
1794 this.edit_mode();
1791 } else {
1795 } else {
1792 this.select(0);
1796 this.select(0);
1793 this.command_mode();
1797 this.command_mode();
1794 };
1798 };
1795 this.set_dirty(false);
1799 this.set_dirty(false);
1796 this.scroll_to_top();
1800 this.scroll_to_top();
1797 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1801 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1798 var msg = "This notebook has been converted from an older " +
1802 var msg = "This notebook has been converted from an older " +
1799 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1803 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1800 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1804 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1801 "newer notebook format will be used and older versions of IPython " +
1805 "newer notebook format will be used and older versions of IPython " +
1802 "may not be able to read it. To keep the older version, close the " +
1806 "may not be able to read it. To keep the older version, close the " +
1803 "notebook without saving it.";
1807 "notebook without saving it.";
1804 IPython.dialog.modal({
1808 IPython.dialog.modal({
1805 title : "Notebook converted",
1809 title : "Notebook converted",
1806 body : msg,
1810 body : msg,
1807 buttons : {
1811 buttons : {
1808 OK : {
1812 OK : {
1809 class : "btn-primary"
1813 class : "btn-primary"
1810 }
1814 }
1811 }
1815 }
1812 });
1816 });
1813 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1817 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1814 var that = this;
1818 var that = this;
1815 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1819 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1816 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1820 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1817 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1821 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1818 this_vs + ". You can still work with this notebook, but some features " +
1822 this_vs + ". You can still work with this notebook, but some features " +
1819 "introduced in later notebook versions may not be available."
1823 "introduced in later notebook versions may not be available."
1820
1824
1821 IPython.dialog.modal({
1825 IPython.dialog.modal({
1822 title : "Newer Notebook",
1826 title : "Newer Notebook",
1823 body : msg,
1827 body : msg,
1824 buttons : {
1828 buttons : {
1825 OK : {
1829 OK : {
1826 class : "btn-danger"
1830 class : "btn-danger"
1827 }
1831 }
1828 }
1832 }
1829 });
1833 });
1830
1834
1831 }
1835 }
1832
1836
1833 // Create the session after the notebook is completely loaded to prevent
1837 // Create the session after the notebook is completely loaded to prevent
1834 // code execution upon loading, which is a security risk.
1838 // code execution upon loading, which is a security risk.
1835 if (this.session == null) {
1839 if (this.session == null) {
1836 this.start_session();
1840 this.start_session();
1837 }
1841 }
1838 // load our checkpoint list
1842 // load our checkpoint list
1839 this.list_checkpoints();
1843 this.list_checkpoints();
1840
1844
1841 // load toolbar state
1845 // load toolbar state
1842 if (this.metadata.celltoolbar) {
1846 if (this.metadata.celltoolbar) {
1843 IPython.CellToolbar.global_show();
1847 IPython.CellToolbar.global_show();
1844 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1848 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1845 }
1849 }
1846
1850
1847 $([IPython.events]).trigger('notebook_loaded.Notebook');
1851 $([IPython.events]).trigger('notebook_loaded.Notebook');
1848 };
1852 };
1849
1853
1850 /**
1854 /**
1851 * Failure callback for loading a notebook from the server.
1855 * Failure callback for loading a notebook from the server.
1852 *
1856 *
1853 * @method load_notebook_error
1857 * @method load_notebook_error
1854 * @param {jqXHR} xhr jQuery Ajax object
1858 * @param {jqXHR} xhr jQuery Ajax object
1855 * @param {String} status Description of response status
1859 * @param {String} status Description of response status
1856 * @param {String} error HTTP error message
1860 * @param {String} error HTTP error message
1857 */
1861 */
1858 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1862 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1859 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1863 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1860 if (xhr.status === 400) {
1864 if (xhr.status === 400) {
1861 var msg = error;
1865 var msg = error;
1862 } else if (xhr.status === 500) {
1866 } else if (xhr.status === 500) {
1863 var msg = "An unknown error occurred while loading this notebook. " +
1867 var msg = "An unknown error occurred while loading this notebook. " +
1864 "This version can load notebook formats " +
1868 "This version can load notebook formats " +
1865 "v" + this.nbformat + " or earlier.";
1869 "v" + this.nbformat + " or earlier.";
1866 }
1870 }
1867 IPython.dialog.modal({
1871 IPython.dialog.modal({
1868 title: "Error loading notebook",
1872 title: "Error loading notebook",
1869 body : msg,
1873 body : msg,
1870 buttons : {
1874 buttons : {
1871 "OK": {}
1875 "OK": {}
1872 }
1876 }
1873 });
1877 });
1874 }
1878 }
1875
1879
1876 /********************* checkpoint-related *********************/
1880 /********************* checkpoint-related *********************/
1877
1881
1878 /**
1882 /**
1879 * Save the notebook then immediately create a checkpoint.
1883 * Save the notebook then immediately create a checkpoint.
1880 *
1884 *
1881 * @method save_checkpoint
1885 * @method save_checkpoint
1882 */
1886 */
1883 Notebook.prototype.save_checkpoint = function () {
1887 Notebook.prototype.save_checkpoint = function () {
1884 this._checkpoint_after_save = true;
1888 this._checkpoint_after_save = true;
1885 this.save_notebook();
1889 this.save_notebook();
1886 };
1890 };
1887
1891
1888 /**
1892 /**
1889 * Add a checkpoint for this notebook.
1893 * Add a checkpoint for this notebook.
1890 * for use as a callback from checkpoint creation.
1894 * for use as a callback from checkpoint creation.
1891 *
1895 *
1892 * @method add_checkpoint
1896 * @method add_checkpoint
1893 */
1897 */
1894 Notebook.prototype.add_checkpoint = function (checkpoint) {
1898 Notebook.prototype.add_checkpoint = function (checkpoint) {
1895 var found = false;
1899 var found = false;
1896 for (var i = 0; i < this.checkpoints.length; i++) {
1900 for (var i = 0; i < this.checkpoints.length; i++) {
1897 var existing = this.checkpoints[i];
1901 var existing = this.checkpoints[i];
1898 if (existing.id == checkpoint.id) {
1902 if (existing.id == checkpoint.id) {
1899 found = true;
1903 found = true;
1900 this.checkpoints[i] = checkpoint;
1904 this.checkpoints[i] = checkpoint;
1901 break;
1905 break;
1902 }
1906 }
1903 }
1907 }
1904 if (!found) {
1908 if (!found) {
1905 this.checkpoints.push(checkpoint);
1909 this.checkpoints.push(checkpoint);
1906 }
1910 }
1907 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1911 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1908 };
1912 };
1909
1913
1910 /**
1914 /**
1911 * List checkpoints for this notebook.
1915 * List checkpoints for this notebook.
1912 *
1916 *
1913 * @method list_checkpoints
1917 * @method list_checkpoints
1914 */
1918 */
1915 Notebook.prototype.list_checkpoints = function () {
1919 Notebook.prototype.list_checkpoints = function () {
1916 var url = utils.url_join_encode(
1920 var url = utils.url_join_encode(
1917 this._baseProjectUrl,
1921 this._baseProjectUrl,
1918 'api/notebooks',
1922 'api/notebooks',
1919 this.notebook_path,
1923 this.notebook_path,
1920 this.notebook_name,
1924 this.notebook_name,
1921 'checkpoints'
1925 'checkpoints'
1922 );
1926 );
1923 $.get(url).done(
1927 $.get(url).done(
1924 $.proxy(this.list_checkpoints_success, this)
1928 $.proxy(this.list_checkpoints_success, this)
1925 ).fail(
1929 ).fail(
1926 $.proxy(this.list_checkpoints_error, this)
1930 $.proxy(this.list_checkpoints_error, this)
1927 );
1931 );
1928 };
1932 };
1929
1933
1930 /**
1934 /**
1931 * Success callback for listing checkpoints.
1935 * Success callback for listing checkpoints.
1932 *
1936 *
1933 * @method list_checkpoint_success
1937 * @method list_checkpoint_success
1934 * @param {Object} data JSON representation of a checkpoint
1938 * @param {Object} data JSON representation of a checkpoint
1935 * @param {String} status Description of response status
1939 * @param {String} status Description of response status
1936 * @param {jqXHR} xhr jQuery Ajax object
1940 * @param {jqXHR} xhr jQuery Ajax object
1937 */
1941 */
1938 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1942 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1939 var data = $.parseJSON(data);
1943 var data = $.parseJSON(data);
1940 this.checkpoints = data;
1944 this.checkpoints = data;
1941 if (data.length) {
1945 if (data.length) {
1942 this.last_checkpoint = data[data.length - 1];
1946 this.last_checkpoint = data[data.length - 1];
1943 } else {
1947 } else {
1944 this.last_checkpoint = null;
1948 this.last_checkpoint = null;
1945 }
1949 }
1946 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1950 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1947 };
1951 };
1948
1952
1949 /**
1953 /**
1950 * Failure callback for listing a checkpoint.
1954 * Failure callback for listing a checkpoint.
1951 *
1955 *
1952 * @method list_checkpoint_error
1956 * @method list_checkpoint_error
1953 * @param {jqXHR} xhr jQuery Ajax object
1957 * @param {jqXHR} xhr jQuery Ajax object
1954 * @param {String} status Description of response status
1958 * @param {String} status Description of response status
1955 * @param {String} error_msg HTTP error message
1959 * @param {String} error_msg HTTP error message
1956 */
1960 */
1957 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1961 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1958 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1962 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1959 };
1963 };
1960
1964
1961 /**
1965 /**
1962 * Create a checkpoint of this notebook on the server from the most recent save.
1966 * Create a checkpoint of this notebook on the server from the most recent save.
1963 *
1967 *
1964 * @method create_checkpoint
1968 * @method create_checkpoint
1965 */
1969 */
1966 Notebook.prototype.create_checkpoint = function () {
1970 Notebook.prototype.create_checkpoint = function () {
1967 var url = utils.url_join_encode(
1971 var url = utils.url_join_encode(
1968 this._baseProjectUrl,
1972 this._baseProjectUrl,
1969 'api/notebooks',
1973 'api/notebooks',
1970 this.notebookPath(),
1974 this.notebookPath(),
1971 this.notebook_name,
1975 this.notebook_name,
1972 'checkpoints'
1976 'checkpoints'
1973 );
1977 );
1974 $.post(url).done(
1978 $.post(url).done(
1975 $.proxy(this.create_checkpoint_success, this)
1979 $.proxy(this.create_checkpoint_success, this)
1976 ).fail(
1980 ).fail(
1977 $.proxy(this.create_checkpoint_error, this)
1981 $.proxy(this.create_checkpoint_error, this)
1978 );
1982 );
1979 };
1983 };
1980
1984
1981 /**
1985 /**
1982 * Success callback for creating a checkpoint.
1986 * Success callback for creating a checkpoint.
1983 *
1987 *
1984 * @method create_checkpoint_success
1988 * @method create_checkpoint_success
1985 * @param {Object} data JSON representation of a checkpoint
1989 * @param {Object} data JSON representation of a checkpoint
1986 * @param {String} status Description of response status
1990 * @param {String} status Description of response status
1987 * @param {jqXHR} xhr jQuery Ajax object
1991 * @param {jqXHR} xhr jQuery Ajax object
1988 */
1992 */
1989 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1993 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1990 var data = $.parseJSON(data);
1994 var data = $.parseJSON(data);
1991 this.add_checkpoint(data);
1995 this.add_checkpoint(data);
1992 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1996 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1993 };
1997 };
1994
1998
1995 /**
1999 /**
1996 * Failure callback for creating a checkpoint.
2000 * Failure callback for creating a checkpoint.
1997 *
2001 *
1998 * @method create_checkpoint_error
2002 * @method create_checkpoint_error
1999 * @param {jqXHR} xhr jQuery Ajax object
2003 * @param {jqXHR} xhr jQuery Ajax object
2000 * @param {String} status Description of response status
2004 * @param {String} status Description of response status
2001 * @param {String} error_msg HTTP error message
2005 * @param {String} error_msg HTTP error message
2002 */
2006 */
2003 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2007 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2004 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2008 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2005 };
2009 };
2006
2010
2007 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2011 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2008 var that = this;
2012 var that = this;
2009 var checkpoint = checkpoint || this.last_checkpoint;
2013 var checkpoint = checkpoint || this.last_checkpoint;
2010 if ( ! checkpoint ) {
2014 if ( ! checkpoint ) {
2011 console.log("restore dialog, but no checkpoint to restore to!");
2015 console.log("restore dialog, but no checkpoint to restore to!");
2012 return;
2016 return;
2013 }
2017 }
2014 var body = $('<div/>').append(
2018 var body = $('<div/>').append(
2015 $('<p/>').addClass("p-space").text(
2019 $('<p/>').addClass("p-space").text(
2016 "Are you sure you want to revert the notebook to " +
2020 "Are you sure you want to revert the notebook to " +
2017 "the latest checkpoint?"
2021 "the latest checkpoint?"
2018 ).append(
2022 ).append(
2019 $("<strong/>").text(
2023 $("<strong/>").text(
2020 " This cannot be undone."
2024 " This cannot be undone."
2021 )
2025 )
2022 )
2026 )
2023 ).append(
2027 ).append(
2024 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2028 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2025 ).append(
2029 ).append(
2026 $('<p/>').addClass("p-space").text(
2030 $('<p/>').addClass("p-space").text(
2027 Date(checkpoint.last_modified)
2031 Date(checkpoint.last_modified)
2028 ).css("text-align", "center")
2032 ).css("text-align", "center")
2029 );
2033 );
2030
2034
2031 IPython.dialog.modal({
2035 IPython.dialog.modal({
2032 title : "Revert notebook to checkpoint",
2036 title : "Revert notebook to checkpoint",
2033 body : body,
2037 body : body,
2034 buttons : {
2038 buttons : {
2035 Revert : {
2039 Revert : {
2036 class : "btn-danger",
2040 class : "btn-danger",
2037 click : function () {
2041 click : function () {
2038 that.restore_checkpoint(checkpoint.id);
2042 that.restore_checkpoint(checkpoint.id);
2039 }
2043 }
2040 },
2044 },
2041 Cancel : {}
2045 Cancel : {}
2042 }
2046 }
2043 });
2047 });
2044 }
2048 }
2045
2049
2046 /**
2050 /**
2047 * Restore the notebook to a checkpoint state.
2051 * Restore the notebook to a checkpoint state.
2048 *
2052 *
2049 * @method restore_checkpoint
2053 * @method restore_checkpoint
2050 * @param {String} checkpoint ID
2054 * @param {String} checkpoint ID
2051 */
2055 */
2052 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2056 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2053 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2057 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2054 var url = utils.url_join_encode(
2058 var url = utils.url_join_encode(
2055 this._baseProjectUrl,
2059 this._baseProjectUrl,
2056 'api/notebooks',
2060 'api/notebooks',
2057 this.notebookPath(),
2061 this.notebookPath(),
2058 this.notebook_name,
2062 this.notebook_name,
2059 'checkpoints',
2063 'checkpoints',
2060 checkpoint
2064 checkpoint
2061 );
2065 );
2062 $.post(url).done(
2066 $.post(url).done(
2063 $.proxy(this.restore_checkpoint_success, this)
2067 $.proxy(this.restore_checkpoint_success, this)
2064 ).fail(
2068 ).fail(
2065 $.proxy(this.restore_checkpoint_error, this)
2069 $.proxy(this.restore_checkpoint_error, this)
2066 );
2070 );
2067 };
2071 };
2068
2072
2069 /**
2073 /**
2070 * Success callback for restoring a notebook to a checkpoint.
2074 * Success callback for restoring a notebook to a checkpoint.
2071 *
2075 *
2072 * @method restore_checkpoint_success
2076 * @method restore_checkpoint_success
2073 * @param {Object} data (ignored, should be empty)
2077 * @param {Object} data (ignored, should be empty)
2074 * @param {String} status Description of response status
2078 * @param {String} status Description of response status
2075 * @param {jqXHR} xhr jQuery Ajax object
2079 * @param {jqXHR} xhr jQuery Ajax object
2076 */
2080 */
2077 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2081 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2078 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2082 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2079 this.load_notebook(this.notebook_name, this.notebook_path);
2083 this.load_notebook(this.notebook_name, this.notebook_path);
2080 };
2084 };
2081
2085
2082 /**
2086 /**
2083 * Failure callback for restoring a notebook to a checkpoint.
2087 * Failure callback for restoring a notebook to a checkpoint.
2084 *
2088 *
2085 * @method restore_checkpoint_error
2089 * @method restore_checkpoint_error
2086 * @param {jqXHR} xhr jQuery Ajax object
2090 * @param {jqXHR} xhr jQuery Ajax object
2087 * @param {String} status Description of response status
2091 * @param {String} status Description of response status
2088 * @param {String} error_msg HTTP error message
2092 * @param {String} error_msg HTTP error message
2089 */
2093 */
2090 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2094 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2091 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2095 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2092 };
2096 };
2093
2097
2094 /**
2098 /**
2095 * Delete a notebook checkpoint.
2099 * Delete a notebook checkpoint.
2096 *
2100 *
2097 * @method delete_checkpoint
2101 * @method delete_checkpoint
2098 * @param {String} checkpoint ID
2102 * @param {String} checkpoint ID
2099 */
2103 */
2100 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2104 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2101 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2105 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2102 var url = utils.url_join_encode(
2106 var url = utils.url_join_encode(
2103 this._baseProjectUrl,
2107 this._baseProjectUrl,
2104 'api/notebooks',
2108 'api/notebooks',
2105 this.notebookPath(),
2109 this.notebookPath(),
2106 this.notebook_name,
2110 this.notebook_name,
2107 'checkpoints',
2111 'checkpoints',
2108 checkpoint
2112 checkpoint
2109 );
2113 );
2110 $.ajax(url, {
2114 $.ajax(url, {
2111 type: 'DELETE',
2115 type: 'DELETE',
2112 success: $.proxy(this.delete_checkpoint_success, this),
2116 success: $.proxy(this.delete_checkpoint_success, this),
2113 error: $.proxy(this.delete_notebook_error,this)
2117 error: $.proxy(this.delete_notebook_error,this)
2114 });
2118 });
2115 };
2119 };
2116
2120
2117 /**
2121 /**
2118 * Success callback for deleting a notebook checkpoint
2122 * Success callback for deleting a notebook checkpoint
2119 *
2123 *
2120 * @method delete_checkpoint_success
2124 * @method delete_checkpoint_success
2121 * @param {Object} data (ignored, should be empty)
2125 * @param {Object} data (ignored, should be empty)
2122 * @param {String} status Description of response status
2126 * @param {String} status Description of response status
2123 * @param {jqXHR} xhr jQuery Ajax object
2127 * @param {jqXHR} xhr jQuery Ajax object
2124 */
2128 */
2125 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2129 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2126 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2130 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2127 this.load_notebook(this.notebook_name, this.notebook_path);
2131 this.load_notebook(this.notebook_name, this.notebook_path);
2128 };
2132 };
2129
2133
2130 /**
2134 /**
2131 * Failure callback for deleting a notebook checkpoint.
2135 * Failure callback for deleting a notebook checkpoint.
2132 *
2136 *
2133 * @method delete_checkpoint_error
2137 * @method delete_checkpoint_error
2134 * @param {jqXHR} xhr jQuery Ajax object
2138 * @param {jqXHR} xhr jQuery Ajax object
2135 * @param {String} status Description of response status
2139 * @param {String} status Description of response status
2136 * @param {String} error_msg HTTP error message
2140 * @param {String} error_msg HTTP error message
2137 */
2141 */
2138 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2142 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2139 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2143 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2140 };
2144 };
2141
2145
2142
2146
2143 IPython.Notebook = Notebook;
2147 IPython.Notebook = Notebook;
2144
2148
2145
2149
2146 return IPython;
2150 return IPython;
2147
2151
2148 }(IPython));
2152 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now