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