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