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