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