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