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