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