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