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