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