##// END OF EJS Templates
Fix asyncy nbconvert to download
Thomas Kluyver -
Show More
@@ -1,379 +1,383
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 // Constructor
16 // Constructor
17 //
17 //
18 // A MenuBar Class to generate the menubar of IPython notebook
18 // A MenuBar Class to generate the menubar of IPython notebook
19 //
19 //
20 // Parameters:
20 // Parameters:
21 // selector: string
21 // selector: string
22 // options: dictionary
22 // options: dictionary
23 // Dictionary of keyword arguments.
23 // Dictionary of keyword arguments.
24 // notebook: Notebook instance
24 // notebook: Notebook instance
25 // contents: ContentManager instance
25 // contents: ContentManager instance
26 // layout_manager: LayoutManager instance
26 // layout_manager: LayoutManager instance
27 // events: $(Events) instance
27 // events: $(Events) instance
28 // save_widget: SaveWidget instance
28 // save_widget: SaveWidget instance
29 // quick_help: QuickHelp instance
29 // quick_help: QuickHelp instance
30 // base_url : string
30 // base_url : string
31 // notebook_path : string
31 // notebook_path : string
32 // notebook_name : string
32 // notebook_name : string
33 options = options || {};
33 options = options || {};
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.selector = selector;
35 this.selector = selector;
36 this.notebook = options.notebook;
36 this.notebook = options.notebook;
37 this.contents = options.contents;
37 this.contents = options.contents;
38 this.layout_manager = options.layout_manager;
38 this.layout_manager = options.layout_manager;
39 this.events = options.events;
39 this.events = options.events;
40 this.save_widget = options.save_widget;
40 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
41 this.quick_help = options.quick_help;
42
42
43 try {
43 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
44 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
45 } catch (e) {
46 this.tour = undefined;
46 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
47 console.log("Failed to instantiate Notebook Tour", e);
48 }
48 }
49
49
50 if (this.selector !== undefined) {
50 if (this.selector !== undefined) {
51 this.element = $(selector);
51 this.element = $(selector);
52 this.style();
52 this.style();
53 this.bind_events();
53 this.bind_events();
54 }
54 }
55 };
55 };
56
56
57 // TODO: This has definitively nothing to do with style ...
57 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
58 MenuBar.prototype.style = function () {
59 var that = this;
59 var that = this;
60 this.element.find("li").click(function (event, ui) {
60 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
61 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
62 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
63 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
64 that.notebook.select(i);
65 }
65 }
66 );
66 );
67 };
67 };
68
68
69 MenuBar.prototype._nbconvert = function (format, download) {
69 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
70 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
71 var notebook_path = this.notebook.notebook_path;
72 if (this.notebook.dirty) {
73 this.notebook.save_notebook({async : false});
74 }
75 var url = utils.url_join_encode(
72 var url = utils.url_join_encode(
76 this.base_url,
73 this.base_url,
77 'nbconvert',
74 'nbconvert',
78 format,
75 format,
79 notebook_path
76 notebook_path
80 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
81
78
82 window.open(url);
79 var w = window.open()
80 if (this.notebook.dirty) {
81 this.notebook.save_notebook().then(function() {
82 w.location = url;
83 });
84 } else {
85 w.location = url;
86 }
83 };
87 };
84
88
85 MenuBar.prototype.bind_events = function () {
89 MenuBar.prototype.bind_events = function () {
86 // File
90 // File
87 var that = this;
91 var that = this;
88 this.element.find('#new_notebook').click(function () {
92 this.element.find('#new_notebook').click(function () {
89 var w = window.open();
93 var w = window.open();
90 // Create a new notebook in the same path as the current
94 // Create a new notebook in the same path as the current
91 // notebook's path.
95 // notebook's path.
92 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
96 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
93 that.contents.new_untitled(parent, {type: "notebook"}).then(
97 that.contents.new_untitled(parent, {type: "notebook"}).then(
94 function (data) {
98 function (data) {
95 w.location = utils.url_join_encode(
99 w.location = utils.url_join_encode(
96 that.base_url, 'notebooks', data.path
100 that.base_url, 'notebooks', data.path
97 );
101 );
98 },
102 },
99 function(error) {
103 function(error) {
100 w.close();
104 w.close();
101 dialog.modal({
105 dialog.modal({
102 title : 'Creating Notebook Failed',
106 title : 'Creating Notebook Failed',
103 body : "The error was: " + error.message,
107 body : "The error was: " + error.message,
104 buttons : {'OK' : {'class' : 'btn-primary'}}
108 buttons : {'OK' : {'class' : 'btn-primary'}}
105 });
109 });
106 }
110 }
107 );
111 );
108 });
112 });
109 this.element.find('#open_notebook').click(function () {
113 this.element.find('#open_notebook').click(function () {
110 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
114 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
111 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
115 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
112 });
116 });
113 this.element.find('#copy_notebook').click(function () {
117 this.element.find('#copy_notebook').click(function () {
114 that.notebook.copy_notebook();
118 that.notebook.copy_notebook();
115 return false;
119 return false;
116 });
120 });
117 this.element.find('#download_ipynb').click(function () {
121 this.element.find('#download_ipynb').click(function () {
118 var base_url = that.notebook.base_url;
122 var base_url = that.notebook.base_url;
119 var notebook_path = that.notebook.notebook_path;
123 var notebook_path = that.notebook.notebook_path;
120 if (that.notebook.dirty) {
124 if (that.notebook.dirty) {
121 that.notebook.save_notebook({async : false});
125 that.notebook.save_notebook({async : false});
122 }
126 }
123
127
124 var url = utils.url_join_encode(base_url, 'files', notebook_path);
128 var url = utils.url_join_encode(base_url, 'files', notebook_path);
125 window.open(url + '?download=1');
129 window.open(url + '?download=1');
126 });
130 });
127
131
128 this.element.find('#print_preview').click(function () {
132 this.element.find('#print_preview').click(function () {
129 that._nbconvert('html', false);
133 that._nbconvert('html', false);
130 });
134 });
131
135
132 this.element.find('#download_html').click(function () {
136 this.element.find('#download_html').click(function () {
133 that._nbconvert('html', true);
137 that._nbconvert('html', true);
134 });
138 });
135
139
136 this.element.find('#download_rst').click(function () {
140 this.element.find('#download_rst').click(function () {
137 that._nbconvert('rst', true);
141 that._nbconvert('rst', true);
138 });
142 });
139
143
140 this.element.find('#download_pdf').click(function () {
144 this.element.find('#download_pdf').click(function () {
141 that._nbconvert('pdf', true);
145 that._nbconvert('pdf', true);
142 });
146 });
143
147
144 this.element.find('#rename_notebook').click(function () {
148 this.element.find('#rename_notebook').click(function () {
145 that.save_widget.rename_notebook({notebook: that.notebook});
149 that.save_widget.rename_notebook({notebook: that.notebook});
146 });
150 });
147 this.element.find('#save_checkpoint').click(function () {
151 this.element.find('#save_checkpoint').click(function () {
148 that.notebook.save_checkpoint();
152 that.notebook.save_checkpoint();
149 });
153 });
150 this.element.find('#restore_checkpoint').click(function () {
154 this.element.find('#restore_checkpoint').click(function () {
151 });
155 });
152 this.element.find('#trust_notebook').click(function () {
156 this.element.find('#trust_notebook').click(function () {
153 that.notebook.trust_notebook();
157 that.notebook.trust_notebook();
154 });
158 });
155 this.events.on('trust_changed.Notebook', function (event, trusted) {
159 this.events.on('trust_changed.Notebook', function (event, trusted) {
156 if (trusted) {
160 if (trusted) {
157 that.element.find('#trust_notebook')
161 that.element.find('#trust_notebook')
158 .addClass("disabled")
162 .addClass("disabled")
159 .find("a").text("Trusted Notebook");
163 .find("a").text("Trusted Notebook");
160 } else {
164 } else {
161 that.element.find('#trust_notebook')
165 that.element.find('#trust_notebook')
162 .removeClass("disabled")
166 .removeClass("disabled")
163 .find("a").text("Trust Notebook");
167 .find("a").text("Trust Notebook");
164 }
168 }
165 });
169 });
166 this.element.find('#kill_and_exit').click(function () {
170 this.element.find('#kill_and_exit').click(function () {
167 var close_window = function () {
171 var close_window = function () {
168 // allow closing of new tabs in Chromium, impossible in FF
172 // allow closing of new tabs in Chromium, impossible in FF
169 window.open('', '_self', '');
173 window.open('', '_self', '');
170 window.close();
174 window.close();
171 };
175 };
172 // finish with close on success or failure
176 // finish with close on success or failure
173 that.notebook.session.delete(close_window, close_window);
177 that.notebook.session.delete(close_window, close_window);
174 });
178 });
175 // Edit
179 // Edit
176 this.element.find('#cut_cell').click(function () {
180 this.element.find('#cut_cell').click(function () {
177 that.notebook.cut_cell();
181 that.notebook.cut_cell();
178 });
182 });
179 this.element.find('#copy_cell').click(function () {
183 this.element.find('#copy_cell').click(function () {
180 that.notebook.copy_cell();
184 that.notebook.copy_cell();
181 });
185 });
182 this.element.find('#delete_cell').click(function () {
186 this.element.find('#delete_cell').click(function () {
183 that.notebook.delete_cell();
187 that.notebook.delete_cell();
184 });
188 });
185 this.element.find('#undelete_cell').click(function () {
189 this.element.find('#undelete_cell').click(function () {
186 that.notebook.undelete_cell();
190 that.notebook.undelete_cell();
187 });
191 });
188 this.element.find('#split_cell').click(function () {
192 this.element.find('#split_cell').click(function () {
189 that.notebook.split_cell();
193 that.notebook.split_cell();
190 });
194 });
191 this.element.find('#merge_cell_above').click(function () {
195 this.element.find('#merge_cell_above').click(function () {
192 that.notebook.merge_cell_above();
196 that.notebook.merge_cell_above();
193 });
197 });
194 this.element.find('#merge_cell_below').click(function () {
198 this.element.find('#merge_cell_below').click(function () {
195 that.notebook.merge_cell_below();
199 that.notebook.merge_cell_below();
196 });
200 });
197 this.element.find('#move_cell_up').click(function () {
201 this.element.find('#move_cell_up').click(function () {
198 that.notebook.move_cell_up();
202 that.notebook.move_cell_up();
199 });
203 });
200 this.element.find('#move_cell_down').click(function () {
204 this.element.find('#move_cell_down').click(function () {
201 that.notebook.move_cell_down();
205 that.notebook.move_cell_down();
202 });
206 });
203 this.element.find('#edit_nb_metadata').click(function () {
207 this.element.find('#edit_nb_metadata').click(function () {
204 that.notebook.edit_metadata({
208 that.notebook.edit_metadata({
205 notebook: that.notebook,
209 notebook: that.notebook,
206 keyboard_manager: that.notebook.keyboard_manager});
210 keyboard_manager: that.notebook.keyboard_manager});
207 });
211 });
208
212
209 // View
213 // View
210 this.element.find('#toggle_header').click(function () {
214 this.element.find('#toggle_header').click(function () {
211 $('div#header').toggle();
215 $('div#header').toggle();
212 that.layout_manager.do_resize();
216 that.layout_manager.do_resize();
213 });
217 });
214 this.element.find('#toggle_toolbar').click(function () {
218 this.element.find('#toggle_toolbar').click(function () {
215 $('div#maintoolbar').toggle();
219 $('div#maintoolbar').toggle();
216 that.layout_manager.do_resize();
220 that.layout_manager.do_resize();
217 });
221 });
218 // Insert
222 // Insert
219 this.element.find('#insert_cell_above').click(function () {
223 this.element.find('#insert_cell_above').click(function () {
220 that.notebook.insert_cell_above('code');
224 that.notebook.insert_cell_above('code');
221 that.notebook.select_prev();
225 that.notebook.select_prev();
222 });
226 });
223 this.element.find('#insert_cell_below').click(function () {
227 this.element.find('#insert_cell_below').click(function () {
224 that.notebook.insert_cell_below('code');
228 that.notebook.insert_cell_below('code');
225 that.notebook.select_next();
229 that.notebook.select_next();
226 });
230 });
227 // Cell
231 // Cell
228 this.element.find('#run_cell').click(function () {
232 this.element.find('#run_cell').click(function () {
229 that.notebook.execute_cell();
233 that.notebook.execute_cell();
230 });
234 });
231 this.element.find('#run_cell_select_below').click(function () {
235 this.element.find('#run_cell_select_below').click(function () {
232 that.notebook.execute_cell_and_select_below();
236 that.notebook.execute_cell_and_select_below();
233 });
237 });
234 this.element.find('#run_cell_insert_below').click(function () {
238 this.element.find('#run_cell_insert_below').click(function () {
235 that.notebook.execute_cell_and_insert_below();
239 that.notebook.execute_cell_and_insert_below();
236 });
240 });
237 this.element.find('#run_all_cells').click(function () {
241 this.element.find('#run_all_cells').click(function () {
238 that.notebook.execute_all_cells();
242 that.notebook.execute_all_cells();
239 });
243 });
240 this.element.find('#run_all_cells_above').click(function () {
244 this.element.find('#run_all_cells_above').click(function () {
241 that.notebook.execute_cells_above();
245 that.notebook.execute_cells_above();
242 });
246 });
243 this.element.find('#run_all_cells_below').click(function () {
247 this.element.find('#run_all_cells_below').click(function () {
244 that.notebook.execute_cells_below();
248 that.notebook.execute_cells_below();
245 });
249 });
246 this.element.find('#to_code').click(function () {
250 this.element.find('#to_code').click(function () {
247 that.notebook.to_code();
251 that.notebook.to_code();
248 });
252 });
249 this.element.find('#to_markdown').click(function () {
253 this.element.find('#to_markdown').click(function () {
250 that.notebook.to_markdown();
254 that.notebook.to_markdown();
251 });
255 });
252 this.element.find('#to_raw').click(function () {
256 this.element.find('#to_raw').click(function () {
253 that.notebook.to_raw();
257 that.notebook.to_raw();
254 });
258 });
255
259
256 this.element.find('#toggle_current_output').click(function () {
260 this.element.find('#toggle_current_output').click(function () {
257 that.notebook.toggle_output();
261 that.notebook.toggle_output();
258 });
262 });
259 this.element.find('#toggle_current_output_scroll').click(function () {
263 this.element.find('#toggle_current_output_scroll').click(function () {
260 that.notebook.toggle_output_scroll();
264 that.notebook.toggle_output_scroll();
261 });
265 });
262 this.element.find('#clear_current_output').click(function () {
266 this.element.find('#clear_current_output').click(function () {
263 that.notebook.clear_output();
267 that.notebook.clear_output();
264 });
268 });
265
269
266 this.element.find('#toggle_all_output').click(function () {
270 this.element.find('#toggle_all_output').click(function () {
267 that.notebook.toggle_all_output();
271 that.notebook.toggle_all_output();
268 });
272 });
269 this.element.find('#toggle_all_output_scroll').click(function () {
273 this.element.find('#toggle_all_output_scroll').click(function () {
270 that.notebook.toggle_all_output_scroll();
274 that.notebook.toggle_all_output_scroll();
271 });
275 });
272 this.element.find('#clear_all_output').click(function () {
276 this.element.find('#clear_all_output').click(function () {
273 that.notebook.clear_all_output();
277 that.notebook.clear_all_output();
274 });
278 });
275
279
276 // Kernel
280 // Kernel
277 this.element.find('#int_kernel').click(function () {
281 this.element.find('#int_kernel').click(function () {
278 that.notebook.kernel.interrupt();
282 that.notebook.kernel.interrupt();
279 });
283 });
280 this.element.find('#restart_kernel').click(function () {
284 this.element.find('#restart_kernel').click(function () {
281 that.notebook.restart_kernel();
285 that.notebook.restart_kernel();
282 });
286 });
283 this.element.find('#reconnect_kernel').click(function () {
287 this.element.find('#reconnect_kernel').click(function () {
284 that.notebook.kernel.reconnect();
288 that.notebook.kernel.reconnect();
285 });
289 });
286 // Help
290 // Help
287 if (this.tour) {
291 if (this.tour) {
288 this.element.find('#notebook_tour').click(function () {
292 this.element.find('#notebook_tour').click(function () {
289 that.tour.start();
293 that.tour.start();
290 });
294 });
291 } else {
295 } else {
292 this.element.find('#notebook_tour').addClass("disabled");
296 this.element.find('#notebook_tour').addClass("disabled");
293 }
297 }
294 this.element.find('#keyboard_shortcuts').click(function () {
298 this.element.find('#keyboard_shortcuts').click(function () {
295 that.quick_help.show_keyboard_shortcuts();
299 that.quick_help.show_keyboard_shortcuts();
296 });
300 });
297
301
298 this.update_restore_checkpoint(null);
302 this.update_restore_checkpoint(null);
299
303
300 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
301 that.update_restore_checkpoint(that.notebook.checkpoints);
305 that.update_restore_checkpoint(that.notebook.checkpoints);
302 });
306 });
303
307
304 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
305 that.update_restore_checkpoint(that.notebook.checkpoints);
309 that.update_restore_checkpoint(that.notebook.checkpoints);
306 });
310 });
307
311
308 this.events.on('notebook_loaded.Notebook', function() {
312 this.events.on('notebook_loaded.Notebook', function() {
309 var langinfo = that.notebook.metadata.language_info || {};
313 var langinfo = that.notebook.metadata.language_info || {};
310 that.update_nbconvert_script(langinfo);
314 that.update_nbconvert_script(langinfo);
311 });
315 });
312
316
313 this.events.on('kernel_ready.Kernel', function(event, data) {
317 this.events.on('kernel_ready.Kernel', function(event, data) {
314 var langinfo = data.kernel.info_reply.language_info || {};
318 var langinfo = data.kernel.info_reply.language_info || {};
315 that.update_nbconvert_script(langinfo);
319 that.update_nbconvert_script(langinfo);
316 });
320 });
317 };
321 };
318
322
319 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
323 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
320 var ul = this.element.find("#restore_checkpoint").find("ul");
324 var ul = this.element.find("#restore_checkpoint").find("ul");
321 ul.empty();
325 ul.empty();
322 if (!checkpoints || checkpoints.length === 0) {
326 if (!checkpoints || checkpoints.length === 0) {
323 ul.append(
327 ul.append(
324 $("<li/>")
328 $("<li/>")
325 .addClass("disabled")
329 .addClass("disabled")
326 .append(
330 .append(
327 $("<a/>")
331 $("<a/>")
328 .text("No checkpoints")
332 .text("No checkpoints")
329 )
333 )
330 );
334 );
331 return;
335 return;
332 }
336 }
333
337
334 var that = this;
338 var that = this;
335 checkpoints.map(function (checkpoint) {
339 checkpoints.map(function (checkpoint) {
336 var d = new Date(checkpoint.last_modified);
340 var d = new Date(checkpoint.last_modified);
337 ul.append(
341 ul.append(
338 $("<li/>").append(
342 $("<li/>").append(
339 $("<a/>")
343 $("<a/>")
340 .attr("href", "#")
344 .attr("href", "#")
341 .text(moment(d).format("LLLL"))
345 .text(moment(d).format("LLLL"))
342 .click(function () {
346 .click(function () {
343 that.notebook.restore_checkpoint_dialog(checkpoint);
347 that.notebook.restore_checkpoint_dialog(checkpoint);
344 })
348 })
345 )
349 )
346 );
350 );
347 });
351 });
348 };
352 };
349
353
350 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
354 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
351 // Set the 'Download as foo' menu option for the relevant language.
355 // Set the 'Download as foo' menu option for the relevant language.
352 var el = this.element.find('#download_script');
356 var el = this.element.find('#download_script');
353 var that = this;
357 var that = this;
354
358
355 // Set menu entry text to e.g. "Python (.py)"
359 // Set menu entry text to e.g. "Python (.py)"
356 var langname = (langinfo.name || 'Script')
360 var langname = (langinfo.name || 'Script')
357 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
361 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
358 el.find('a').text(langname + ' (.'+(langinfo.file_extension || 'txt')+')');
362 el.find('a').text(langname + ' (.'+(langinfo.file_extension || 'txt')+')');
359
363
360 // Unregister any previously registered handlers
364 // Unregister any previously registered handlers
361 el.off('click');
365 el.off('click');
362 if (langinfo.nbconvert_exporter) {
366 if (langinfo.nbconvert_exporter) {
363 // Metadata specifies a specific exporter, e.g. 'python'
367 // Metadata specifies a specific exporter, e.g. 'python'
364 el.click(function() {
368 el.click(function() {
365 that._nbconvert(langinfo.nbconvert_exporter, true);
369 that._nbconvert(langinfo.nbconvert_exporter, true);
366 });
370 });
367 } else {
371 } else {
368 // Use generic 'script' exporter
372 // Use generic 'script' exporter
369 el.click(function() {
373 el.click(function() {
370 that._nbconvert('script', true);
374 that._nbconvert('script', true);
371 });
375 });
372 }
376 }
373 };
377 };
374
378
375 // Backwards compatability.
379 // Backwards compatability.
376 IPython.MenuBar = MenuBar;
380 IPython.MenuBar = MenuBar;
377
381
378 return {'MenuBar': MenuBar};
382 return {'MenuBar': MenuBar};
379 });
383 });
@@ -1,2499 +1,2499
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'codemirror/lib/codemirror',
14 'codemirror/lib/codemirror',
15 'codemirror/addon/runmode/runmode',
15 'codemirror/addon/runmode/runmode',
16 'notebook/js/mathjaxutils',
16 'notebook/js/mathjaxutils',
17 'base/js/keyboard',
17 'base/js/keyboard',
18 'notebook/js/tooltip',
18 'notebook/js/tooltip',
19 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/default',
20 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/rawcell',
21 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/celltoolbarpresets/slideshow',
22 'notebook/js/scrollmanager'
22 'notebook/js/scrollmanager'
23 ], function (
23 ], function (
24 IPython,
24 IPython,
25 $,
25 $,
26 utils,
26 utils,
27 dialog,
27 dialog,
28 textcell,
28 textcell,
29 codecell,
29 codecell,
30 session,
30 session,
31 celltoolbar,
31 celltoolbar,
32 marked,
32 marked,
33 CodeMirror,
33 CodeMirror,
34 runMode,
34 runMode,
35 mathjaxutils,
35 mathjaxutils,
36 keyboard,
36 keyboard,
37 tooltip,
37 tooltip,
38 default_celltoolbar,
38 default_celltoolbar,
39 rawcell_celltoolbar,
39 rawcell_celltoolbar,
40 slideshow_celltoolbar,
40 slideshow_celltoolbar,
41 scrollmanager
41 scrollmanager
42 ) {
42 ) {
43
43
44 var Notebook = function (selector, options) {
44 var Notebook = function (selector, options) {
45 // Constructor
45 // Constructor
46 //
46 //
47 // A notebook contains and manages cells.
47 // A notebook contains and manages cells.
48 //
48 //
49 // Parameters:
49 // Parameters:
50 // selector: string
50 // selector: string
51 // options: dictionary
51 // options: dictionary
52 // Dictionary of keyword arguments.
52 // Dictionary of keyword arguments.
53 // events: $(Events) instance
53 // events: $(Events) instance
54 // keyboard_manager: KeyboardManager instance
54 // keyboard_manager: KeyboardManager instance
55 // contents: Contents instance
55 // contents: Contents instance
56 // save_widget: SaveWidget instance
56 // save_widget: SaveWidget instance
57 // config: dictionary
57 // config: dictionary
58 // base_url : string
58 // base_url : string
59 // notebook_path : string
59 // notebook_path : string
60 // notebook_name : string
60 // notebook_name : string
61 this.config = utils.mergeopt(Notebook, options.config);
61 this.config = utils.mergeopt(Notebook, options.config);
62 this.base_url = options.base_url;
62 this.base_url = options.base_url;
63 this.notebook_path = options.notebook_path;
63 this.notebook_path = options.notebook_path;
64 this.notebook_name = options.notebook_name;
64 this.notebook_name = options.notebook_name;
65 this.events = options.events;
65 this.events = options.events;
66 this.keyboard_manager = options.keyboard_manager;
66 this.keyboard_manager = options.keyboard_manager;
67 this.contents = options.contents;
67 this.contents = options.contents;
68 this.save_widget = options.save_widget;
68 this.save_widget = options.save_widget;
69 this.tooltip = new tooltip.Tooltip(this.events);
69 this.tooltip = new tooltip.Tooltip(this.events);
70 this.ws_url = options.ws_url;
70 this.ws_url = options.ws_url;
71 this._session_starting = false;
71 this._session_starting = false;
72 this.default_cell_type = this.config.default_cell_type || 'code';
72 this.default_cell_type = this.config.default_cell_type || 'code';
73
73
74 // Create default scroll manager.
74 // Create default scroll manager.
75 this.scroll_manager = new scrollmanager.ScrollManager(this);
75 this.scroll_manager = new scrollmanager.ScrollManager(this);
76
76
77 // TODO: This code smells (and the other `= this` line a couple lines down)
77 // TODO: This code smells (and the other `= this` line a couple lines down)
78 // We need a better way to deal with circular instance references.
78 // We need a better way to deal with circular instance references.
79 this.keyboard_manager.notebook = this;
79 this.keyboard_manager.notebook = this;
80 this.save_widget.notebook = this;
80 this.save_widget.notebook = this;
81
81
82 mathjaxutils.init();
82 mathjaxutils.init();
83
83
84 if (marked) {
84 if (marked) {
85 marked.setOptions({
85 marked.setOptions({
86 gfm : true,
86 gfm : true,
87 tables: true,
87 tables: true,
88 // FIXME: probably want central config for CodeMirror theme when we have js config
88 // FIXME: probably want central config for CodeMirror theme when we have js config
89 langPrefix: "cm-s-ipython language-",
89 langPrefix: "cm-s-ipython language-",
90 highlight: function(code, lang, callback) {
90 highlight: function(code, lang, callback) {
91 if (!lang) {
91 if (!lang) {
92 // no language, no highlight
92 // no language, no highlight
93 if (callback) {
93 if (callback) {
94 callback(null, code);
94 callback(null, code);
95 return;
95 return;
96 } else {
96 } else {
97 return code;
97 return code;
98 }
98 }
99 }
99 }
100 utils.requireCodeMirrorMode(lang, function () {
100 utils.requireCodeMirrorMode(lang, function () {
101 var el = document.createElement("div");
101 var el = document.createElement("div");
102 mode = CodeMirror.getMode({}, lang);
102 mode = CodeMirror.getMode({}, lang);
103 if (!mode) {
103 if (!mode) {
104 console.log("No CodeMirror mode: " + lang);
104 console.log("No CodeMirror mode: " + lang);
105 callback(null, code);
105 callback(null, code);
106 return;
106 return;
107 }
107 }
108 try {
108 try {
109 CodeMirror.runMode(code, mode, el);
109 CodeMirror.runMode(code, mode, el);
110 callback(null, el.innerHTML);
110 callback(null, el.innerHTML);
111 } catch (err) {
111 } catch (err) {
112 console.log("Failed to highlight " + lang + " code", error);
112 console.log("Failed to highlight " + lang + " code", error);
113 callback(err, code);
113 callback(err, code);
114 }
114 }
115 }, function (err) {
115 }, function (err) {
116 console.log("No CodeMirror mode: " + lang);
116 console.log("No CodeMirror mode: " + lang);
117 callback(err, code);
117 callback(err, code);
118 });
118 });
119 }
119 }
120 });
120 });
121 }
121 }
122
122
123 this.element = $(selector);
123 this.element = $(selector);
124 this.element.scroll();
124 this.element.scroll();
125 this.element.data("notebook", this);
125 this.element.data("notebook", this);
126 this.next_prompt_number = 1;
126 this.next_prompt_number = 1;
127 this.session = null;
127 this.session = null;
128 this.kernel = null;
128 this.kernel = null;
129 this.clipboard = null;
129 this.clipboard = null;
130 this.undelete_backup = null;
130 this.undelete_backup = null;
131 this.undelete_index = null;
131 this.undelete_index = null;
132 this.undelete_below = false;
132 this.undelete_below = false;
133 this.paste_enabled = false;
133 this.paste_enabled = false;
134 // It is important to start out in command mode to match the intial mode
134 // It is important to start out in command mode to match the intial mode
135 // of the KeyboardManager.
135 // of the KeyboardManager.
136 this.mode = 'command';
136 this.mode = 'command';
137 this.set_dirty(false);
137 this.set_dirty(false);
138 this.metadata = {};
138 this.metadata = {};
139 this._checkpoint_after_save = false;
139 this._checkpoint_after_save = false;
140 this.last_checkpoint = null;
140 this.last_checkpoint = null;
141 this.checkpoints = [];
141 this.checkpoints = [];
142 this.autosave_interval = 0;
142 this.autosave_interval = 0;
143 this.autosave_timer = null;
143 this.autosave_timer = null;
144 // autosave *at most* every two minutes
144 // autosave *at most* every two minutes
145 this.minimum_autosave_interval = 120000;
145 this.minimum_autosave_interval = 120000;
146 this.notebook_name_blacklist_re = /[\/\\:]/;
146 this.notebook_name_blacklist_re = /[\/\\:]/;
147 this.nbformat = 4; // Increment this when changing the nbformat
147 this.nbformat = 4; // Increment this when changing the nbformat
148 this.nbformat_minor = 0; // Increment this when changing the nbformat
148 this.nbformat_minor = 0; // Increment this when changing the nbformat
149 this.codemirror_mode = 'ipython';
149 this.codemirror_mode = 'ipython';
150 this.create_elements();
150 this.create_elements();
151 this.bind_events();
151 this.bind_events();
152 this.save_notebook = function() { // don't allow save until notebook_loaded
152 this.save_notebook = function() { // don't allow save until notebook_loaded
153 this.save_notebook_error(null, null, "Load failed, save is disabled");
153 this.save_notebook_error(null, null, "Load failed, save is disabled");
154 };
154 };
155
155
156 // Trigger cell toolbar registration.
156 // Trigger cell toolbar registration.
157 default_celltoolbar.register(this);
157 default_celltoolbar.register(this);
158 rawcell_celltoolbar.register(this);
158 rawcell_celltoolbar.register(this);
159 slideshow_celltoolbar.register(this);
159 slideshow_celltoolbar.register(this);
160 };
160 };
161
161
162 Notebook.options_default = {
162 Notebook.options_default = {
163 // can be any cell type, or the special values of
163 // can be any cell type, or the special values of
164 // 'above', 'below', or 'selected' to get the value from another cell.
164 // 'above', 'below', or 'selected' to get the value from another cell.
165 Notebook: {
165 Notebook: {
166 default_cell_type: 'code',
166 default_cell_type: 'code',
167 }
167 }
168 };
168 };
169
169
170
170
171 /**
171 /**
172 * Create an HTML and CSS representation of the notebook.
172 * Create an HTML and CSS representation of the notebook.
173 *
173 *
174 * @method create_elements
174 * @method create_elements
175 */
175 */
176 Notebook.prototype.create_elements = function () {
176 Notebook.prototype.create_elements = function () {
177 var that = this;
177 var that = this;
178 this.element.attr('tabindex','-1');
178 this.element.attr('tabindex','-1');
179 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
179 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
180 // We add this end_space div to the end of the notebook div to:
180 // We add this end_space div to the end of the notebook div to:
181 // i) provide a margin between the last cell and the end of the notebook
181 // i) provide a margin between the last cell and the end of the notebook
182 // ii) to prevent the div from scrolling up when the last cell is being
182 // ii) to prevent the div from scrolling up when the last cell is being
183 // edited, but is too low on the page, which browsers will do automatically.
183 // edited, but is too low on the page, which browsers will do automatically.
184 var end_space = $('<div/>').addClass('end_space');
184 var end_space = $('<div/>').addClass('end_space');
185 end_space.dblclick(function (e) {
185 end_space.dblclick(function (e) {
186 var ncells = that.ncells();
186 var ncells = that.ncells();
187 that.insert_cell_below('code',ncells-1);
187 that.insert_cell_below('code',ncells-1);
188 });
188 });
189 this.element.append(this.container);
189 this.element.append(this.container);
190 this.container.append(end_space);
190 this.container.append(end_space);
191 };
191 };
192
192
193 /**
193 /**
194 * Bind JavaScript events: key presses and custom IPython events.
194 * Bind JavaScript events: key presses and custom IPython events.
195 *
195 *
196 * @method bind_events
196 * @method bind_events
197 */
197 */
198 Notebook.prototype.bind_events = function () {
198 Notebook.prototype.bind_events = function () {
199 var that = this;
199 var that = this;
200
200
201 this.events.on('set_next_input.Notebook', function (event, data) {
201 this.events.on('set_next_input.Notebook', function (event, data) {
202 var index = that.find_cell_index(data.cell);
202 var index = that.find_cell_index(data.cell);
203 var new_cell = that.insert_cell_below('code',index);
203 var new_cell = that.insert_cell_below('code',index);
204 new_cell.set_text(data.text);
204 new_cell.set_text(data.text);
205 that.dirty = true;
205 that.dirty = true;
206 });
206 });
207
207
208 this.events.on('set_dirty.Notebook', function (event, data) {
208 this.events.on('set_dirty.Notebook', function (event, data) {
209 that.dirty = data.value;
209 that.dirty = data.value;
210 });
210 });
211
211
212 this.events.on('trust_changed.Notebook', function (event, trusted) {
212 this.events.on('trust_changed.Notebook', function (event, trusted) {
213 that.trusted = trusted;
213 that.trusted = trusted;
214 });
214 });
215
215
216 this.events.on('select.Cell', function (event, data) {
216 this.events.on('select.Cell', function (event, data) {
217 var index = that.find_cell_index(data.cell);
217 var index = that.find_cell_index(data.cell);
218 that.select(index);
218 that.select(index);
219 });
219 });
220
220
221 this.events.on('edit_mode.Cell', function (event, data) {
221 this.events.on('edit_mode.Cell', function (event, data) {
222 that.handle_edit_mode(data.cell);
222 that.handle_edit_mode(data.cell);
223 });
223 });
224
224
225 this.events.on('command_mode.Cell', function (event, data) {
225 this.events.on('command_mode.Cell', function (event, data) {
226 that.handle_command_mode(data.cell);
226 that.handle_command_mode(data.cell);
227 });
227 });
228
228
229 this.events.on('spec_changed.Kernel', function(event, data) {
229 this.events.on('spec_changed.Kernel', function(event, data) {
230 that.metadata.kernelspec =
230 that.metadata.kernelspec =
231 {name: data.name, display_name: data.display_name};
231 {name: data.name, display_name: data.display_name};
232 });
232 });
233
233
234 this.events.on('kernel_ready.Kernel', function(event, data) {
234 this.events.on('kernel_ready.Kernel', function(event, data) {
235 var kinfo = data.kernel.info_reply;
235 var kinfo = data.kernel.info_reply;
236 var langinfo = kinfo.language_info || {};
236 var langinfo = kinfo.language_info || {};
237 if (!langinfo.name) langinfo.name = kinfo.language;
237 if (!langinfo.name) langinfo.name = kinfo.language;
238
238
239 that.metadata.language_info = langinfo;
239 that.metadata.language_info = langinfo;
240 // Mode 'null' should be plain, unhighlighted text.
240 // Mode 'null' should be plain, unhighlighted text.
241 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
241 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
242 that.set_codemirror_mode(cm_mode);
242 that.set_codemirror_mode(cm_mode);
243 });
243 });
244
244
245 var collapse_time = function (time) {
245 var collapse_time = function (time) {
246 var app_height = $('#ipython-main-app').height(); // content height
246 var app_height = $('#ipython-main-app').height(); // content height
247 var splitter_height = $('div#pager_splitter').outerHeight(true);
247 var splitter_height = $('div#pager_splitter').outerHeight(true);
248 var new_height = app_height - splitter_height;
248 var new_height = app_height - splitter_height;
249 that.element.animate({height : new_height + 'px'}, time);
249 that.element.animate({height : new_height + 'px'}, time);
250 };
250 };
251
251
252 this.element.bind('collapse_pager', function (event, extrap) {
252 this.element.bind('collapse_pager', function (event, extrap) {
253 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
253 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
254 collapse_time(time);
254 collapse_time(time);
255 });
255 });
256
256
257 var expand_time = function (time) {
257 var expand_time = function (time) {
258 var app_height = $('#ipython-main-app').height(); // content height
258 var app_height = $('#ipython-main-app').height(); // content height
259 var splitter_height = $('div#pager_splitter').outerHeight(true);
259 var splitter_height = $('div#pager_splitter').outerHeight(true);
260 var pager_height = $('div#pager').outerHeight(true);
260 var pager_height = $('div#pager').outerHeight(true);
261 var new_height = app_height - pager_height - splitter_height;
261 var new_height = app_height - pager_height - splitter_height;
262 that.element.animate({height : new_height + 'px'}, time);
262 that.element.animate({height : new_height + 'px'}, time);
263 };
263 };
264
264
265 this.element.bind('expand_pager', function (event, extrap) {
265 this.element.bind('expand_pager', function (event, extrap) {
266 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
266 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
267 expand_time(time);
267 expand_time(time);
268 });
268 });
269
269
270 // Firefox 22 broke $(window).on("beforeunload")
270 // Firefox 22 broke $(window).on("beforeunload")
271 // I'm not sure why or how.
271 // I'm not sure why or how.
272 window.onbeforeunload = function (e) {
272 window.onbeforeunload = function (e) {
273 // TODO: Make killing the kernel configurable.
273 // TODO: Make killing the kernel configurable.
274 var kill_kernel = false;
274 var kill_kernel = false;
275 if (kill_kernel) {
275 if (kill_kernel) {
276 that.session.delete();
276 that.session.delete();
277 }
277 }
278 // if we are autosaving, trigger an autosave on nav-away.
278 // if we are autosaving, trigger an autosave on nav-away.
279 // still warn, because if we don't the autosave may fail.
279 // still warn, because if we don't the autosave may fail.
280 if (that.dirty) {
280 if (that.dirty) {
281 if ( that.autosave_interval ) {
281 if ( that.autosave_interval ) {
282 // schedule autosave in a timeout
282 // schedule autosave in a timeout
283 // this gives you a chance to forcefully discard changes
283 // this gives you a chance to forcefully discard changes
284 // by reloading the page if you *really* want to.
284 // by reloading the page if you *really* want to.
285 // the timer doesn't start until you *dismiss* the dialog.
285 // the timer doesn't start until you *dismiss* the dialog.
286 setTimeout(function () {
286 setTimeout(function () {
287 if (that.dirty) {
287 if (that.dirty) {
288 that.save_notebook();
288 that.save_notebook();
289 }
289 }
290 }, 1000);
290 }, 1000);
291 return "Autosave in progress, latest changes may be lost.";
291 return "Autosave in progress, latest changes may be lost.";
292 } else {
292 } else {
293 return "Unsaved changes will be lost.";
293 return "Unsaved changes will be lost.";
294 }
294 }
295 }
295 }
296 // Null is the *only* return value that will make the browser not
296 // Null is the *only* return value that will make the browser not
297 // pop up the "don't leave" dialog.
297 // pop up the "don't leave" dialog.
298 return null;
298 return null;
299 };
299 };
300 };
300 };
301
301
302 /**
302 /**
303 * Set the dirty flag, and trigger the set_dirty.Notebook event
303 * Set the dirty flag, and trigger the set_dirty.Notebook event
304 *
304 *
305 * @method set_dirty
305 * @method set_dirty
306 */
306 */
307 Notebook.prototype.set_dirty = function (value) {
307 Notebook.prototype.set_dirty = function (value) {
308 if (value === undefined) {
308 if (value === undefined) {
309 value = true;
309 value = true;
310 }
310 }
311 if (this.dirty == value) {
311 if (this.dirty == value) {
312 return;
312 return;
313 }
313 }
314 this.events.trigger('set_dirty.Notebook', {value: value});
314 this.events.trigger('set_dirty.Notebook', {value: value});
315 };
315 };
316
316
317 /**
317 /**
318 * Scroll the top of the page to a given cell.
318 * Scroll the top of the page to a given cell.
319 *
319 *
320 * @method scroll_to_cell
320 * @method scroll_to_cell
321 * @param {Number} cell_number An index of the cell to view
321 * @param {Number} cell_number An index of the cell to view
322 * @param {Number} time Animation time in milliseconds
322 * @param {Number} time Animation time in milliseconds
323 * @return {Number} Pixel offset from the top of the container
323 * @return {Number} Pixel offset from the top of the container
324 */
324 */
325 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
325 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
326 var cells = this.get_cells();
326 var cells = this.get_cells();
327 time = time || 0;
327 time = time || 0;
328 cell_number = Math.min(cells.length-1,cell_number);
328 cell_number = Math.min(cells.length-1,cell_number);
329 cell_number = Math.max(0 ,cell_number);
329 cell_number = Math.max(0 ,cell_number);
330 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
330 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
331 this.element.animate({scrollTop:scroll_value}, time);
331 this.element.animate({scrollTop:scroll_value}, time);
332 return scroll_value;
332 return scroll_value;
333 };
333 };
334
334
335 /**
335 /**
336 * Scroll to the bottom of the page.
336 * Scroll to the bottom of the page.
337 *
337 *
338 * @method scroll_to_bottom
338 * @method scroll_to_bottom
339 */
339 */
340 Notebook.prototype.scroll_to_bottom = function () {
340 Notebook.prototype.scroll_to_bottom = function () {
341 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
341 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
342 };
342 };
343
343
344 /**
344 /**
345 * Scroll to the top of the page.
345 * Scroll to the top of the page.
346 *
346 *
347 * @method scroll_to_top
347 * @method scroll_to_top
348 */
348 */
349 Notebook.prototype.scroll_to_top = function () {
349 Notebook.prototype.scroll_to_top = function () {
350 this.element.animate({scrollTop:0}, 0);
350 this.element.animate({scrollTop:0}, 0);
351 };
351 };
352
352
353 // Edit Notebook metadata
353 // Edit Notebook metadata
354
354
355 Notebook.prototype.edit_metadata = function () {
355 Notebook.prototype.edit_metadata = function () {
356 var that = this;
356 var that = this;
357 dialog.edit_metadata({
357 dialog.edit_metadata({
358 md: this.metadata,
358 md: this.metadata,
359 callback: function (md) {
359 callback: function (md) {
360 that.metadata = md;
360 that.metadata = md;
361 },
361 },
362 name: 'Notebook',
362 name: 'Notebook',
363 notebook: this,
363 notebook: this,
364 keyboard_manager: this.keyboard_manager});
364 keyboard_manager: this.keyboard_manager});
365 };
365 };
366
366
367 // Cell indexing, retrieval, etc.
367 // Cell indexing, retrieval, etc.
368
368
369 /**
369 /**
370 * Get all cell elements in the notebook.
370 * Get all cell elements in the notebook.
371 *
371 *
372 * @method get_cell_elements
372 * @method get_cell_elements
373 * @return {jQuery} A selector of all cell elements
373 * @return {jQuery} A selector of all cell elements
374 */
374 */
375 Notebook.prototype.get_cell_elements = function () {
375 Notebook.prototype.get_cell_elements = function () {
376 return this.container.children("div.cell");
376 return this.container.children("div.cell");
377 };
377 };
378
378
379 /**
379 /**
380 * Get a particular cell element.
380 * Get a particular cell element.
381 *
381 *
382 * @method get_cell_element
382 * @method get_cell_element
383 * @param {Number} index An index of a cell to select
383 * @param {Number} index An index of a cell to select
384 * @return {jQuery} A selector of the given cell.
384 * @return {jQuery} A selector of the given cell.
385 */
385 */
386 Notebook.prototype.get_cell_element = function (index) {
386 Notebook.prototype.get_cell_element = function (index) {
387 var result = null;
387 var result = null;
388 var e = this.get_cell_elements().eq(index);
388 var e = this.get_cell_elements().eq(index);
389 if (e.length !== 0) {
389 if (e.length !== 0) {
390 result = e;
390 result = e;
391 }
391 }
392 return result;
392 return result;
393 };
393 };
394
394
395 /**
395 /**
396 * Try to get a particular cell by msg_id.
396 * Try to get a particular cell by msg_id.
397 *
397 *
398 * @method get_msg_cell
398 * @method get_msg_cell
399 * @param {String} msg_id A message UUID
399 * @param {String} msg_id A message UUID
400 * @return {Cell} Cell or null if no cell was found.
400 * @return {Cell} Cell or null if no cell was found.
401 */
401 */
402 Notebook.prototype.get_msg_cell = function (msg_id) {
402 Notebook.prototype.get_msg_cell = function (msg_id) {
403 return codecell.CodeCell.msg_cells[msg_id] || null;
403 return codecell.CodeCell.msg_cells[msg_id] || null;
404 };
404 };
405
405
406 /**
406 /**
407 * Count the cells in this notebook.
407 * Count the cells in this notebook.
408 *
408 *
409 * @method ncells
409 * @method ncells
410 * @return {Number} The number of cells in this notebook
410 * @return {Number} The number of cells in this notebook
411 */
411 */
412 Notebook.prototype.ncells = function () {
412 Notebook.prototype.ncells = function () {
413 return this.get_cell_elements().length;
413 return this.get_cell_elements().length;
414 };
414 };
415
415
416 /**
416 /**
417 * Get all Cell objects in this notebook.
417 * Get all Cell objects in this notebook.
418 *
418 *
419 * @method get_cells
419 * @method get_cells
420 * @return {Array} This notebook's Cell objects
420 * @return {Array} This notebook's Cell objects
421 */
421 */
422 // TODO: we are often calling cells as cells()[i], which we should optimize
422 // TODO: we are often calling cells as cells()[i], which we should optimize
423 // to cells(i) or a new method.
423 // to cells(i) or a new method.
424 Notebook.prototype.get_cells = function () {
424 Notebook.prototype.get_cells = function () {
425 return this.get_cell_elements().toArray().map(function (e) {
425 return this.get_cell_elements().toArray().map(function (e) {
426 return $(e).data("cell");
426 return $(e).data("cell");
427 });
427 });
428 };
428 };
429
429
430 /**
430 /**
431 * Get a Cell object from this notebook.
431 * Get a Cell object from this notebook.
432 *
432 *
433 * @method get_cell
433 * @method get_cell
434 * @param {Number} index An index of a cell to retrieve
434 * @param {Number} index An index of a cell to retrieve
435 * @return {Cell} Cell or null if no cell was found.
435 * @return {Cell} Cell or null if no cell was found.
436 */
436 */
437 Notebook.prototype.get_cell = function (index) {
437 Notebook.prototype.get_cell = function (index) {
438 var result = null;
438 var result = null;
439 var ce = this.get_cell_element(index);
439 var ce = this.get_cell_element(index);
440 if (ce !== null) {
440 if (ce !== null) {
441 result = ce.data('cell');
441 result = ce.data('cell');
442 }
442 }
443 return result;
443 return result;
444 };
444 };
445
445
446 /**
446 /**
447 * Get the cell below a given cell.
447 * Get the cell below a given cell.
448 *
448 *
449 * @method get_next_cell
449 * @method get_next_cell
450 * @param {Cell} cell The provided cell
450 * @param {Cell} cell The provided cell
451 * @return {Cell} the next cell or null if no cell was found.
451 * @return {Cell} the next cell or null if no cell was found.
452 */
452 */
453 Notebook.prototype.get_next_cell = function (cell) {
453 Notebook.prototype.get_next_cell = function (cell) {
454 var result = null;
454 var result = null;
455 var index = this.find_cell_index(cell);
455 var index = this.find_cell_index(cell);
456 if (this.is_valid_cell_index(index+1)) {
456 if (this.is_valid_cell_index(index+1)) {
457 result = this.get_cell(index+1);
457 result = this.get_cell(index+1);
458 }
458 }
459 return result;
459 return result;
460 };
460 };
461
461
462 /**
462 /**
463 * Get the cell above a given cell.
463 * Get the cell above a given cell.
464 *
464 *
465 * @method get_prev_cell
465 * @method get_prev_cell
466 * @param {Cell} cell The provided cell
466 * @param {Cell} cell The provided cell
467 * @return {Cell} The previous cell or null if no cell was found.
467 * @return {Cell} The previous cell or null if no cell was found.
468 */
468 */
469 Notebook.prototype.get_prev_cell = function (cell) {
469 Notebook.prototype.get_prev_cell = function (cell) {
470 var result = null;
470 var result = null;
471 var index = this.find_cell_index(cell);
471 var index = this.find_cell_index(cell);
472 if (index !== null && index > 0) {
472 if (index !== null && index > 0) {
473 result = this.get_cell(index-1);
473 result = this.get_cell(index-1);
474 }
474 }
475 return result;
475 return result;
476 };
476 };
477
477
478 /**
478 /**
479 * Get the numeric index of a given cell.
479 * Get the numeric index of a given cell.
480 *
480 *
481 * @method find_cell_index
481 * @method find_cell_index
482 * @param {Cell} cell The provided cell
482 * @param {Cell} cell The provided cell
483 * @return {Number} The cell's numeric index or null if no cell was found.
483 * @return {Number} The cell's numeric index or null if no cell was found.
484 */
484 */
485 Notebook.prototype.find_cell_index = function (cell) {
485 Notebook.prototype.find_cell_index = function (cell) {
486 var result = null;
486 var result = null;
487 this.get_cell_elements().filter(function (index) {
487 this.get_cell_elements().filter(function (index) {
488 if ($(this).data("cell") === cell) {
488 if ($(this).data("cell") === cell) {
489 result = index;
489 result = index;
490 }
490 }
491 });
491 });
492 return result;
492 return result;
493 };
493 };
494
494
495 /**
495 /**
496 * Get a given index , or the selected index if none is provided.
496 * Get a given index , or the selected index if none is provided.
497 *
497 *
498 * @method index_or_selected
498 * @method index_or_selected
499 * @param {Number} index A cell's index
499 * @param {Number} index A cell's index
500 * @return {Number} The given index, or selected index if none is provided.
500 * @return {Number} The given index, or selected index if none is provided.
501 */
501 */
502 Notebook.prototype.index_or_selected = function (index) {
502 Notebook.prototype.index_or_selected = function (index) {
503 var i;
503 var i;
504 if (index === undefined || index === null) {
504 if (index === undefined || index === null) {
505 i = this.get_selected_index();
505 i = this.get_selected_index();
506 if (i === null) {
506 if (i === null) {
507 i = 0;
507 i = 0;
508 }
508 }
509 } else {
509 } else {
510 i = index;
510 i = index;
511 }
511 }
512 return i;
512 return i;
513 };
513 };
514
514
515 /**
515 /**
516 * Get the currently selected cell.
516 * Get the currently selected cell.
517 * @method get_selected_cell
517 * @method get_selected_cell
518 * @return {Cell} The selected cell
518 * @return {Cell} The selected cell
519 */
519 */
520 Notebook.prototype.get_selected_cell = function () {
520 Notebook.prototype.get_selected_cell = function () {
521 var index = this.get_selected_index();
521 var index = this.get_selected_index();
522 return this.get_cell(index);
522 return this.get_cell(index);
523 };
523 };
524
524
525 /**
525 /**
526 * Check whether a cell index is valid.
526 * Check whether a cell index is valid.
527 *
527 *
528 * @method is_valid_cell_index
528 * @method is_valid_cell_index
529 * @param {Number} index A cell index
529 * @param {Number} index A cell index
530 * @return True if the index is valid, false otherwise
530 * @return True if the index is valid, false otherwise
531 */
531 */
532 Notebook.prototype.is_valid_cell_index = function (index) {
532 Notebook.prototype.is_valid_cell_index = function (index) {
533 if (index !== null && index >= 0 && index < this.ncells()) {
533 if (index !== null && index >= 0 && index < this.ncells()) {
534 return true;
534 return true;
535 } else {
535 } else {
536 return false;
536 return false;
537 }
537 }
538 };
538 };
539
539
540 /**
540 /**
541 * Get the index of the currently selected cell.
541 * Get the index of the currently selected cell.
542
542
543 * @method get_selected_index
543 * @method get_selected_index
544 * @return {Number} The selected cell's numeric index
544 * @return {Number} The selected cell's numeric index
545 */
545 */
546 Notebook.prototype.get_selected_index = function () {
546 Notebook.prototype.get_selected_index = function () {
547 var result = null;
547 var result = null;
548 this.get_cell_elements().filter(function (index) {
548 this.get_cell_elements().filter(function (index) {
549 if ($(this).data("cell").selected === true) {
549 if ($(this).data("cell").selected === true) {
550 result = index;
550 result = index;
551 }
551 }
552 });
552 });
553 return result;
553 return result;
554 };
554 };
555
555
556
556
557 // Cell selection.
557 // Cell selection.
558
558
559 /**
559 /**
560 * Programmatically select a cell.
560 * Programmatically select a cell.
561 *
561 *
562 * @method select
562 * @method select
563 * @param {Number} index A cell's index
563 * @param {Number} index A cell's index
564 * @return {Notebook} This notebook
564 * @return {Notebook} This notebook
565 */
565 */
566 Notebook.prototype.select = function (index) {
566 Notebook.prototype.select = function (index) {
567 if (this.is_valid_cell_index(index)) {
567 if (this.is_valid_cell_index(index)) {
568 var sindex = this.get_selected_index();
568 var sindex = this.get_selected_index();
569 if (sindex !== null && index !== sindex) {
569 if (sindex !== null && index !== sindex) {
570 // If we are about to select a different cell, make sure we are
570 // If we are about to select a different cell, make sure we are
571 // first in command mode.
571 // first in command mode.
572 if (this.mode !== 'command') {
572 if (this.mode !== 'command') {
573 this.command_mode();
573 this.command_mode();
574 }
574 }
575 this.get_cell(sindex).unselect();
575 this.get_cell(sindex).unselect();
576 }
576 }
577 var cell = this.get_cell(index);
577 var cell = this.get_cell(index);
578 cell.select();
578 cell.select();
579 if (cell.cell_type === 'heading') {
579 if (cell.cell_type === 'heading') {
580 this.events.trigger('selected_cell_type_changed.Notebook',
580 this.events.trigger('selected_cell_type_changed.Notebook',
581 {'cell_type':cell.cell_type,level:cell.level}
581 {'cell_type':cell.cell_type,level:cell.level}
582 );
582 );
583 } else {
583 } else {
584 this.events.trigger('selected_cell_type_changed.Notebook',
584 this.events.trigger('selected_cell_type_changed.Notebook',
585 {'cell_type':cell.cell_type}
585 {'cell_type':cell.cell_type}
586 );
586 );
587 }
587 }
588 }
588 }
589 return this;
589 return this;
590 };
590 };
591
591
592 /**
592 /**
593 * Programmatically select the next cell.
593 * Programmatically select the next cell.
594 *
594 *
595 * @method select_next
595 * @method select_next
596 * @return {Notebook} This notebook
596 * @return {Notebook} This notebook
597 */
597 */
598 Notebook.prototype.select_next = function () {
598 Notebook.prototype.select_next = function () {
599 var index = this.get_selected_index();
599 var index = this.get_selected_index();
600 this.select(index+1);
600 this.select(index+1);
601 return this;
601 return this;
602 };
602 };
603
603
604 /**
604 /**
605 * Programmatically select the previous cell.
605 * Programmatically select the previous cell.
606 *
606 *
607 * @method select_prev
607 * @method select_prev
608 * @return {Notebook} This notebook
608 * @return {Notebook} This notebook
609 */
609 */
610 Notebook.prototype.select_prev = function () {
610 Notebook.prototype.select_prev = function () {
611 var index = this.get_selected_index();
611 var index = this.get_selected_index();
612 this.select(index-1);
612 this.select(index-1);
613 return this;
613 return this;
614 };
614 };
615
615
616
616
617 // Edit/Command mode
617 // Edit/Command mode
618
618
619 /**
619 /**
620 * Gets the index of the cell that is in edit mode.
620 * Gets the index of the cell that is in edit mode.
621 *
621 *
622 * @method get_edit_index
622 * @method get_edit_index
623 *
623 *
624 * @return index {int}
624 * @return index {int}
625 **/
625 **/
626 Notebook.prototype.get_edit_index = function () {
626 Notebook.prototype.get_edit_index = function () {
627 var result = null;
627 var result = null;
628 this.get_cell_elements().filter(function (index) {
628 this.get_cell_elements().filter(function (index) {
629 if ($(this).data("cell").mode === 'edit') {
629 if ($(this).data("cell").mode === 'edit') {
630 result = index;
630 result = index;
631 }
631 }
632 });
632 });
633 return result;
633 return result;
634 };
634 };
635
635
636 /**
636 /**
637 * Handle when a a cell blurs and the notebook should enter command mode.
637 * Handle when a a cell blurs and the notebook should enter command mode.
638 *
638 *
639 * @method handle_command_mode
639 * @method handle_command_mode
640 * @param [cell] {Cell} Cell to enter command mode on.
640 * @param [cell] {Cell} Cell to enter command mode on.
641 **/
641 **/
642 Notebook.prototype.handle_command_mode = function (cell) {
642 Notebook.prototype.handle_command_mode = function (cell) {
643 if (this.mode !== 'command') {
643 if (this.mode !== 'command') {
644 cell.command_mode();
644 cell.command_mode();
645 this.mode = 'command';
645 this.mode = 'command';
646 this.events.trigger('command_mode.Notebook');
646 this.events.trigger('command_mode.Notebook');
647 this.keyboard_manager.command_mode();
647 this.keyboard_manager.command_mode();
648 }
648 }
649 };
649 };
650
650
651 /**
651 /**
652 * Make the notebook enter command mode.
652 * Make the notebook enter command mode.
653 *
653 *
654 * @method command_mode
654 * @method command_mode
655 **/
655 **/
656 Notebook.prototype.command_mode = function () {
656 Notebook.prototype.command_mode = function () {
657 var cell = this.get_cell(this.get_edit_index());
657 var cell = this.get_cell(this.get_edit_index());
658 if (cell && this.mode !== 'command') {
658 if (cell && this.mode !== 'command') {
659 // We don't call cell.command_mode, but rather call cell.focus_cell()
659 // We don't call cell.command_mode, but rather call cell.focus_cell()
660 // which will blur and CM editor and trigger the call to
660 // which will blur and CM editor and trigger the call to
661 // handle_command_mode.
661 // handle_command_mode.
662 cell.focus_cell();
662 cell.focus_cell();
663 }
663 }
664 };
664 };
665
665
666 /**
666 /**
667 * Handle when a cell fires it's edit_mode event.
667 * Handle when a cell fires it's edit_mode event.
668 *
668 *
669 * @method handle_edit_mode
669 * @method handle_edit_mode
670 * @param [cell] {Cell} Cell to enter edit mode on.
670 * @param [cell] {Cell} Cell to enter edit mode on.
671 **/
671 **/
672 Notebook.prototype.handle_edit_mode = function (cell) {
672 Notebook.prototype.handle_edit_mode = function (cell) {
673 if (cell && this.mode !== 'edit') {
673 if (cell && this.mode !== 'edit') {
674 cell.edit_mode();
674 cell.edit_mode();
675 this.mode = 'edit';
675 this.mode = 'edit';
676 this.events.trigger('edit_mode.Notebook');
676 this.events.trigger('edit_mode.Notebook');
677 this.keyboard_manager.edit_mode();
677 this.keyboard_manager.edit_mode();
678 }
678 }
679 };
679 };
680
680
681 /**
681 /**
682 * Make a cell enter edit mode.
682 * Make a cell enter edit mode.
683 *
683 *
684 * @method edit_mode
684 * @method edit_mode
685 **/
685 **/
686 Notebook.prototype.edit_mode = function () {
686 Notebook.prototype.edit_mode = function () {
687 var cell = this.get_selected_cell();
687 var cell = this.get_selected_cell();
688 if (cell && this.mode !== 'edit') {
688 if (cell && this.mode !== 'edit') {
689 cell.unrender();
689 cell.unrender();
690 cell.focus_editor();
690 cell.focus_editor();
691 }
691 }
692 };
692 };
693
693
694 /**
694 /**
695 * Focus the currently selected cell.
695 * Focus the currently selected cell.
696 *
696 *
697 * @method focus_cell
697 * @method focus_cell
698 **/
698 **/
699 Notebook.prototype.focus_cell = function () {
699 Notebook.prototype.focus_cell = function () {
700 var cell = this.get_selected_cell();
700 var cell = this.get_selected_cell();
701 if (cell === null) {return;} // No cell is selected
701 if (cell === null) {return;} // No cell is selected
702 cell.focus_cell();
702 cell.focus_cell();
703 };
703 };
704
704
705 // Cell movement
705 // Cell movement
706
706
707 /**
707 /**
708 * Move given (or selected) cell up and select it.
708 * Move given (or selected) cell up and select it.
709 *
709 *
710 * @method move_cell_up
710 * @method move_cell_up
711 * @param [index] {integer} cell index
711 * @param [index] {integer} cell index
712 * @return {Notebook} This notebook
712 * @return {Notebook} This notebook
713 **/
713 **/
714 Notebook.prototype.move_cell_up = function (index) {
714 Notebook.prototype.move_cell_up = function (index) {
715 var i = this.index_or_selected(index);
715 var i = this.index_or_selected(index);
716 if (this.is_valid_cell_index(i) && i > 0) {
716 if (this.is_valid_cell_index(i) && i > 0) {
717 var pivot = this.get_cell_element(i-1);
717 var pivot = this.get_cell_element(i-1);
718 var tomove = this.get_cell_element(i);
718 var tomove = this.get_cell_element(i);
719 if (pivot !== null && tomove !== null) {
719 if (pivot !== null && tomove !== null) {
720 tomove.detach();
720 tomove.detach();
721 pivot.before(tomove);
721 pivot.before(tomove);
722 this.select(i-1);
722 this.select(i-1);
723 var cell = this.get_selected_cell();
723 var cell = this.get_selected_cell();
724 cell.focus_cell();
724 cell.focus_cell();
725 }
725 }
726 this.set_dirty(true);
726 this.set_dirty(true);
727 }
727 }
728 return this;
728 return this;
729 };
729 };
730
730
731
731
732 /**
732 /**
733 * Move given (or selected) cell down and select it
733 * Move given (or selected) cell down and select it
734 *
734 *
735 * @method move_cell_down
735 * @method move_cell_down
736 * @param [index] {integer} cell index
736 * @param [index] {integer} cell index
737 * @return {Notebook} This notebook
737 * @return {Notebook} This notebook
738 **/
738 **/
739 Notebook.prototype.move_cell_down = function (index) {
739 Notebook.prototype.move_cell_down = function (index) {
740 var i = this.index_or_selected(index);
740 var i = this.index_or_selected(index);
741 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
741 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
742 var pivot = this.get_cell_element(i+1);
742 var pivot = this.get_cell_element(i+1);
743 var tomove = this.get_cell_element(i);
743 var tomove = this.get_cell_element(i);
744 if (pivot !== null && tomove !== null) {
744 if (pivot !== null && tomove !== null) {
745 tomove.detach();
745 tomove.detach();
746 pivot.after(tomove);
746 pivot.after(tomove);
747 this.select(i+1);
747 this.select(i+1);
748 var cell = this.get_selected_cell();
748 var cell = this.get_selected_cell();
749 cell.focus_cell();
749 cell.focus_cell();
750 }
750 }
751 }
751 }
752 this.set_dirty();
752 this.set_dirty();
753 return this;
753 return this;
754 };
754 };
755
755
756
756
757 // Insertion, deletion.
757 // Insertion, deletion.
758
758
759 /**
759 /**
760 * Delete a cell from the notebook.
760 * Delete a cell from the notebook.
761 *
761 *
762 * @method delete_cell
762 * @method delete_cell
763 * @param [index] A cell's numeric index
763 * @param [index] A cell's numeric index
764 * @return {Notebook} This notebook
764 * @return {Notebook} This notebook
765 */
765 */
766 Notebook.prototype.delete_cell = function (index) {
766 Notebook.prototype.delete_cell = function (index) {
767 var i = this.index_or_selected(index);
767 var i = this.index_or_selected(index);
768 var cell = this.get_cell(i);
768 var cell = this.get_cell(i);
769 if (!cell.is_deletable()) {
769 if (!cell.is_deletable()) {
770 return this;
770 return this;
771 }
771 }
772
772
773 this.undelete_backup = cell.toJSON();
773 this.undelete_backup = cell.toJSON();
774 $('#undelete_cell').removeClass('disabled');
774 $('#undelete_cell').removeClass('disabled');
775 if (this.is_valid_cell_index(i)) {
775 if (this.is_valid_cell_index(i)) {
776 var old_ncells = this.ncells();
776 var old_ncells = this.ncells();
777 var ce = this.get_cell_element(i);
777 var ce = this.get_cell_element(i);
778 ce.remove();
778 ce.remove();
779 if (i === 0) {
779 if (i === 0) {
780 // Always make sure we have at least one cell.
780 // Always make sure we have at least one cell.
781 if (old_ncells === 1) {
781 if (old_ncells === 1) {
782 this.insert_cell_below('code');
782 this.insert_cell_below('code');
783 }
783 }
784 this.select(0);
784 this.select(0);
785 this.undelete_index = 0;
785 this.undelete_index = 0;
786 this.undelete_below = false;
786 this.undelete_below = false;
787 } else if (i === old_ncells-1 && i !== 0) {
787 } else if (i === old_ncells-1 && i !== 0) {
788 this.select(i-1);
788 this.select(i-1);
789 this.undelete_index = i - 1;
789 this.undelete_index = i - 1;
790 this.undelete_below = true;
790 this.undelete_below = true;
791 } else {
791 } else {
792 this.select(i);
792 this.select(i);
793 this.undelete_index = i;
793 this.undelete_index = i;
794 this.undelete_below = false;
794 this.undelete_below = false;
795 }
795 }
796 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
796 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
797 this.set_dirty(true);
797 this.set_dirty(true);
798 }
798 }
799 return this;
799 return this;
800 };
800 };
801
801
802 /**
802 /**
803 * Restore the most recently deleted cell.
803 * Restore the most recently deleted cell.
804 *
804 *
805 * @method undelete
805 * @method undelete
806 */
806 */
807 Notebook.prototype.undelete_cell = function() {
807 Notebook.prototype.undelete_cell = function() {
808 if (this.undelete_backup !== null && this.undelete_index !== null) {
808 if (this.undelete_backup !== null && this.undelete_index !== null) {
809 var current_index = this.get_selected_index();
809 var current_index = this.get_selected_index();
810 if (this.undelete_index < current_index) {
810 if (this.undelete_index < current_index) {
811 current_index = current_index + 1;
811 current_index = current_index + 1;
812 }
812 }
813 if (this.undelete_index >= this.ncells()) {
813 if (this.undelete_index >= this.ncells()) {
814 this.select(this.ncells() - 1);
814 this.select(this.ncells() - 1);
815 }
815 }
816 else {
816 else {
817 this.select(this.undelete_index);
817 this.select(this.undelete_index);
818 }
818 }
819 var cell_data = this.undelete_backup;
819 var cell_data = this.undelete_backup;
820 var new_cell = null;
820 var new_cell = null;
821 if (this.undelete_below) {
821 if (this.undelete_below) {
822 new_cell = this.insert_cell_below(cell_data.cell_type);
822 new_cell = this.insert_cell_below(cell_data.cell_type);
823 } else {
823 } else {
824 new_cell = this.insert_cell_above(cell_data.cell_type);
824 new_cell = this.insert_cell_above(cell_data.cell_type);
825 }
825 }
826 new_cell.fromJSON(cell_data);
826 new_cell.fromJSON(cell_data);
827 if (this.undelete_below) {
827 if (this.undelete_below) {
828 this.select(current_index+1);
828 this.select(current_index+1);
829 } else {
829 } else {
830 this.select(current_index);
830 this.select(current_index);
831 }
831 }
832 this.undelete_backup = null;
832 this.undelete_backup = null;
833 this.undelete_index = null;
833 this.undelete_index = null;
834 }
834 }
835 $('#undelete_cell').addClass('disabled');
835 $('#undelete_cell').addClass('disabled');
836 };
836 };
837
837
838 /**
838 /**
839 * Insert a cell so that after insertion the cell is at given index.
839 * Insert a cell so that after insertion the cell is at given index.
840 *
840 *
841 * If cell type is not provided, it will default to the type of the
841 * If cell type is not provided, it will default to the type of the
842 * currently active cell.
842 * currently active cell.
843 *
843 *
844 * Similar to insert_above, but index parameter is mandatory
844 * Similar to insert_above, but index parameter is mandatory
845 *
845 *
846 * Index will be brought back into the accessible range [0,n]
846 * Index will be brought back into the accessible range [0,n]
847 *
847 *
848 * @method insert_cell_at_index
848 * @method insert_cell_at_index
849 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
849 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
850 * @param [index] {int} a valid index where to insert cell
850 * @param [index] {int} a valid index where to insert cell
851 *
851 *
852 * @return cell {cell|null} created cell or null
852 * @return cell {cell|null} created cell or null
853 **/
853 **/
854 Notebook.prototype.insert_cell_at_index = function(type, index){
854 Notebook.prototype.insert_cell_at_index = function(type, index){
855
855
856 var ncells = this.ncells();
856 var ncells = this.ncells();
857 index = Math.min(index, ncells);
857 index = Math.min(index, ncells);
858 index = Math.max(index, 0);
858 index = Math.max(index, 0);
859 var cell = null;
859 var cell = null;
860 type = type || this.default_cell_type;
860 type = type || this.default_cell_type;
861 if (type === 'above') {
861 if (type === 'above') {
862 if (index > 0) {
862 if (index > 0) {
863 type = this.get_cell(index-1).cell_type;
863 type = this.get_cell(index-1).cell_type;
864 } else {
864 } else {
865 type = 'code';
865 type = 'code';
866 }
866 }
867 } else if (type === 'below') {
867 } else if (type === 'below') {
868 if (index < ncells) {
868 if (index < ncells) {
869 type = this.get_cell(index).cell_type;
869 type = this.get_cell(index).cell_type;
870 } else {
870 } else {
871 type = 'code';
871 type = 'code';
872 }
872 }
873 } else if (type === 'selected') {
873 } else if (type === 'selected') {
874 type = this.get_selected_cell().cell_type;
874 type = this.get_selected_cell().cell_type;
875 }
875 }
876
876
877 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
877 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
878 var cell_options = {
878 var cell_options = {
879 events: this.events,
879 events: this.events,
880 config: this.config,
880 config: this.config,
881 keyboard_manager: this.keyboard_manager,
881 keyboard_manager: this.keyboard_manager,
882 notebook: this,
882 notebook: this,
883 tooltip: this.tooltip,
883 tooltip: this.tooltip,
884 };
884 };
885 switch(type) {
885 switch(type) {
886 case 'code':
886 case 'code':
887 cell = new codecell.CodeCell(this.kernel, cell_options);
887 cell = new codecell.CodeCell(this.kernel, cell_options);
888 cell.set_input_prompt();
888 cell.set_input_prompt();
889 break;
889 break;
890 case 'markdown':
890 case 'markdown':
891 cell = new textcell.MarkdownCell(cell_options);
891 cell = new textcell.MarkdownCell(cell_options);
892 break;
892 break;
893 case 'raw':
893 case 'raw':
894 cell = new textcell.RawCell(cell_options);
894 cell = new textcell.RawCell(cell_options);
895 break;
895 break;
896 default:
896 default:
897 console.log("invalid cell type: ", type);
897 console.log("invalid cell type: ", type);
898 }
898 }
899
899
900 if(this._insert_element_at_index(cell.element,index)) {
900 if(this._insert_element_at_index(cell.element,index)) {
901 cell.render();
901 cell.render();
902 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
902 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
903 cell.refresh();
903 cell.refresh();
904 // We used to select the cell after we refresh it, but there
904 // We used to select the cell after we refresh it, but there
905 // are now cases were this method is called where select is
905 // are now cases were this method is called where select is
906 // not appropriate. The selection logic should be handled by the
906 // not appropriate. The selection logic should be handled by the
907 // caller of the the top level insert_cell methods.
907 // caller of the the top level insert_cell methods.
908 this.set_dirty(true);
908 this.set_dirty(true);
909 }
909 }
910 }
910 }
911 return cell;
911 return cell;
912
912
913 };
913 };
914
914
915 /**
915 /**
916 * Insert an element at given cell index.
916 * Insert an element at given cell index.
917 *
917 *
918 * @method _insert_element_at_index
918 * @method _insert_element_at_index
919 * @param element {dom_element} a cell element
919 * @param element {dom_element} a cell element
920 * @param [index] {int} a valid index where to inser cell
920 * @param [index] {int} a valid index where to inser cell
921 * @private
921 * @private
922 *
922 *
923 * return true if everything whent fine.
923 * return true if everything whent fine.
924 **/
924 **/
925 Notebook.prototype._insert_element_at_index = function(element, index){
925 Notebook.prototype._insert_element_at_index = function(element, index){
926 if (element === undefined){
926 if (element === undefined){
927 return false;
927 return false;
928 }
928 }
929
929
930 var ncells = this.ncells();
930 var ncells = this.ncells();
931
931
932 if (ncells === 0) {
932 if (ncells === 0) {
933 // special case append if empty
933 // special case append if empty
934 this.element.find('div.end_space').before(element);
934 this.element.find('div.end_space').before(element);
935 } else if ( ncells === index ) {
935 } else if ( ncells === index ) {
936 // special case append it the end, but not empty
936 // special case append it the end, but not empty
937 this.get_cell_element(index-1).after(element);
937 this.get_cell_element(index-1).after(element);
938 } else if (this.is_valid_cell_index(index)) {
938 } else if (this.is_valid_cell_index(index)) {
939 // otherwise always somewhere to append to
939 // otherwise always somewhere to append to
940 this.get_cell_element(index).before(element);
940 this.get_cell_element(index).before(element);
941 } else {
941 } else {
942 return false;
942 return false;
943 }
943 }
944
944
945 if (this.undelete_index !== null && index <= this.undelete_index) {
945 if (this.undelete_index !== null && index <= this.undelete_index) {
946 this.undelete_index = this.undelete_index + 1;
946 this.undelete_index = this.undelete_index + 1;
947 this.set_dirty(true);
947 this.set_dirty(true);
948 }
948 }
949 return true;
949 return true;
950 };
950 };
951
951
952 /**
952 /**
953 * Insert a cell of given type above given index, or at top
953 * Insert a cell of given type above given index, or at top
954 * of notebook if index smaller than 0.
954 * of notebook if index smaller than 0.
955 *
955 *
956 * default index value is the one of currently selected cell
956 * default index value is the one of currently selected cell
957 *
957 *
958 * @method insert_cell_above
958 * @method insert_cell_above
959 * @param [type] {string} cell type
959 * @param [type] {string} cell type
960 * @param [index] {integer}
960 * @param [index] {integer}
961 *
961 *
962 * @return handle to created cell or null
962 * @return handle to created cell or null
963 **/
963 **/
964 Notebook.prototype.insert_cell_above = function (type, index) {
964 Notebook.prototype.insert_cell_above = function (type, index) {
965 index = this.index_or_selected(index);
965 index = this.index_or_selected(index);
966 return this.insert_cell_at_index(type, index);
966 return this.insert_cell_at_index(type, index);
967 };
967 };
968
968
969 /**
969 /**
970 * Insert a cell of given type below given index, or at bottom
970 * Insert a cell of given type below given index, or at bottom
971 * of notebook if index greater than number of cells
971 * of notebook if index greater than number of cells
972 *
972 *
973 * default index value is the one of currently selected cell
973 * default index value is the one of currently selected cell
974 *
974 *
975 * @method insert_cell_below
975 * @method insert_cell_below
976 * @param [type] {string} cell type
976 * @param [type] {string} cell type
977 * @param [index] {integer}
977 * @param [index] {integer}
978 *
978 *
979 * @return handle to created cell or null
979 * @return handle to created cell or null
980 *
980 *
981 **/
981 **/
982 Notebook.prototype.insert_cell_below = function (type, index) {
982 Notebook.prototype.insert_cell_below = function (type, index) {
983 index = this.index_or_selected(index);
983 index = this.index_or_selected(index);
984 return this.insert_cell_at_index(type, index+1);
984 return this.insert_cell_at_index(type, index+1);
985 };
985 };
986
986
987
987
988 /**
988 /**
989 * Insert cell at end of notebook
989 * Insert cell at end of notebook
990 *
990 *
991 * @method insert_cell_at_bottom
991 * @method insert_cell_at_bottom
992 * @param {String} type cell type
992 * @param {String} type cell type
993 *
993 *
994 * @return the added cell; or null
994 * @return the added cell; or null
995 **/
995 **/
996 Notebook.prototype.insert_cell_at_bottom = function (type){
996 Notebook.prototype.insert_cell_at_bottom = function (type){
997 var len = this.ncells();
997 var len = this.ncells();
998 return this.insert_cell_below(type,len-1);
998 return this.insert_cell_below(type,len-1);
999 };
999 };
1000
1000
1001 /**
1001 /**
1002 * Turn a cell into a code cell.
1002 * Turn a cell into a code cell.
1003 *
1003 *
1004 * @method to_code
1004 * @method to_code
1005 * @param {Number} [index] A cell's index
1005 * @param {Number} [index] A cell's index
1006 */
1006 */
1007 Notebook.prototype.to_code = function (index) {
1007 Notebook.prototype.to_code = function (index) {
1008 var i = this.index_or_selected(index);
1008 var i = this.index_or_selected(index);
1009 if (this.is_valid_cell_index(i)) {
1009 if (this.is_valid_cell_index(i)) {
1010 var source_cell = this.get_cell(i);
1010 var source_cell = this.get_cell(i);
1011 if (!(source_cell instanceof codecell.CodeCell)) {
1011 if (!(source_cell instanceof codecell.CodeCell)) {
1012 var target_cell = this.insert_cell_below('code',i);
1012 var target_cell = this.insert_cell_below('code',i);
1013 var text = source_cell.get_text();
1013 var text = source_cell.get_text();
1014 if (text === source_cell.placeholder) {
1014 if (text === source_cell.placeholder) {
1015 text = '';
1015 text = '';
1016 }
1016 }
1017 //metadata
1017 //metadata
1018 target_cell.metadata = source_cell.metadata;
1018 target_cell.metadata = source_cell.metadata;
1019
1019
1020 target_cell.set_text(text);
1020 target_cell.set_text(text);
1021 // make this value the starting point, so that we can only undo
1021 // make this value the starting point, so that we can only undo
1022 // to this state, instead of a blank cell
1022 // to this state, instead of a blank cell
1023 target_cell.code_mirror.clearHistory();
1023 target_cell.code_mirror.clearHistory();
1024 source_cell.element.remove();
1024 source_cell.element.remove();
1025 this.select(i);
1025 this.select(i);
1026 var cursor = source_cell.code_mirror.getCursor();
1026 var cursor = source_cell.code_mirror.getCursor();
1027 target_cell.code_mirror.setCursor(cursor);
1027 target_cell.code_mirror.setCursor(cursor);
1028 this.set_dirty(true);
1028 this.set_dirty(true);
1029 }
1029 }
1030 }
1030 }
1031 };
1031 };
1032
1032
1033 /**
1033 /**
1034 * Turn a cell into a Markdown cell.
1034 * Turn a cell into a Markdown cell.
1035 *
1035 *
1036 * @method to_markdown
1036 * @method to_markdown
1037 * @param {Number} [index] A cell's index
1037 * @param {Number} [index] A cell's index
1038 */
1038 */
1039 Notebook.prototype.to_markdown = function (index) {
1039 Notebook.prototype.to_markdown = function (index) {
1040 var i = this.index_or_selected(index);
1040 var i = this.index_or_selected(index);
1041 if (this.is_valid_cell_index(i)) {
1041 if (this.is_valid_cell_index(i)) {
1042 var source_cell = this.get_cell(i);
1042 var source_cell = this.get_cell(i);
1043
1043
1044 if (!(source_cell instanceof textcell.MarkdownCell)) {
1044 if (!(source_cell instanceof textcell.MarkdownCell)) {
1045 var target_cell = this.insert_cell_below('markdown',i);
1045 var target_cell = this.insert_cell_below('markdown',i);
1046 var text = source_cell.get_text();
1046 var text = source_cell.get_text();
1047
1047
1048 if (text === source_cell.placeholder) {
1048 if (text === source_cell.placeholder) {
1049 text = '';
1049 text = '';
1050 }
1050 }
1051 // metadata
1051 // metadata
1052 target_cell.metadata = source_cell.metadata;
1052 target_cell.metadata = source_cell.metadata;
1053 // We must show the editor before setting its contents
1053 // We must show the editor before setting its contents
1054 target_cell.unrender();
1054 target_cell.unrender();
1055 target_cell.set_text(text);
1055 target_cell.set_text(text);
1056 // make this value the starting point, so that we can only undo
1056 // make this value the starting point, so that we can only undo
1057 // to this state, instead of a blank cell
1057 // to this state, instead of a blank cell
1058 target_cell.code_mirror.clearHistory();
1058 target_cell.code_mirror.clearHistory();
1059 source_cell.element.remove();
1059 source_cell.element.remove();
1060 this.select(i);
1060 this.select(i);
1061 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1061 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1062 target_cell.render();
1062 target_cell.render();
1063 }
1063 }
1064 var cursor = source_cell.code_mirror.getCursor();
1064 var cursor = source_cell.code_mirror.getCursor();
1065 target_cell.code_mirror.setCursor(cursor);
1065 target_cell.code_mirror.setCursor(cursor);
1066 this.set_dirty(true);
1066 this.set_dirty(true);
1067 }
1067 }
1068 }
1068 }
1069 };
1069 };
1070
1070
1071 /**
1071 /**
1072 * Turn a cell into a raw text cell.
1072 * Turn a cell into a raw text cell.
1073 *
1073 *
1074 * @method to_raw
1074 * @method to_raw
1075 * @param {Number} [index] A cell's index
1075 * @param {Number} [index] A cell's index
1076 */
1076 */
1077 Notebook.prototype.to_raw = function (index) {
1077 Notebook.prototype.to_raw = function (index) {
1078 var i = this.index_or_selected(index);
1078 var i = this.index_or_selected(index);
1079 if (this.is_valid_cell_index(i)) {
1079 if (this.is_valid_cell_index(i)) {
1080 var target_cell = null;
1080 var target_cell = null;
1081 var source_cell = this.get_cell(i);
1081 var source_cell = this.get_cell(i);
1082
1082
1083 if (!(source_cell instanceof textcell.RawCell)) {
1083 if (!(source_cell instanceof textcell.RawCell)) {
1084 target_cell = this.insert_cell_below('raw',i);
1084 target_cell = this.insert_cell_below('raw',i);
1085 var text = source_cell.get_text();
1085 var text = source_cell.get_text();
1086 if (text === source_cell.placeholder) {
1086 if (text === source_cell.placeholder) {
1087 text = '';
1087 text = '';
1088 }
1088 }
1089 //metadata
1089 //metadata
1090 target_cell.metadata = source_cell.metadata;
1090 target_cell.metadata = source_cell.metadata;
1091 // We must show the editor before setting its contents
1091 // We must show the editor before setting its contents
1092 target_cell.unrender();
1092 target_cell.unrender();
1093 target_cell.set_text(text);
1093 target_cell.set_text(text);
1094 // make this value the starting point, so that we can only undo
1094 // make this value the starting point, so that we can only undo
1095 // to this state, instead of a blank cell
1095 // to this state, instead of a blank cell
1096 target_cell.code_mirror.clearHistory();
1096 target_cell.code_mirror.clearHistory();
1097 source_cell.element.remove();
1097 source_cell.element.remove();
1098 this.select(i);
1098 this.select(i);
1099 var cursor = source_cell.code_mirror.getCursor();
1099 var cursor = source_cell.code_mirror.getCursor();
1100 target_cell.code_mirror.setCursor(cursor);
1100 target_cell.code_mirror.setCursor(cursor);
1101 this.set_dirty(true);
1101 this.set_dirty(true);
1102 }
1102 }
1103 }
1103 }
1104 };
1104 };
1105
1105
1106 Notebook.prototype._warn_heading = function () {
1106 Notebook.prototype._warn_heading = function () {
1107 // warn about heading cells being removed
1107 // warn about heading cells being removed
1108 dialog.modal({
1108 dialog.modal({
1109 notebook: this,
1109 notebook: this,
1110 keyboard_manager: this.keyboard_manager,
1110 keyboard_manager: this.keyboard_manager,
1111 title : "Use markdown headings",
1111 title : "Use markdown headings",
1112 body : $("<p/>").text(
1112 body : $("<p/>").text(
1113 'IPython no longer uses special heading cells. ' +
1113 'IPython no longer uses special heading cells. ' +
1114 'Instead, write your headings in Markdown cells using # characters:'
1114 'Instead, write your headings in Markdown cells using # characters:'
1115 ).append($('<pre/>').text(
1115 ).append($('<pre/>').text(
1116 '## This is a level 2 heading'
1116 '## This is a level 2 heading'
1117 )),
1117 )),
1118 buttons : {
1118 buttons : {
1119 "OK" : {},
1119 "OK" : {},
1120 }
1120 }
1121 });
1121 });
1122 };
1122 };
1123
1123
1124 /**
1124 /**
1125 * Turn a cell into a markdown cell with a heading.
1125 * Turn a cell into a markdown cell with a heading.
1126 *
1126 *
1127 * @method to_heading
1127 * @method to_heading
1128 * @param {Number} [index] A cell's index
1128 * @param {Number} [index] A cell's index
1129 * @param {Number} [level] A heading level (e.g., 1 for h1)
1129 * @param {Number} [level] A heading level (e.g., 1 for h1)
1130 */
1130 */
1131 Notebook.prototype.to_heading = function (index, level) {
1131 Notebook.prototype.to_heading = function (index, level) {
1132 this.to_markdown(index);
1132 this.to_markdown(index);
1133 level = level || 1;
1133 level = level || 1;
1134 var i = this.index_or_selected(index);
1134 var i = this.index_or_selected(index);
1135 if (this.is_valid_cell_index(i)) {
1135 if (this.is_valid_cell_index(i)) {
1136 var cell = this.get_cell(i);
1136 var cell = this.get_cell(i);
1137 cell.set_heading_level(level);
1137 cell.set_heading_level(level);
1138 this.set_dirty(true);
1138 this.set_dirty(true);
1139 }
1139 }
1140 };
1140 };
1141
1141
1142
1142
1143 // Cut/Copy/Paste
1143 // Cut/Copy/Paste
1144
1144
1145 /**
1145 /**
1146 * Enable UI elements for pasting cells.
1146 * Enable UI elements for pasting cells.
1147 *
1147 *
1148 * @method enable_paste
1148 * @method enable_paste
1149 */
1149 */
1150 Notebook.prototype.enable_paste = function () {
1150 Notebook.prototype.enable_paste = function () {
1151 var that = this;
1151 var that = this;
1152 if (!this.paste_enabled) {
1152 if (!this.paste_enabled) {
1153 $('#paste_cell_replace').removeClass('disabled')
1153 $('#paste_cell_replace').removeClass('disabled')
1154 .on('click', function () {that.paste_cell_replace();});
1154 .on('click', function () {that.paste_cell_replace();});
1155 $('#paste_cell_above').removeClass('disabled')
1155 $('#paste_cell_above').removeClass('disabled')
1156 .on('click', function () {that.paste_cell_above();});
1156 .on('click', function () {that.paste_cell_above();});
1157 $('#paste_cell_below').removeClass('disabled')
1157 $('#paste_cell_below').removeClass('disabled')
1158 .on('click', function () {that.paste_cell_below();});
1158 .on('click', function () {that.paste_cell_below();});
1159 this.paste_enabled = true;
1159 this.paste_enabled = true;
1160 }
1160 }
1161 };
1161 };
1162
1162
1163 /**
1163 /**
1164 * Disable UI elements for pasting cells.
1164 * Disable UI elements for pasting cells.
1165 *
1165 *
1166 * @method disable_paste
1166 * @method disable_paste
1167 */
1167 */
1168 Notebook.prototype.disable_paste = function () {
1168 Notebook.prototype.disable_paste = function () {
1169 if (this.paste_enabled) {
1169 if (this.paste_enabled) {
1170 $('#paste_cell_replace').addClass('disabled').off('click');
1170 $('#paste_cell_replace').addClass('disabled').off('click');
1171 $('#paste_cell_above').addClass('disabled').off('click');
1171 $('#paste_cell_above').addClass('disabled').off('click');
1172 $('#paste_cell_below').addClass('disabled').off('click');
1172 $('#paste_cell_below').addClass('disabled').off('click');
1173 this.paste_enabled = false;
1173 this.paste_enabled = false;
1174 }
1174 }
1175 };
1175 };
1176
1176
1177 /**
1177 /**
1178 * Cut a cell.
1178 * Cut a cell.
1179 *
1179 *
1180 * @method cut_cell
1180 * @method cut_cell
1181 */
1181 */
1182 Notebook.prototype.cut_cell = function () {
1182 Notebook.prototype.cut_cell = function () {
1183 this.copy_cell();
1183 this.copy_cell();
1184 this.delete_cell();
1184 this.delete_cell();
1185 };
1185 };
1186
1186
1187 /**
1187 /**
1188 * Copy a cell.
1188 * Copy a cell.
1189 *
1189 *
1190 * @method copy_cell
1190 * @method copy_cell
1191 */
1191 */
1192 Notebook.prototype.copy_cell = function () {
1192 Notebook.prototype.copy_cell = function () {
1193 var cell = this.get_selected_cell();
1193 var cell = this.get_selected_cell();
1194 this.clipboard = cell.toJSON();
1194 this.clipboard = cell.toJSON();
1195 // remove undeletable status from the copied cell
1195 // remove undeletable status from the copied cell
1196 if (this.clipboard.metadata.deletable !== undefined) {
1196 if (this.clipboard.metadata.deletable !== undefined) {
1197 delete this.clipboard.metadata.deletable;
1197 delete this.clipboard.metadata.deletable;
1198 }
1198 }
1199 this.enable_paste();
1199 this.enable_paste();
1200 };
1200 };
1201
1201
1202 /**
1202 /**
1203 * Replace the selected cell with a cell in the clipboard.
1203 * Replace the selected cell with a cell in the clipboard.
1204 *
1204 *
1205 * @method paste_cell_replace
1205 * @method paste_cell_replace
1206 */
1206 */
1207 Notebook.prototype.paste_cell_replace = function () {
1207 Notebook.prototype.paste_cell_replace = function () {
1208 if (this.clipboard !== null && this.paste_enabled) {
1208 if (this.clipboard !== null && this.paste_enabled) {
1209 var cell_data = this.clipboard;
1209 var cell_data = this.clipboard;
1210 var new_cell = this.insert_cell_above(cell_data.cell_type);
1210 var new_cell = this.insert_cell_above(cell_data.cell_type);
1211 new_cell.fromJSON(cell_data);
1211 new_cell.fromJSON(cell_data);
1212 var old_cell = this.get_next_cell(new_cell);
1212 var old_cell = this.get_next_cell(new_cell);
1213 this.delete_cell(this.find_cell_index(old_cell));
1213 this.delete_cell(this.find_cell_index(old_cell));
1214 this.select(this.find_cell_index(new_cell));
1214 this.select(this.find_cell_index(new_cell));
1215 }
1215 }
1216 };
1216 };
1217
1217
1218 /**
1218 /**
1219 * Paste a cell from the clipboard above the selected cell.
1219 * Paste a cell from the clipboard above the selected cell.
1220 *
1220 *
1221 * @method paste_cell_above
1221 * @method paste_cell_above
1222 */
1222 */
1223 Notebook.prototype.paste_cell_above = function () {
1223 Notebook.prototype.paste_cell_above = function () {
1224 if (this.clipboard !== null && this.paste_enabled) {
1224 if (this.clipboard !== null && this.paste_enabled) {
1225 var cell_data = this.clipboard;
1225 var cell_data = this.clipboard;
1226 var new_cell = this.insert_cell_above(cell_data.cell_type);
1226 var new_cell = this.insert_cell_above(cell_data.cell_type);
1227 new_cell.fromJSON(cell_data);
1227 new_cell.fromJSON(cell_data);
1228 new_cell.focus_cell();
1228 new_cell.focus_cell();
1229 }
1229 }
1230 };
1230 };
1231
1231
1232 /**
1232 /**
1233 * Paste a cell from the clipboard below the selected cell.
1233 * Paste a cell from the clipboard below the selected cell.
1234 *
1234 *
1235 * @method paste_cell_below
1235 * @method paste_cell_below
1236 */
1236 */
1237 Notebook.prototype.paste_cell_below = function () {
1237 Notebook.prototype.paste_cell_below = function () {
1238 if (this.clipboard !== null && this.paste_enabled) {
1238 if (this.clipboard !== null && this.paste_enabled) {
1239 var cell_data = this.clipboard;
1239 var cell_data = this.clipboard;
1240 var new_cell = this.insert_cell_below(cell_data.cell_type);
1240 var new_cell = this.insert_cell_below(cell_data.cell_type);
1241 new_cell.fromJSON(cell_data);
1241 new_cell.fromJSON(cell_data);
1242 new_cell.focus_cell();
1242 new_cell.focus_cell();
1243 }
1243 }
1244 };
1244 };
1245
1245
1246 // Split/merge
1246 // Split/merge
1247
1247
1248 /**
1248 /**
1249 * Split the selected cell into two, at the cursor.
1249 * Split the selected cell into two, at the cursor.
1250 *
1250 *
1251 * @method split_cell
1251 * @method split_cell
1252 */
1252 */
1253 Notebook.prototype.split_cell = function () {
1253 Notebook.prototype.split_cell = function () {
1254 var cell = this.get_selected_cell();
1254 var cell = this.get_selected_cell();
1255 if (cell.is_splittable()) {
1255 if (cell.is_splittable()) {
1256 var texta = cell.get_pre_cursor();
1256 var texta = cell.get_pre_cursor();
1257 var textb = cell.get_post_cursor();
1257 var textb = cell.get_post_cursor();
1258 cell.set_text(textb);
1258 cell.set_text(textb);
1259 var new_cell = this.insert_cell_above(cell.cell_type);
1259 var new_cell = this.insert_cell_above(cell.cell_type);
1260 // Unrender the new cell so we can call set_text.
1260 // Unrender the new cell so we can call set_text.
1261 new_cell.unrender();
1261 new_cell.unrender();
1262 new_cell.set_text(texta);
1262 new_cell.set_text(texta);
1263 }
1263 }
1264 };
1264 };
1265
1265
1266 /**
1266 /**
1267 * Combine the selected cell into the cell above it.
1267 * Combine the selected cell into the cell above it.
1268 *
1268 *
1269 * @method merge_cell_above
1269 * @method merge_cell_above
1270 */
1270 */
1271 Notebook.prototype.merge_cell_above = function () {
1271 Notebook.prototype.merge_cell_above = function () {
1272 var index = this.get_selected_index();
1272 var index = this.get_selected_index();
1273 var cell = this.get_cell(index);
1273 var cell = this.get_cell(index);
1274 var render = cell.rendered;
1274 var render = cell.rendered;
1275 if (!cell.is_mergeable()) {
1275 if (!cell.is_mergeable()) {
1276 return;
1276 return;
1277 }
1277 }
1278 if (index > 0) {
1278 if (index > 0) {
1279 var upper_cell = this.get_cell(index-1);
1279 var upper_cell = this.get_cell(index-1);
1280 if (!upper_cell.is_mergeable()) {
1280 if (!upper_cell.is_mergeable()) {
1281 return;
1281 return;
1282 }
1282 }
1283 var upper_text = upper_cell.get_text();
1283 var upper_text = upper_cell.get_text();
1284 var text = cell.get_text();
1284 var text = cell.get_text();
1285 if (cell instanceof codecell.CodeCell) {
1285 if (cell instanceof codecell.CodeCell) {
1286 cell.set_text(upper_text+'\n'+text);
1286 cell.set_text(upper_text+'\n'+text);
1287 } else {
1287 } else {
1288 cell.unrender(); // Must unrender before we set_text.
1288 cell.unrender(); // Must unrender before we set_text.
1289 cell.set_text(upper_text+'\n\n'+text);
1289 cell.set_text(upper_text+'\n\n'+text);
1290 if (render) {
1290 if (render) {
1291 // The rendered state of the final cell should match
1291 // The rendered state of the final cell should match
1292 // that of the original selected cell;
1292 // that of the original selected cell;
1293 cell.render();
1293 cell.render();
1294 }
1294 }
1295 }
1295 }
1296 this.delete_cell(index-1);
1296 this.delete_cell(index-1);
1297 this.select(this.find_cell_index(cell));
1297 this.select(this.find_cell_index(cell));
1298 }
1298 }
1299 };
1299 };
1300
1300
1301 /**
1301 /**
1302 * Combine the selected cell into the cell below it.
1302 * Combine the selected cell into the cell below it.
1303 *
1303 *
1304 * @method merge_cell_below
1304 * @method merge_cell_below
1305 */
1305 */
1306 Notebook.prototype.merge_cell_below = function () {
1306 Notebook.prototype.merge_cell_below = function () {
1307 var index = this.get_selected_index();
1307 var index = this.get_selected_index();
1308 var cell = this.get_cell(index);
1308 var cell = this.get_cell(index);
1309 var render = cell.rendered;
1309 var render = cell.rendered;
1310 if (!cell.is_mergeable()) {
1310 if (!cell.is_mergeable()) {
1311 return;
1311 return;
1312 }
1312 }
1313 if (index < this.ncells()-1) {
1313 if (index < this.ncells()-1) {
1314 var lower_cell = this.get_cell(index+1);
1314 var lower_cell = this.get_cell(index+1);
1315 if (!lower_cell.is_mergeable()) {
1315 if (!lower_cell.is_mergeable()) {
1316 return;
1316 return;
1317 }
1317 }
1318 var lower_text = lower_cell.get_text();
1318 var lower_text = lower_cell.get_text();
1319 var text = cell.get_text();
1319 var text = cell.get_text();
1320 if (cell instanceof codecell.CodeCell) {
1320 if (cell instanceof codecell.CodeCell) {
1321 cell.set_text(text+'\n'+lower_text);
1321 cell.set_text(text+'\n'+lower_text);
1322 } else {
1322 } else {
1323 cell.unrender(); // Must unrender before we set_text.
1323 cell.unrender(); // Must unrender before we set_text.
1324 cell.set_text(text+'\n\n'+lower_text);
1324 cell.set_text(text+'\n\n'+lower_text);
1325 if (render) {
1325 if (render) {
1326 // The rendered state of the final cell should match
1326 // The rendered state of the final cell should match
1327 // that of the original selected cell;
1327 // that of the original selected cell;
1328 cell.render();
1328 cell.render();
1329 }
1329 }
1330 }
1330 }
1331 this.delete_cell(index+1);
1331 this.delete_cell(index+1);
1332 this.select(this.find_cell_index(cell));
1332 this.select(this.find_cell_index(cell));
1333 }
1333 }
1334 };
1334 };
1335
1335
1336
1336
1337 // Cell collapsing and output clearing
1337 // Cell collapsing and output clearing
1338
1338
1339 /**
1339 /**
1340 * Hide a cell's output.
1340 * Hide a cell's output.
1341 *
1341 *
1342 * @method collapse_output
1342 * @method collapse_output
1343 * @param {Number} index A cell's numeric index
1343 * @param {Number} index A cell's numeric index
1344 */
1344 */
1345 Notebook.prototype.collapse_output = function (index) {
1345 Notebook.prototype.collapse_output = function (index) {
1346 var i = this.index_or_selected(index);
1346 var i = this.index_or_selected(index);
1347 var cell = this.get_cell(i);
1347 var cell = this.get_cell(i);
1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1349 cell.collapse_output();
1349 cell.collapse_output();
1350 this.set_dirty(true);
1350 this.set_dirty(true);
1351 }
1351 }
1352 };
1352 };
1353
1353
1354 /**
1354 /**
1355 * Hide each code cell's output area.
1355 * Hide each code cell's output area.
1356 *
1356 *
1357 * @method collapse_all_output
1357 * @method collapse_all_output
1358 */
1358 */
1359 Notebook.prototype.collapse_all_output = function () {
1359 Notebook.prototype.collapse_all_output = function () {
1360 this.get_cells().map(function (cell, i) {
1360 this.get_cells().map(function (cell, i) {
1361 if (cell instanceof codecell.CodeCell) {
1361 if (cell instanceof codecell.CodeCell) {
1362 cell.collapse_output();
1362 cell.collapse_output();
1363 }
1363 }
1364 });
1364 });
1365 // this should not be set if the `collapse` key is removed from nbformat
1365 // this should not be set if the `collapse` key is removed from nbformat
1366 this.set_dirty(true);
1366 this.set_dirty(true);
1367 };
1367 };
1368
1368
1369 /**
1369 /**
1370 * Show a cell's output.
1370 * Show a cell's output.
1371 *
1371 *
1372 * @method expand_output
1372 * @method expand_output
1373 * @param {Number} index A cell's numeric index
1373 * @param {Number} index A cell's numeric index
1374 */
1374 */
1375 Notebook.prototype.expand_output = function (index) {
1375 Notebook.prototype.expand_output = function (index) {
1376 var i = this.index_or_selected(index);
1376 var i = this.index_or_selected(index);
1377 var cell = this.get_cell(i);
1377 var cell = this.get_cell(i);
1378 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1378 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1379 cell.expand_output();
1379 cell.expand_output();
1380 this.set_dirty(true);
1380 this.set_dirty(true);
1381 }
1381 }
1382 };
1382 };
1383
1383
1384 /**
1384 /**
1385 * Expand each code cell's output area, and remove scrollbars.
1385 * Expand each code cell's output area, and remove scrollbars.
1386 *
1386 *
1387 * @method expand_all_output
1387 * @method expand_all_output
1388 */
1388 */
1389 Notebook.prototype.expand_all_output = function () {
1389 Notebook.prototype.expand_all_output = function () {
1390 this.get_cells().map(function (cell, i) {
1390 this.get_cells().map(function (cell, i) {
1391 if (cell instanceof codecell.CodeCell) {
1391 if (cell instanceof codecell.CodeCell) {
1392 cell.expand_output();
1392 cell.expand_output();
1393 }
1393 }
1394 });
1394 });
1395 // this should not be set if the `collapse` key is removed from nbformat
1395 // this should not be set if the `collapse` key is removed from nbformat
1396 this.set_dirty(true);
1396 this.set_dirty(true);
1397 };
1397 };
1398
1398
1399 /**
1399 /**
1400 * Clear the selected CodeCell's output area.
1400 * Clear the selected CodeCell's output area.
1401 *
1401 *
1402 * @method clear_output
1402 * @method clear_output
1403 * @param {Number} index A cell's numeric index
1403 * @param {Number} index A cell's numeric index
1404 */
1404 */
1405 Notebook.prototype.clear_output = function (index) {
1405 Notebook.prototype.clear_output = function (index) {
1406 var i = this.index_or_selected(index);
1406 var i = this.index_or_selected(index);
1407 var cell = this.get_cell(i);
1407 var cell = this.get_cell(i);
1408 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1408 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1409 cell.clear_output();
1409 cell.clear_output();
1410 this.set_dirty(true);
1410 this.set_dirty(true);
1411 }
1411 }
1412 };
1412 };
1413
1413
1414 /**
1414 /**
1415 * Clear each code cell's output area.
1415 * Clear each code cell's output area.
1416 *
1416 *
1417 * @method clear_all_output
1417 * @method clear_all_output
1418 */
1418 */
1419 Notebook.prototype.clear_all_output = function () {
1419 Notebook.prototype.clear_all_output = function () {
1420 this.get_cells().map(function (cell, i) {
1420 this.get_cells().map(function (cell, i) {
1421 if (cell instanceof codecell.CodeCell) {
1421 if (cell instanceof codecell.CodeCell) {
1422 cell.clear_output();
1422 cell.clear_output();
1423 }
1423 }
1424 });
1424 });
1425 this.set_dirty(true);
1425 this.set_dirty(true);
1426 };
1426 };
1427
1427
1428 /**
1428 /**
1429 * Scroll the selected CodeCell's output area.
1429 * Scroll the selected CodeCell's output area.
1430 *
1430 *
1431 * @method scroll_output
1431 * @method scroll_output
1432 * @param {Number} index A cell's numeric index
1432 * @param {Number} index A cell's numeric index
1433 */
1433 */
1434 Notebook.prototype.scroll_output = function (index) {
1434 Notebook.prototype.scroll_output = function (index) {
1435 var i = this.index_or_selected(index);
1435 var i = this.index_or_selected(index);
1436 var cell = this.get_cell(i);
1436 var cell = this.get_cell(i);
1437 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1437 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1438 cell.scroll_output();
1438 cell.scroll_output();
1439 this.set_dirty(true);
1439 this.set_dirty(true);
1440 }
1440 }
1441 };
1441 };
1442
1442
1443 /**
1443 /**
1444 * Expand each code cell's output area, and add a scrollbar for long output.
1444 * Expand each code cell's output area, and add a scrollbar for long output.
1445 *
1445 *
1446 * @method scroll_all_output
1446 * @method scroll_all_output
1447 */
1447 */
1448 Notebook.prototype.scroll_all_output = function () {
1448 Notebook.prototype.scroll_all_output = function () {
1449 this.get_cells().map(function (cell, i) {
1449 this.get_cells().map(function (cell, i) {
1450 if (cell instanceof codecell.CodeCell) {
1450 if (cell instanceof codecell.CodeCell) {
1451 cell.scroll_output();
1451 cell.scroll_output();
1452 }
1452 }
1453 });
1453 });
1454 // this should not be set if the `collapse` key is removed from nbformat
1454 // this should not be set if the `collapse` key is removed from nbformat
1455 this.set_dirty(true);
1455 this.set_dirty(true);
1456 };
1456 };
1457
1457
1458 /** Toggle whether a cell's output is collapsed or expanded.
1458 /** Toggle whether a cell's output is collapsed or expanded.
1459 *
1459 *
1460 * @method toggle_output
1460 * @method toggle_output
1461 * @param {Number} index A cell's numeric index
1461 * @param {Number} index A cell's numeric index
1462 */
1462 */
1463 Notebook.prototype.toggle_output = function (index) {
1463 Notebook.prototype.toggle_output = function (index) {
1464 var i = this.index_or_selected(index);
1464 var i = this.index_or_selected(index);
1465 var cell = this.get_cell(i);
1465 var cell = this.get_cell(i);
1466 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1466 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1467 cell.toggle_output();
1467 cell.toggle_output();
1468 this.set_dirty(true);
1468 this.set_dirty(true);
1469 }
1469 }
1470 };
1470 };
1471
1471
1472 /**
1472 /**
1473 * Hide/show the output of all cells.
1473 * Hide/show the output of all cells.
1474 *
1474 *
1475 * @method toggle_all_output
1475 * @method toggle_all_output
1476 */
1476 */
1477 Notebook.prototype.toggle_all_output = function () {
1477 Notebook.prototype.toggle_all_output = function () {
1478 this.get_cells().map(function (cell, i) {
1478 this.get_cells().map(function (cell, i) {
1479 if (cell instanceof codecell.CodeCell) {
1479 if (cell instanceof codecell.CodeCell) {
1480 cell.toggle_output();
1480 cell.toggle_output();
1481 }
1481 }
1482 });
1482 });
1483 // this should not be set if the `collapse` key is removed from nbformat
1483 // this should not be set if the `collapse` key is removed from nbformat
1484 this.set_dirty(true);
1484 this.set_dirty(true);
1485 };
1485 };
1486
1486
1487 /**
1487 /**
1488 * Toggle a scrollbar for long cell outputs.
1488 * Toggle a scrollbar for long cell outputs.
1489 *
1489 *
1490 * @method toggle_output_scroll
1490 * @method toggle_output_scroll
1491 * @param {Number} index A cell's numeric index
1491 * @param {Number} index A cell's numeric index
1492 */
1492 */
1493 Notebook.prototype.toggle_output_scroll = function (index) {
1493 Notebook.prototype.toggle_output_scroll = function (index) {
1494 var i = this.index_or_selected(index);
1494 var i = this.index_or_selected(index);
1495 var cell = this.get_cell(i);
1495 var cell = this.get_cell(i);
1496 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1496 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1497 cell.toggle_output_scroll();
1497 cell.toggle_output_scroll();
1498 this.set_dirty(true);
1498 this.set_dirty(true);
1499 }
1499 }
1500 };
1500 };
1501
1501
1502 /**
1502 /**
1503 * Toggle the scrolling of long output on all cells.
1503 * Toggle the scrolling of long output on all cells.
1504 *
1504 *
1505 * @method toggle_all_output_scrolling
1505 * @method toggle_all_output_scrolling
1506 */
1506 */
1507 Notebook.prototype.toggle_all_output_scroll = function () {
1507 Notebook.prototype.toggle_all_output_scroll = function () {
1508 this.get_cells().map(function (cell, i) {
1508 this.get_cells().map(function (cell, i) {
1509 if (cell instanceof codecell.CodeCell) {
1509 if (cell instanceof codecell.CodeCell) {
1510 cell.toggle_output_scroll();
1510 cell.toggle_output_scroll();
1511 }
1511 }
1512 });
1512 });
1513 // this should not be set if the `collapse` key is removed from nbformat
1513 // this should not be set if the `collapse` key is removed from nbformat
1514 this.set_dirty(true);
1514 this.set_dirty(true);
1515 };
1515 };
1516
1516
1517 // Other cell functions: line numbers, ...
1517 // Other cell functions: line numbers, ...
1518
1518
1519 /**
1519 /**
1520 * Toggle line numbers in the selected cell's input area.
1520 * Toggle line numbers in the selected cell's input area.
1521 *
1521 *
1522 * @method cell_toggle_line_numbers
1522 * @method cell_toggle_line_numbers
1523 */
1523 */
1524 Notebook.prototype.cell_toggle_line_numbers = function() {
1524 Notebook.prototype.cell_toggle_line_numbers = function() {
1525 this.get_selected_cell().toggle_line_numbers();
1525 this.get_selected_cell().toggle_line_numbers();
1526 };
1526 };
1527
1527
1528 /**
1528 /**
1529 * Set the codemirror mode for all code cells, including the default for
1529 * Set the codemirror mode for all code cells, including the default for
1530 * new code cells.
1530 * new code cells.
1531 *
1531 *
1532 * @method set_codemirror_mode
1532 * @method set_codemirror_mode
1533 */
1533 */
1534 Notebook.prototype.set_codemirror_mode = function(newmode){
1534 Notebook.prototype.set_codemirror_mode = function(newmode){
1535 if (newmode === this.codemirror_mode) {
1535 if (newmode === this.codemirror_mode) {
1536 return;
1536 return;
1537 }
1537 }
1538 this.codemirror_mode = newmode;
1538 this.codemirror_mode = newmode;
1539 codecell.CodeCell.options_default.cm_config.mode = newmode;
1539 codecell.CodeCell.options_default.cm_config.mode = newmode;
1540 var modename = newmode.mode || newmode.name || newmode;
1540 var modename = newmode.mode || newmode.name || newmode;
1541
1541
1542 var that = this;
1542 var that = this;
1543 utils.requireCodeMirrorMode(modename, function () {
1543 utils.requireCodeMirrorMode(modename, function () {
1544 that.get_cells().map(function(cell, i) {
1544 that.get_cells().map(function(cell, i) {
1545 if (cell.cell_type === 'code'){
1545 if (cell.cell_type === 'code'){
1546 cell.code_mirror.setOption('mode', newmode);
1546 cell.code_mirror.setOption('mode', newmode);
1547 // This is currently redundant, because cm_config ends up as
1547 // This is currently redundant, because cm_config ends up as
1548 // codemirror's own .options object, but I don't want to
1548 // codemirror's own .options object, but I don't want to
1549 // rely on that.
1549 // rely on that.
1550 cell.cm_config.mode = newmode;
1550 cell.cm_config.mode = newmode;
1551 }
1551 }
1552 });
1552 });
1553 });
1553 });
1554 };
1554 };
1555
1555
1556 // Session related things
1556 // Session related things
1557
1557
1558 /**
1558 /**
1559 * Start a new session and set it on each code cell.
1559 * Start a new session and set it on each code cell.
1560 *
1560 *
1561 * @method start_session
1561 * @method start_session
1562 */
1562 */
1563 Notebook.prototype.start_session = function (kernel_name) {
1563 Notebook.prototype.start_session = function (kernel_name) {
1564 if (this._session_starting) {
1564 if (this._session_starting) {
1565 throw new session.SessionAlreadyStarting();
1565 throw new session.SessionAlreadyStarting();
1566 }
1566 }
1567 this._session_starting = true;
1567 this._session_starting = true;
1568
1568
1569 var options = {
1569 var options = {
1570 base_url: this.base_url,
1570 base_url: this.base_url,
1571 ws_url: this.ws_url,
1571 ws_url: this.ws_url,
1572 notebook_path: this.notebook_path,
1572 notebook_path: this.notebook_path,
1573 notebook_name: this.notebook_name,
1573 notebook_name: this.notebook_name,
1574 kernel_name: kernel_name,
1574 kernel_name: kernel_name,
1575 notebook: this
1575 notebook: this
1576 };
1576 };
1577
1577
1578 var success = $.proxy(this._session_started, this);
1578 var success = $.proxy(this._session_started, this);
1579 var failure = $.proxy(this._session_start_failed, this);
1579 var failure = $.proxy(this._session_start_failed, this);
1580
1580
1581 if (this.session !== null) {
1581 if (this.session !== null) {
1582 this.session.restart(options, success, failure);
1582 this.session.restart(options, success, failure);
1583 } else {
1583 } else {
1584 this.session = new session.Session(options);
1584 this.session = new session.Session(options);
1585 this.session.start(success, failure);
1585 this.session.start(success, failure);
1586 }
1586 }
1587 };
1587 };
1588
1588
1589
1589
1590 /**
1590 /**
1591 * Once a session is started, link the code cells to the kernel and pass the
1591 * Once a session is started, link the code cells to the kernel and pass the
1592 * comm manager to the widget manager
1592 * comm manager to the widget manager
1593 *
1593 *
1594 */
1594 */
1595 Notebook.prototype._session_started = function (){
1595 Notebook.prototype._session_started = function (){
1596 this._session_starting = false;
1596 this._session_starting = false;
1597 this.kernel = this.session.kernel;
1597 this.kernel = this.session.kernel;
1598 var ncells = this.ncells();
1598 var ncells = this.ncells();
1599 for (var i=0; i<ncells; i++) {
1599 for (var i=0; i<ncells; i++) {
1600 var cell = this.get_cell(i);
1600 var cell = this.get_cell(i);
1601 if (cell instanceof codecell.CodeCell) {
1601 if (cell instanceof codecell.CodeCell) {
1602 cell.set_kernel(this.session.kernel);
1602 cell.set_kernel(this.session.kernel);
1603 }
1603 }
1604 }
1604 }
1605 };
1605 };
1606 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1606 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1607 this._session_starting = false;
1607 this._session_starting = false;
1608 utils.log_ajax_error(jqxhr, status, error);
1608 utils.log_ajax_error(jqxhr, status, error);
1609 };
1609 };
1610
1610
1611 /**
1611 /**
1612 * Prompt the user to restart the IPython kernel.
1612 * Prompt the user to restart the IPython kernel.
1613 *
1613 *
1614 * @method restart_kernel
1614 * @method restart_kernel
1615 */
1615 */
1616 Notebook.prototype.restart_kernel = function () {
1616 Notebook.prototype.restart_kernel = function () {
1617 var that = this;
1617 var that = this;
1618 dialog.modal({
1618 dialog.modal({
1619 notebook: this,
1619 notebook: this,
1620 keyboard_manager: this.keyboard_manager,
1620 keyboard_manager: this.keyboard_manager,
1621 title : "Restart kernel or continue running?",
1621 title : "Restart kernel or continue running?",
1622 body : $("<p/>").text(
1622 body : $("<p/>").text(
1623 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1623 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1624 ),
1624 ),
1625 buttons : {
1625 buttons : {
1626 "Continue running" : {},
1626 "Continue running" : {},
1627 "Restart" : {
1627 "Restart" : {
1628 "class" : "btn-danger",
1628 "class" : "btn-danger",
1629 "click" : function() {
1629 "click" : function() {
1630 that.kernel.restart();
1630 that.kernel.restart();
1631 }
1631 }
1632 }
1632 }
1633 }
1633 }
1634 });
1634 });
1635 };
1635 };
1636
1636
1637 /**
1637 /**
1638 * Execute or render cell outputs and go into command mode.
1638 * Execute or render cell outputs and go into command mode.
1639 *
1639 *
1640 * @method execute_cell
1640 * @method execute_cell
1641 */
1641 */
1642 Notebook.prototype.execute_cell = function () {
1642 Notebook.prototype.execute_cell = function () {
1643 // mode = shift, ctrl, alt
1643 // mode = shift, ctrl, alt
1644 var cell = this.get_selected_cell();
1644 var cell = this.get_selected_cell();
1645
1645
1646 cell.execute();
1646 cell.execute();
1647 this.command_mode();
1647 this.command_mode();
1648 this.set_dirty(true);
1648 this.set_dirty(true);
1649 };
1649 };
1650
1650
1651 /**
1651 /**
1652 * Execute or render cell outputs and insert a new cell below.
1652 * Execute or render cell outputs and insert a new cell below.
1653 *
1653 *
1654 * @method execute_cell_and_insert_below
1654 * @method execute_cell_and_insert_below
1655 */
1655 */
1656 Notebook.prototype.execute_cell_and_insert_below = function () {
1656 Notebook.prototype.execute_cell_and_insert_below = function () {
1657 var cell = this.get_selected_cell();
1657 var cell = this.get_selected_cell();
1658 var cell_index = this.find_cell_index(cell);
1658 var cell_index = this.find_cell_index(cell);
1659
1659
1660 cell.execute();
1660 cell.execute();
1661
1661
1662 // If we are at the end always insert a new cell and return
1662 // If we are at the end always insert a new cell and return
1663 if (cell_index === (this.ncells()-1)) {
1663 if (cell_index === (this.ncells()-1)) {
1664 this.command_mode();
1664 this.command_mode();
1665 this.insert_cell_below();
1665 this.insert_cell_below();
1666 this.select(cell_index+1);
1666 this.select(cell_index+1);
1667 this.edit_mode();
1667 this.edit_mode();
1668 this.scroll_to_bottom();
1668 this.scroll_to_bottom();
1669 this.set_dirty(true);
1669 this.set_dirty(true);
1670 return;
1670 return;
1671 }
1671 }
1672
1672
1673 this.command_mode();
1673 this.command_mode();
1674 this.insert_cell_below();
1674 this.insert_cell_below();
1675 this.select(cell_index+1);
1675 this.select(cell_index+1);
1676 this.edit_mode();
1676 this.edit_mode();
1677 this.set_dirty(true);
1677 this.set_dirty(true);
1678 };
1678 };
1679
1679
1680 /**
1680 /**
1681 * Execute or render cell outputs and select the next cell.
1681 * Execute or render cell outputs and select the next cell.
1682 *
1682 *
1683 * @method execute_cell_and_select_below
1683 * @method execute_cell_and_select_below
1684 */
1684 */
1685 Notebook.prototype.execute_cell_and_select_below = function () {
1685 Notebook.prototype.execute_cell_and_select_below = function () {
1686
1686
1687 var cell = this.get_selected_cell();
1687 var cell = this.get_selected_cell();
1688 var cell_index = this.find_cell_index(cell);
1688 var cell_index = this.find_cell_index(cell);
1689
1689
1690 cell.execute();
1690 cell.execute();
1691
1691
1692 // If we are at the end always insert a new cell and return
1692 // If we are at the end always insert a new cell and return
1693 if (cell_index === (this.ncells()-1)) {
1693 if (cell_index === (this.ncells()-1)) {
1694 this.command_mode();
1694 this.command_mode();
1695 this.insert_cell_below();
1695 this.insert_cell_below();
1696 this.select(cell_index+1);
1696 this.select(cell_index+1);
1697 this.edit_mode();
1697 this.edit_mode();
1698 this.scroll_to_bottom();
1698 this.scroll_to_bottom();
1699 this.set_dirty(true);
1699 this.set_dirty(true);
1700 return;
1700 return;
1701 }
1701 }
1702
1702
1703 this.command_mode();
1703 this.command_mode();
1704 this.select(cell_index+1);
1704 this.select(cell_index+1);
1705 this.focus_cell();
1705 this.focus_cell();
1706 this.set_dirty(true);
1706 this.set_dirty(true);
1707 };
1707 };
1708
1708
1709 /**
1709 /**
1710 * Execute all cells below the selected cell.
1710 * Execute all cells below the selected cell.
1711 *
1711 *
1712 * @method execute_cells_below
1712 * @method execute_cells_below
1713 */
1713 */
1714 Notebook.prototype.execute_cells_below = function () {
1714 Notebook.prototype.execute_cells_below = function () {
1715 this.execute_cell_range(this.get_selected_index(), this.ncells());
1715 this.execute_cell_range(this.get_selected_index(), this.ncells());
1716 this.scroll_to_bottom();
1716 this.scroll_to_bottom();
1717 };
1717 };
1718
1718
1719 /**
1719 /**
1720 * Execute all cells above the selected cell.
1720 * Execute all cells above the selected cell.
1721 *
1721 *
1722 * @method execute_cells_above
1722 * @method execute_cells_above
1723 */
1723 */
1724 Notebook.prototype.execute_cells_above = function () {
1724 Notebook.prototype.execute_cells_above = function () {
1725 this.execute_cell_range(0, this.get_selected_index());
1725 this.execute_cell_range(0, this.get_selected_index());
1726 };
1726 };
1727
1727
1728 /**
1728 /**
1729 * Execute all cells.
1729 * Execute all cells.
1730 *
1730 *
1731 * @method execute_all_cells
1731 * @method execute_all_cells
1732 */
1732 */
1733 Notebook.prototype.execute_all_cells = function () {
1733 Notebook.prototype.execute_all_cells = function () {
1734 this.execute_cell_range(0, this.ncells());
1734 this.execute_cell_range(0, this.ncells());
1735 this.scroll_to_bottom();
1735 this.scroll_to_bottom();
1736 };
1736 };
1737
1737
1738 /**
1738 /**
1739 * Execute a contiguous range of cells.
1739 * Execute a contiguous range of cells.
1740 *
1740 *
1741 * @method execute_cell_range
1741 * @method execute_cell_range
1742 * @param {Number} start Index of the first cell to execute (inclusive)
1742 * @param {Number} start Index of the first cell to execute (inclusive)
1743 * @param {Number} end Index of the last cell to execute (exclusive)
1743 * @param {Number} end Index of the last cell to execute (exclusive)
1744 */
1744 */
1745 Notebook.prototype.execute_cell_range = function (start, end) {
1745 Notebook.prototype.execute_cell_range = function (start, end) {
1746 this.command_mode();
1746 this.command_mode();
1747 for (var i=start; i<end; i++) {
1747 for (var i=start; i<end; i++) {
1748 this.select(i);
1748 this.select(i);
1749 this.execute_cell();
1749 this.execute_cell();
1750 }
1750 }
1751 };
1751 };
1752
1752
1753 // Persistance and loading
1753 // Persistance and loading
1754
1754
1755 /**
1755 /**
1756 * Getter method for this notebook's name.
1756 * Getter method for this notebook's name.
1757 *
1757 *
1758 * @method get_notebook_name
1758 * @method get_notebook_name
1759 * @return {String} This notebook's name (excluding file extension)
1759 * @return {String} This notebook's name (excluding file extension)
1760 */
1760 */
1761 Notebook.prototype.get_notebook_name = function () {
1761 Notebook.prototype.get_notebook_name = function () {
1762 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1762 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1763 return nbname;
1763 return nbname;
1764 };
1764 };
1765
1765
1766 /**
1766 /**
1767 * Setter method for this notebook's name.
1767 * Setter method for this notebook's name.
1768 *
1768 *
1769 * @method set_notebook_name
1769 * @method set_notebook_name
1770 * @param {String} name A new name for this notebook
1770 * @param {String} name A new name for this notebook
1771 */
1771 */
1772 Notebook.prototype.set_notebook_name = function (name) {
1772 Notebook.prototype.set_notebook_name = function (name) {
1773 var parent = utils.url_path_split(this.notebook_path)[0];
1773 var parent = utils.url_path_split(this.notebook_path)[0];
1774 this.notebook_name = name;
1774 this.notebook_name = name;
1775 this.notebook_path = utils.url_path_join(parent, name);
1775 this.notebook_path = utils.url_path_join(parent, name);
1776 };
1776 };
1777
1777
1778 /**
1778 /**
1779 * Check that a notebook's name is valid.
1779 * Check that a notebook's name is valid.
1780 *
1780 *
1781 * @method test_notebook_name
1781 * @method test_notebook_name
1782 * @param {String} nbname A name for this notebook
1782 * @param {String} nbname A name for this notebook
1783 * @return {Boolean} True if the name is valid, false if invalid
1783 * @return {Boolean} True if the name is valid, false if invalid
1784 */
1784 */
1785 Notebook.prototype.test_notebook_name = function (nbname) {
1785 Notebook.prototype.test_notebook_name = function (nbname) {
1786 nbname = nbname || '';
1786 nbname = nbname || '';
1787 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1787 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1788 return true;
1788 return true;
1789 } else {
1789 } else {
1790 return false;
1790 return false;
1791 }
1791 }
1792 };
1792 };
1793
1793
1794 /**
1794 /**
1795 * Load a notebook from JSON (.ipynb).
1795 * Load a notebook from JSON (.ipynb).
1796 *
1796 *
1797 * @method fromJSON
1797 * @method fromJSON
1798 * @param {Object} data JSON representation of a notebook
1798 * @param {Object} data JSON representation of a notebook
1799 */
1799 */
1800 Notebook.prototype.fromJSON = function (data) {
1800 Notebook.prototype.fromJSON = function (data) {
1801
1801
1802 var content = data.content;
1802 var content = data.content;
1803 var ncells = this.ncells();
1803 var ncells = this.ncells();
1804 var i;
1804 var i;
1805 for (i=0; i<ncells; i++) {
1805 for (i=0; i<ncells; i++) {
1806 // Always delete cell 0 as they get renumbered as they are deleted.
1806 // Always delete cell 0 as they get renumbered as they are deleted.
1807 this.delete_cell(0);
1807 this.delete_cell(0);
1808 }
1808 }
1809 // Save the metadata and name.
1809 // Save the metadata and name.
1810 this.metadata = content.metadata;
1810 this.metadata = content.metadata;
1811 this.notebook_name = data.name;
1811 this.notebook_name = data.name;
1812 this.notebook_path = data.path;
1812 this.notebook_path = data.path;
1813 var trusted = true;
1813 var trusted = true;
1814
1814
1815 // Trigger an event changing the kernel spec - this will set the default
1815 // Trigger an event changing the kernel spec - this will set the default
1816 // codemirror mode
1816 // codemirror mode
1817 if (this.metadata.kernelspec !== undefined) {
1817 if (this.metadata.kernelspec !== undefined) {
1818 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1818 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1819 }
1819 }
1820
1820
1821 // Set the codemirror mode from language_info metadata
1821 // Set the codemirror mode from language_info metadata
1822 if (this.metadata.language_info !== undefined) {
1822 if (this.metadata.language_info !== undefined) {
1823 var langinfo = this.metadata.language_info;
1823 var langinfo = this.metadata.language_info;
1824 // Mode 'null' should be plain, unhighlighted text.
1824 // Mode 'null' should be plain, unhighlighted text.
1825 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1825 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1826 this.set_codemirror_mode(cm_mode);
1826 this.set_codemirror_mode(cm_mode);
1827 }
1827 }
1828
1828
1829 var new_cells = content.cells;
1829 var new_cells = content.cells;
1830 ncells = new_cells.length;
1830 ncells = new_cells.length;
1831 var cell_data = null;
1831 var cell_data = null;
1832 var new_cell = null;
1832 var new_cell = null;
1833 for (i=0; i<ncells; i++) {
1833 for (i=0; i<ncells; i++) {
1834 cell_data = new_cells[i];
1834 cell_data = new_cells[i];
1835 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1835 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1836 new_cell.fromJSON(cell_data);
1836 new_cell.fromJSON(cell_data);
1837 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1837 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1838 trusted = false;
1838 trusted = false;
1839 }
1839 }
1840 }
1840 }
1841 if (trusted !== this.trusted) {
1841 if (trusted !== this.trusted) {
1842 this.trusted = trusted;
1842 this.trusted = trusted;
1843 this.events.trigger("trust_changed.Notebook", trusted);
1843 this.events.trigger("trust_changed.Notebook", trusted);
1844 }
1844 }
1845 };
1845 };
1846
1846
1847 /**
1847 /**
1848 * Dump this notebook into a JSON-friendly object.
1848 * Dump this notebook into a JSON-friendly object.
1849 *
1849 *
1850 * @method toJSON
1850 * @method toJSON
1851 * @return {Object} A JSON-friendly representation of this notebook.
1851 * @return {Object} A JSON-friendly representation of this notebook.
1852 */
1852 */
1853 Notebook.prototype.toJSON = function () {
1853 Notebook.prototype.toJSON = function () {
1854 // remove the conversion indicator, which only belongs in-memory
1854 // remove the conversion indicator, which only belongs in-memory
1855 delete this.metadata.orig_nbformat;
1855 delete this.metadata.orig_nbformat;
1856 delete this.metadata.orig_nbformat_minor;
1856 delete this.metadata.orig_nbformat_minor;
1857
1857
1858 var cells = this.get_cells();
1858 var cells = this.get_cells();
1859 var ncells = cells.length;
1859 var ncells = cells.length;
1860 var cell_array = new Array(ncells);
1860 var cell_array = new Array(ncells);
1861 var trusted = true;
1861 var trusted = true;
1862 for (var i=0; i<ncells; i++) {
1862 for (var i=0; i<ncells; i++) {
1863 var cell = cells[i];
1863 var cell = cells[i];
1864 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1864 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1865 trusted = false;
1865 trusted = false;
1866 }
1866 }
1867 cell_array[i] = cell.toJSON();
1867 cell_array[i] = cell.toJSON();
1868 }
1868 }
1869 var data = {
1869 var data = {
1870 cells: cell_array,
1870 cells: cell_array,
1871 metadata: this.metadata,
1871 metadata: this.metadata,
1872 nbformat: this.nbformat,
1872 nbformat: this.nbformat,
1873 nbformat_minor: this.nbformat_minor
1873 nbformat_minor: this.nbformat_minor
1874 };
1874 };
1875 if (trusted != this.trusted) {
1875 if (trusted != this.trusted) {
1876 this.trusted = trusted;
1876 this.trusted = trusted;
1877 this.events.trigger("trust_changed.Notebook", trusted);
1877 this.events.trigger("trust_changed.Notebook", trusted);
1878 }
1878 }
1879 return data;
1879 return data;
1880 };
1880 };
1881
1881
1882 /**
1882 /**
1883 * Start an autosave timer, for periodically saving the notebook.
1883 * Start an autosave timer, for periodically saving the notebook.
1884 *
1884 *
1885 * @method set_autosave_interval
1885 * @method set_autosave_interval
1886 * @param {Integer} interval the autosave interval in milliseconds
1886 * @param {Integer} interval the autosave interval in milliseconds
1887 */
1887 */
1888 Notebook.prototype.set_autosave_interval = function (interval) {
1888 Notebook.prototype.set_autosave_interval = function (interval) {
1889 var that = this;
1889 var that = this;
1890 // clear previous interval, so we don't get simultaneous timers
1890 // clear previous interval, so we don't get simultaneous timers
1891 if (this.autosave_timer) {
1891 if (this.autosave_timer) {
1892 clearInterval(this.autosave_timer);
1892 clearInterval(this.autosave_timer);
1893 }
1893 }
1894
1894
1895 this.autosave_interval = this.minimum_autosave_interval = interval;
1895 this.autosave_interval = this.minimum_autosave_interval = interval;
1896 if (interval) {
1896 if (interval) {
1897 this.autosave_timer = setInterval(function() {
1897 this.autosave_timer = setInterval(function() {
1898 if (that.dirty) {
1898 if (that.dirty) {
1899 that.save_notebook();
1899 that.save_notebook();
1900 }
1900 }
1901 }, interval);
1901 }, interval);
1902 this.events.trigger("autosave_enabled.Notebook", interval);
1902 this.events.trigger("autosave_enabled.Notebook", interval);
1903 } else {
1903 } else {
1904 this.autosave_timer = null;
1904 this.autosave_timer = null;
1905 this.events.trigger("autosave_disabled.Notebook");
1905 this.events.trigger("autosave_disabled.Notebook");
1906 }
1906 }
1907 };
1907 };
1908
1908
1909 /**
1909 /**
1910 * Save this notebook on the server. This becomes a notebook instance's
1910 * Save this notebook on the server. This becomes a notebook instance's
1911 * .save_notebook method *after* the entire notebook has been loaded.
1911 * .save_notebook method *after* the entire notebook has been loaded.
1912 *
1912 *
1913 * @method save_notebook
1913 * @method save_notebook
1914 */
1914 */
1915 Notebook.prototype.save_notebook = function () {
1915 Notebook.prototype.save_notebook = function () {
1916 // Create a JSON model to be sent to the server.
1916 // Create a JSON model to be sent to the server.
1917 var model = {
1917 var model = {
1918 type : "notebook",
1918 type : "notebook",
1919 content : this.toJSON()
1919 content : this.toJSON()
1920 };
1920 };
1921 // time the ajax call for autosave tuning purposes.
1921 // time the ajax call for autosave tuning purposes.
1922 var start = new Date().getTime();
1922 var start = new Date().getTime();
1923
1923
1924 var that = this;
1924 var that = this;
1925 this.contents.save(this.notebook_path, model).then(
1925 return this.contents.save(this.notebook_path, model).then(
1926 $.proxy(this.save_notebook_success, this, start),
1926 $.proxy(this.save_notebook_success, this, start),
1927 function (error) {
1927 function (error) {
1928 that.events.trigger('notebook_save_failed.Notebook', error);
1928 that.events.trigger('notebook_save_failed.Notebook', error);
1929 }
1929 }
1930 );
1930 );
1931 };
1931 };
1932
1932
1933 /**
1933 /**
1934 * Success callback for saving a notebook.
1934 * Success callback for saving a notebook.
1935 *
1935 *
1936 * @method save_notebook_success
1936 * @method save_notebook_success
1937 * @param {Integer} start Time when the save request start
1937 * @param {Integer} start Time when the save request start
1938 * @param {Object} data JSON representation of a notebook
1938 * @param {Object} data JSON representation of a notebook
1939 */
1939 */
1940 Notebook.prototype.save_notebook_success = function (start, data) {
1940 Notebook.prototype.save_notebook_success = function (start, data) {
1941 this.set_dirty(false);
1941 this.set_dirty(false);
1942 if (data.message) {
1942 if (data.message) {
1943 // save succeeded, but validation failed.
1943 // save succeeded, but validation failed.
1944 var body = $("<div>");
1944 var body = $("<div>");
1945 var title = "Notebook validation failed";
1945 var title = "Notebook validation failed";
1946
1946
1947 body.append($("<p>").text(
1947 body.append($("<p>").text(
1948 "The save operation succeeded," +
1948 "The save operation succeeded," +
1949 " but the notebook does not appear to be valid." +
1949 " but the notebook does not appear to be valid." +
1950 " The validation error was:"
1950 " The validation error was:"
1951 )).append($("<div>").addClass("validation-error").append(
1951 )).append($("<div>").addClass("validation-error").append(
1952 $("<pre>").text(data.message)
1952 $("<pre>").text(data.message)
1953 ));
1953 ));
1954 dialog.modal({
1954 dialog.modal({
1955 notebook: this,
1955 notebook: this,
1956 keyboard_manager: this.keyboard_manager,
1956 keyboard_manager: this.keyboard_manager,
1957 title: title,
1957 title: title,
1958 body: body,
1958 body: body,
1959 buttons : {
1959 buttons : {
1960 OK : {
1960 OK : {
1961 "class" : "btn-primary"
1961 "class" : "btn-primary"
1962 }
1962 }
1963 }
1963 }
1964 });
1964 });
1965 }
1965 }
1966 this.events.trigger('notebook_saved.Notebook');
1966 this.events.trigger('notebook_saved.Notebook');
1967 this._update_autosave_interval(start);
1967 this._update_autosave_interval(start);
1968 if (this._checkpoint_after_save) {
1968 if (this._checkpoint_after_save) {
1969 this.create_checkpoint();
1969 this.create_checkpoint();
1970 this._checkpoint_after_save = false;
1970 this._checkpoint_after_save = false;
1971 }
1971 }
1972 };
1972 };
1973
1973
1974 /**
1974 /**
1975 * update the autosave interval based on how long the last save took
1975 * update the autosave interval based on how long the last save took
1976 *
1976 *
1977 * @method _update_autosave_interval
1977 * @method _update_autosave_interval
1978 * @param {Integer} timestamp when the save request started
1978 * @param {Integer} timestamp when the save request started
1979 */
1979 */
1980 Notebook.prototype._update_autosave_interval = function (start) {
1980 Notebook.prototype._update_autosave_interval = function (start) {
1981 var duration = (new Date().getTime() - start);
1981 var duration = (new Date().getTime() - start);
1982 if (this.autosave_interval) {
1982 if (this.autosave_interval) {
1983 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1983 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1984 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1984 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1985 // round to 10 seconds, otherwise we will be setting a new interval too often
1985 // round to 10 seconds, otherwise we will be setting a new interval too often
1986 interval = 10000 * Math.round(interval / 10000);
1986 interval = 10000 * Math.round(interval / 10000);
1987 // set new interval, if it's changed
1987 // set new interval, if it's changed
1988 if (interval != this.autosave_interval) {
1988 if (interval != this.autosave_interval) {
1989 this.set_autosave_interval(interval);
1989 this.set_autosave_interval(interval);
1990 }
1990 }
1991 }
1991 }
1992 };
1992 };
1993
1993
1994 /**
1994 /**
1995 * Explicitly trust the output of this notebook.
1995 * Explicitly trust the output of this notebook.
1996 *
1996 *
1997 * @method trust_notebook
1997 * @method trust_notebook
1998 */
1998 */
1999 Notebook.prototype.trust_notebook = function () {
1999 Notebook.prototype.trust_notebook = function () {
2000 var body = $("<div>").append($("<p>")
2000 var body = $("<div>").append($("<p>")
2001 .text("A trusted IPython notebook may execute hidden malicious code ")
2001 .text("A trusted IPython notebook may execute hidden malicious code ")
2002 .append($("<strong>")
2002 .append($("<strong>")
2003 .append(
2003 .append(
2004 $("<em>").text("when you open it")
2004 $("<em>").text("when you open it")
2005 )
2005 )
2006 ).append(".").append(
2006 ).append(".").append(
2007 " Selecting trust will immediately reload this notebook in a trusted state."
2007 " Selecting trust will immediately reload this notebook in a trusted state."
2008 ).append(
2008 ).append(
2009 " For more information, see the "
2009 " For more information, see the "
2010 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2010 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2011 .text("IPython security documentation")
2011 .text("IPython security documentation")
2012 ).append(".")
2012 ).append(".")
2013 );
2013 );
2014
2014
2015 var nb = this;
2015 var nb = this;
2016 dialog.modal({
2016 dialog.modal({
2017 notebook: this,
2017 notebook: this,
2018 keyboard_manager: this.keyboard_manager,
2018 keyboard_manager: this.keyboard_manager,
2019 title: "Trust this notebook?",
2019 title: "Trust this notebook?",
2020 body: body,
2020 body: body,
2021
2021
2022 buttons: {
2022 buttons: {
2023 Cancel : {},
2023 Cancel : {},
2024 Trust : {
2024 Trust : {
2025 class : "btn-danger",
2025 class : "btn-danger",
2026 click : function () {
2026 click : function () {
2027 var cells = nb.get_cells();
2027 var cells = nb.get_cells();
2028 for (var i = 0; i < cells.length; i++) {
2028 for (var i = 0; i < cells.length; i++) {
2029 var cell = cells[i];
2029 var cell = cells[i];
2030 if (cell.cell_type == 'code') {
2030 if (cell.cell_type == 'code') {
2031 cell.output_area.trusted = true;
2031 cell.output_area.trusted = true;
2032 }
2032 }
2033 }
2033 }
2034 nb.events.on('notebook_saved.Notebook', function () {
2034 nb.events.on('notebook_saved.Notebook', function () {
2035 window.location.reload();
2035 window.location.reload();
2036 });
2036 });
2037 nb.save_notebook();
2037 nb.save_notebook();
2038 }
2038 }
2039 }
2039 }
2040 }
2040 }
2041 });
2041 });
2042 };
2042 };
2043
2043
2044 Notebook.prototype.copy_notebook = function(){
2044 Notebook.prototype.copy_notebook = function(){
2045 var base_url = this.base_url;
2045 var base_url = this.base_url;
2046 var w = window.open();
2046 var w = window.open();
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2048 this.contents.copy(this.notebook_path, parent).then(
2048 this.contents.copy(this.notebook_path, parent).then(
2049 function (data) {
2049 function (data) {
2050 w.location = utils.url_join_encode(
2050 w.location = utils.url_join_encode(
2051 base_url, 'notebooks', data.path
2051 base_url, 'notebooks', data.path
2052 );
2052 );
2053 },
2053 },
2054 function(error) {
2054 function(error) {
2055 w.close();
2055 w.close();
2056 console.log(error);
2056 console.log(error);
2057 }
2057 }
2058 );
2058 );
2059 };
2059 };
2060
2060
2061 Notebook.prototype.rename = function (new_name) {
2061 Notebook.prototype.rename = function (new_name) {
2062 if (!new_name.match(/\.ipynb$/)) {
2062 if (!new_name.match(/\.ipynb$/)) {
2063 new_name = new_name + ".ipynb";
2063 new_name = new_name + ".ipynb";
2064 }
2064 }
2065
2065
2066 var that = this;
2066 var that = this;
2067 var parent = utils.url_path_split(this.notebook_path)[0];
2067 var parent = utils.url_path_split(this.notebook_path)[0];
2068 var new_path = utils.url_path_join(parent, new_name);
2068 var new_path = utils.url_path_join(parent, new_name);
2069 this.contents.rename(this.notebook_path, new_path).then(
2069 this.contents.rename(this.notebook_path, new_path).then(
2070 function (json) {
2070 function (json) {
2071 that.notebook_name = json.name;
2071 that.notebook_name = json.name;
2072 that.notebook_path = json.path;
2072 that.notebook_path = json.path;
2073 that.session.rename_notebook(json.path);
2073 that.session.rename_notebook(json.path);
2074 that.events.trigger('notebook_renamed.Notebook', json);
2074 that.events.trigger('notebook_renamed.Notebook', json);
2075 },
2075 },
2076 $.proxy(this.rename_error, this)
2076 $.proxy(this.rename_error, this)
2077 );
2077 );
2078 };
2078 };
2079
2079
2080 Notebook.prototype.delete = function () {
2080 Notebook.prototype.delete = function () {
2081 this.contents.delete(this.notebook_path);
2081 this.contents.delete(this.notebook_path);
2082 };
2082 };
2083
2083
2084 Notebook.prototype.rename_error = function (error) {
2084 Notebook.prototype.rename_error = function (error) {
2085 var that = this;
2085 var that = this;
2086 var dialog_body = $('<div/>').append(
2086 var dialog_body = $('<div/>').append(
2087 $("<p/>").text('This notebook name already exists.')
2087 $("<p/>").text('This notebook name already exists.')
2088 );
2088 );
2089 this.events.trigger('notebook_rename_failed.Notebook', error);
2089 this.events.trigger('notebook_rename_failed.Notebook', error);
2090 dialog.modal({
2090 dialog.modal({
2091 notebook: this,
2091 notebook: this,
2092 keyboard_manager: this.keyboard_manager,
2092 keyboard_manager: this.keyboard_manager,
2093 title: "Notebook Rename Error!",
2093 title: "Notebook Rename Error!",
2094 body: dialog_body,
2094 body: dialog_body,
2095 buttons : {
2095 buttons : {
2096 "Cancel": {},
2096 "Cancel": {},
2097 "OK": {
2097 "OK": {
2098 class: "btn-primary",
2098 class: "btn-primary",
2099 click: function () {
2099 click: function () {
2100 that.save_widget.rename_notebook({notebook:that});
2100 that.save_widget.rename_notebook({notebook:that});
2101 }}
2101 }}
2102 },
2102 },
2103 open : function (event, ui) {
2103 open : function (event, ui) {
2104 var that = $(this);
2104 var that = $(this);
2105 // Upon ENTER, click the OK button.
2105 // Upon ENTER, click the OK button.
2106 that.find('input[type="text"]').keydown(function (event, ui) {
2106 that.find('input[type="text"]').keydown(function (event, ui) {
2107 if (event.which === this.keyboard.keycodes.enter) {
2107 if (event.which === this.keyboard.keycodes.enter) {
2108 that.find('.btn-primary').first().click();
2108 that.find('.btn-primary').first().click();
2109 }
2109 }
2110 });
2110 });
2111 that.find('input[type="text"]').focus();
2111 that.find('input[type="text"]').focus();
2112 }
2112 }
2113 });
2113 });
2114 };
2114 };
2115
2115
2116 /**
2116 /**
2117 * Request a notebook's data from the server.
2117 * Request a notebook's data from the server.
2118 *
2118 *
2119 * @method load_notebook
2119 * @method load_notebook
2120 * @param {String} notebook_path A notebook to load
2120 * @param {String} notebook_path A notebook to load
2121 */
2121 */
2122 Notebook.prototype.load_notebook = function (notebook_path) {
2122 Notebook.prototype.load_notebook = function (notebook_path) {
2123 this.notebook_path = notebook_path;
2123 this.notebook_path = notebook_path;
2124 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2124 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2125 this.events.trigger('notebook_loading.Notebook');
2125 this.events.trigger('notebook_loading.Notebook');
2126 this.contents.get(notebook_path, {type: 'notebook'}).then(
2126 this.contents.get(notebook_path, {type: 'notebook'}).then(
2127 $.proxy(this.load_notebook_success, this),
2127 $.proxy(this.load_notebook_success, this),
2128 $.proxy(this.load_notebook_error, this)
2128 $.proxy(this.load_notebook_error, this)
2129 );
2129 );
2130 };
2130 };
2131
2131
2132 /**
2132 /**
2133 * Success callback for loading a notebook from the server.
2133 * Success callback for loading a notebook from the server.
2134 *
2134 *
2135 * Load notebook data from the JSON response.
2135 * Load notebook data from the JSON response.
2136 *
2136 *
2137 * @method load_notebook_success
2137 * @method load_notebook_success
2138 * @param {Object} data JSON representation of a notebook
2138 * @param {Object} data JSON representation of a notebook
2139 */
2139 */
2140 Notebook.prototype.load_notebook_success = function (data) {
2140 Notebook.prototype.load_notebook_success = function (data) {
2141 var failed, msg;
2141 var failed, msg;
2142 try {
2142 try {
2143 this.fromJSON(data);
2143 this.fromJSON(data);
2144 } catch (e) {
2144 } catch (e) {
2145 failed = e;
2145 failed = e;
2146 console.log("Notebook failed to load from JSON:", e);
2146 console.log("Notebook failed to load from JSON:", e);
2147 }
2147 }
2148 if (failed || data.message) {
2148 if (failed || data.message) {
2149 // *either* fromJSON failed or validation failed
2149 // *either* fromJSON failed or validation failed
2150 var body = $("<div>");
2150 var body = $("<div>");
2151 var title;
2151 var title;
2152 if (failed) {
2152 if (failed) {
2153 title = "Notebook failed to load";
2153 title = "Notebook failed to load";
2154 body.append($("<p>").text(
2154 body.append($("<p>").text(
2155 "The error was: "
2155 "The error was: "
2156 )).append($("<div>").addClass("js-error").text(
2156 )).append($("<div>").addClass("js-error").text(
2157 failed.toString()
2157 failed.toString()
2158 )).append($("<p>").text(
2158 )).append($("<p>").text(
2159 "See the error console for details."
2159 "See the error console for details."
2160 ));
2160 ));
2161 } else {
2161 } else {
2162 title = "Notebook validation failed";
2162 title = "Notebook validation failed";
2163 }
2163 }
2164
2164
2165 if (data.message) {
2165 if (data.message) {
2166 if (failed) {
2166 if (failed) {
2167 msg = "The notebook also failed validation:";
2167 msg = "The notebook also failed validation:";
2168 } else {
2168 } else {
2169 msg = "An invalid notebook may not function properly." +
2169 msg = "An invalid notebook may not function properly." +
2170 " The validation error was:";
2170 " The validation error was:";
2171 }
2171 }
2172 body.append($("<p>").text(
2172 body.append($("<p>").text(
2173 msg
2173 msg
2174 )).append($("<div>").addClass("validation-error").append(
2174 )).append($("<div>").addClass("validation-error").append(
2175 $("<pre>").text(data.message)
2175 $("<pre>").text(data.message)
2176 ));
2176 ));
2177 }
2177 }
2178
2178
2179 dialog.modal({
2179 dialog.modal({
2180 notebook: this,
2180 notebook: this,
2181 keyboard_manager: this.keyboard_manager,
2181 keyboard_manager: this.keyboard_manager,
2182 title: title,
2182 title: title,
2183 body: body,
2183 body: body,
2184 buttons : {
2184 buttons : {
2185 OK : {
2185 OK : {
2186 "class" : "btn-primary"
2186 "class" : "btn-primary"
2187 }
2187 }
2188 }
2188 }
2189 });
2189 });
2190 }
2190 }
2191 if (this.ncells() === 0) {
2191 if (this.ncells() === 0) {
2192 this.insert_cell_below('code');
2192 this.insert_cell_below('code');
2193 this.edit_mode(0);
2193 this.edit_mode(0);
2194 } else {
2194 } else {
2195 this.select(0);
2195 this.select(0);
2196 this.handle_command_mode(this.get_cell(0));
2196 this.handle_command_mode(this.get_cell(0));
2197 }
2197 }
2198 this.set_dirty(false);
2198 this.set_dirty(false);
2199 this.scroll_to_top();
2199 this.scroll_to_top();
2200 var nbmodel = data.content;
2200 var nbmodel = data.content;
2201 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2201 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2202 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2202 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2203 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2203 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2204 var src;
2204 var src;
2205 if (nbmodel.nbformat > orig_nbformat) {
2205 if (nbmodel.nbformat > orig_nbformat) {
2206 src = " an older notebook format ";
2206 src = " an older notebook format ";
2207 } else {
2207 } else {
2208 src = " a newer notebook format ";
2208 src = " a newer notebook format ";
2209 }
2209 }
2210
2210
2211 msg = "This notebook has been converted from" + src +
2211 msg = "This notebook has been converted from" + src +
2212 "(v"+orig_nbformat+") to the current notebook " +
2212 "(v"+orig_nbformat+") to the current notebook " +
2213 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2213 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2214 "current notebook format will be used.";
2214 "current notebook format will be used.";
2215
2215
2216 if (nbmodel.nbformat > orig_nbformat) {
2216 if (nbmodel.nbformat > orig_nbformat) {
2217 msg += " Older versions of IPython may not be able to read the new format.";
2217 msg += " Older versions of IPython may not be able to read the new format.";
2218 } else {
2218 } else {
2219 msg += " Some features of the original notebook may not be available.";
2219 msg += " Some features of the original notebook may not be available.";
2220 }
2220 }
2221 msg += " To preserve the original version, close the " +
2221 msg += " To preserve the original version, close the " +
2222 "notebook without saving it.";
2222 "notebook without saving it.";
2223 dialog.modal({
2223 dialog.modal({
2224 notebook: this,
2224 notebook: this,
2225 keyboard_manager: this.keyboard_manager,
2225 keyboard_manager: this.keyboard_manager,
2226 title : "Notebook converted",
2226 title : "Notebook converted",
2227 body : msg,
2227 body : msg,
2228 buttons : {
2228 buttons : {
2229 OK : {
2229 OK : {
2230 class : "btn-primary"
2230 class : "btn-primary"
2231 }
2231 }
2232 }
2232 }
2233 });
2233 });
2234 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2234 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2235 var that = this;
2235 var that = this;
2236 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2236 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2237 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2237 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2238 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2238 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2239 this_vs + ". You can still work with this notebook, but some features " +
2239 this_vs + ". You can still work with this notebook, but some features " +
2240 "introduced in later notebook versions may not be available.";
2240 "introduced in later notebook versions may not be available.";
2241
2241
2242 dialog.modal({
2242 dialog.modal({
2243 notebook: this,
2243 notebook: this,
2244 keyboard_manager: this.keyboard_manager,
2244 keyboard_manager: this.keyboard_manager,
2245 title : "Newer Notebook",
2245 title : "Newer Notebook",
2246 body : msg,
2246 body : msg,
2247 buttons : {
2247 buttons : {
2248 OK : {
2248 OK : {
2249 class : "btn-danger"
2249 class : "btn-danger"
2250 }
2250 }
2251 }
2251 }
2252 });
2252 });
2253
2253
2254 }
2254 }
2255
2255
2256 // Create the session after the notebook is completely loaded to prevent
2256 // Create the session after the notebook is completely loaded to prevent
2257 // code execution upon loading, which is a security risk.
2257 // code execution upon loading, which is a security risk.
2258 if (this.session === null) {
2258 if (this.session === null) {
2259 var kernelspec = this.metadata.kernelspec || {};
2259 var kernelspec = this.metadata.kernelspec || {};
2260 var kernel_name = kernelspec.name;
2260 var kernel_name = kernelspec.name;
2261
2261
2262 this.start_session(kernel_name);
2262 this.start_session(kernel_name);
2263 }
2263 }
2264 // load our checkpoint list
2264 // load our checkpoint list
2265 this.list_checkpoints();
2265 this.list_checkpoints();
2266
2266
2267 // load toolbar state
2267 // load toolbar state
2268 if (this.metadata.celltoolbar) {
2268 if (this.metadata.celltoolbar) {
2269 celltoolbar.CellToolbar.global_show();
2269 celltoolbar.CellToolbar.global_show();
2270 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2270 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2271 } else {
2271 } else {
2272 celltoolbar.CellToolbar.global_hide();
2272 celltoolbar.CellToolbar.global_hide();
2273 }
2273 }
2274
2274
2275 // now that we're fully loaded, it is safe to restore save functionality
2275 // now that we're fully loaded, it is safe to restore save functionality
2276 delete(this.save_notebook);
2276 delete(this.save_notebook);
2277 this.events.trigger('notebook_loaded.Notebook');
2277 this.events.trigger('notebook_loaded.Notebook');
2278 };
2278 };
2279
2279
2280 /**
2280 /**
2281 * Failure callback for loading a notebook from the server.
2281 * Failure callback for loading a notebook from the server.
2282 *
2282 *
2283 * @method load_notebook_error
2283 * @method load_notebook_error
2284 * @param {Error} error
2284 * @param {Error} error
2285 */
2285 */
2286 Notebook.prototype.load_notebook_error = function (error) {
2286 Notebook.prototype.load_notebook_error = function (error) {
2287 this.events.trigger('notebook_load_failed.Notebook', error);
2287 this.events.trigger('notebook_load_failed.Notebook', error);
2288 var msg;
2288 var msg;
2289 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2289 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2290 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2290 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2291 msg = "An unknown error occurred while loading this notebook. " +
2291 msg = "An unknown error occurred while loading this notebook. " +
2292 "This version can load notebook formats " +
2292 "This version can load notebook formats " +
2293 "v" + this.nbformat + " or earlier. See the server log for details.";
2293 "v" + this.nbformat + " or earlier. See the server log for details.";
2294 } else {
2294 } else {
2295 msg = error.message;
2295 msg = error.message;
2296 }
2296 }
2297 dialog.modal({
2297 dialog.modal({
2298 notebook: this,
2298 notebook: this,
2299 keyboard_manager: this.keyboard_manager,
2299 keyboard_manager: this.keyboard_manager,
2300 title: "Error loading notebook",
2300 title: "Error loading notebook",
2301 body : msg,
2301 body : msg,
2302 buttons : {
2302 buttons : {
2303 "OK": {}
2303 "OK": {}
2304 }
2304 }
2305 });
2305 });
2306 };
2306 };
2307
2307
2308 /********************* checkpoint-related *********************/
2308 /********************* checkpoint-related *********************/
2309
2309
2310 /**
2310 /**
2311 * Save the notebook then immediately create a checkpoint.
2311 * Save the notebook then immediately create a checkpoint.
2312 *
2312 *
2313 * @method save_checkpoint
2313 * @method save_checkpoint
2314 */
2314 */
2315 Notebook.prototype.save_checkpoint = function () {
2315 Notebook.prototype.save_checkpoint = function () {
2316 this._checkpoint_after_save = true;
2316 this._checkpoint_after_save = true;
2317 this.save_notebook();
2317 this.save_notebook();
2318 };
2318 };
2319
2319
2320 /**
2320 /**
2321 * Add a checkpoint for this notebook.
2321 * Add a checkpoint for this notebook.
2322 * for use as a callback from checkpoint creation.
2322 * for use as a callback from checkpoint creation.
2323 *
2323 *
2324 * @method add_checkpoint
2324 * @method add_checkpoint
2325 */
2325 */
2326 Notebook.prototype.add_checkpoint = function (checkpoint) {
2326 Notebook.prototype.add_checkpoint = function (checkpoint) {
2327 var found = false;
2327 var found = false;
2328 for (var i = 0; i < this.checkpoints.length; i++) {
2328 for (var i = 0; i < this.checkpoints.length; i++) {
2329 var existing = this.checkpoints[i];
2329 var existing = this.checkpoints[i];
2330 if (existing.id == checkpoint.id) {
2330 if (existing.id == checkpoint.id) {
2331 found = true;
2331 found = true;
2332 this.checkpoints[i] = checkpoint;
2332 this.checkpoints[i] = checkpoint;
2333 break;
2333 break;
2334 }
2334 }
2335 }
2335 }
2336 if (!found) {
2336 if (!found) {
2337 this.checkpoints.push(checkpoint);
2337 this.checkpoints.push(checkpoint);
2338 }
2338 }
2339 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2339 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2340 };
2340 };
2341
2341
2342 /**
2342 /**
2343 * List checkpoints for this notebook.
2343 * List checkpoints for this notebook.
2344 *
2344 *
2345 * @method list_checkpoints
2345 * @method list_checkpoints
2346 */
2346 */
2347 Notebook.prototype.list_checkpoints = function () {
2347 Notebook.prototype.list_checkpoints = function () {
2348 var that = this;
2348 var that = this;
2349 this.contents.list_checkpoints(this.notebook_path).then(
2349 this.contents.list_checkpoints(this.notebook_path).then(
2350 $.proxy(this.list_checkpoints_success, this),
2350 $.proxy(this.list_checkpoints_success, this),
2351 function(error) {
2351 function(error) {
2352 that.events.trigger('list_checkpoints_failed.Notebook', error);
2352 that.events.trigger('list_checkpoints_failed.Notebook', error);
2353 }
2353 }
2354 );
2354 );
2355 };
2355 };
2356
2356
2357 /**
2357 /**
2358 * Success callback for listing checkpoints.
2358 * Success callback for listing checkpoints.
2359 *
2359 *
2360 * @method list_checkpoint_success
2360 * @method list_checkpoint_success
2361 * @param {Object} data JSON representation of a checkpoint
2361 * @param {Object} data JSON representation of a checkpoint
2362 */
2362 */
2363 Notebook.prototype.list_checkpoints_success = function (data) {
2363 Notebook.prototype.list_checkpoints_success = function (data) {
2364 this.checkpoints = data;
2364 this.checkpoints = data;
2365 if (data.length) {
2365 if (data.length) {
2366 this.last_checkpoint = data[data.length - 1];
2366 this.last_checkpoint = data[data.length - 1];
2367 } else {
2367 } else {
2368 this.last_checkpoint = null;
2368 this.last_checkpoint = null;
2369 }
2369 }
2370 this.events.trigger('checkpoints_listed.Notebook', [data]);
2370 this.events.trigger('checkpoints_listed.Notebook', [data]);
2371 };
2371 };
2372
2372
2373 /**
2373 /**
2374 * Create a checkpoint of this notebook on the server from the most recent save.
2374 * Create a checkpoint of this notebook on the server from the most recent save.
2375 *
2375 *
2376 * @method create_checkpoint
2376 * @method create_checkpoint
2377 */
2377 */
2378 Notebook.prototype.create_checkpoint = function () {
2378 Notebook.prototype.create_checkpoint = function () {
2379 var that = this;
2379 var that = this;
2380 this.contents.create_checkpoint(this.notebook_path).then(
2380 this.contents.create_checkpoint(this.notebook_path).then(
2381 $.proxy(this.create_checkpoint_success, this),
2381 $.proxy(this.create_checkpoint_success, this),
2382 function (error) {
2382 function (error) {
2383 that.events.trigger('checkpoint_failed.Notebook', error);
2383 that.events.trigger('checkpoint_failed.Notebook', error);
2384 }
2384 }
2385 );
2385 );
2386 };
2386 };
2387
2387
2388 /**
2388 /**
2389 * Success callback for creating a checkpoint.
2389 * Success callback for creating a checkpoint.
2390 *
2390 *
2391 * @method create_checkpoint_success
2391 * @method create_checkpoint_success
2392 * @param {Object} data JSON representation of a checkpoint
2392 * @param {Object} data JSON representation of a checkpoint
2393 */
2393 */
2394 Notebook.prototype.create_checkpoint_success = function (data) {
2394 Notebook.prototype.create_checkpoint_success = function (data) {
2395 this.add_checkpoint(data);
2395 this.add_checkpoint(data);
2396 this.events.trigger('checkpoint_created.Notebook', data);
2396 this.events.trigger('checkpoint_created.Notebook', data);
2397 };
2397 };
2398
2398
2399 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2399 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2400 var that = this;
2400 var that = this;
2401 checkpoint = checkpoint || this.last_checkpoint;
2401 checkpoint = checkpoint || this.last_checkpoint;
2402 if ( ! checkpoint ) {
2402 if ( ! checkpoint ) {
2403 console.log("restore dialog, but no checkpoint to restore to!");
2403 console.log("restore dialog, but no checkpoint to restore to!");
2404 return;
2404 return;
2405 }
2405 }
2406 var body = $('<div/>').append(
2406 var body = $('<div/>').append(
2407 $('<p/>').addClass("p-space").text(
2407 $('<p/>').addClass("p-space").text(
2408 "Are you sure you want to revert the notebook to " +
2408 "Are you sure you want to revert the notebook to " +
2409 "the latest checkpoint?"
2409 "the latest checkpoint?"
2410 ).append(
2410 ).append(
2411 $("<strong/>").text(
2411 $("<strong/>").text(
2412 " This cannot be undone."
2412 " This cannot be undone."
2413 )
2413 )
2414 )
2414 )
2415 ).append(
2415 ).append(
2416 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2416 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2417 ).append(
2417 ).append(
2418 $('<p/>').addClass("p-space").text(
2418 $('<p/>').addClass("p-space").text(
2419 Date(checkpoint.last_modified)
2419 Date(checkpoint.last_modified)
2420 ).css("text-align", "center")
2420 ).css("text-align", "center")
2421 );
2421 );
2422
2422
2423 dialog.modal({
2423 dialog.modal({
2424 notebook: this,
2424 notebook: this,
2425 keyboard_manager: this.keyboard_manager,
2425 keyboard_manager: this.keyboard_manager,
2426 title : "Revert notebook to checkpoint",
2426 title : "Revert notebook to checkpoint",
2427 body : body,
2427 body : body,
2428 buttons : {
2428 buttons : {
2429 Revert : {
2429 Revert : {
2430 class : "btn-danger",
2430 class : "btn-danger",
2431 click : function () {
2431 click : function () {
2432 that.restore_checkpoint(checkpoint.id);
2432 that.restore_checkpoint(checkpoint.id);
2433 }
2433 }
2434 },
2434 },
2435 Cancel : {}
2435 Cancel : {}
2436 }
2436 }
2437 });
2437 });
2438 };
2438 };
2439
2439
2440 /**
2440 /**
2441 * Restore the notebook to a checkpoint state.
2441 * Restore the notebook to a checkpoint state.
2442 *
2442 *
2443 * @method restore_checkpoint
2443 * @method restore_checkpoint
2444 * @param {String} checkpoint ID
2444 * @param {String} checkpoint ID
2445 */
2445 */
2446 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2446 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2447 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2447 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2448 var that = this;
2448 var that = this;
2449 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2449 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2450 $.proxy(this.restore_checkpoint_success, this),
2450 $.proxy(this.restore_checkpoint_success, this),
2451 function (error) {
2451 function (error) {
2452 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2452 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2453 }
2453 }
2454 );
2454 );
2455 };
2455 };
2456
2456
2457 /**
2457 /**
2458 * Success callback for restoring a notebook to a checkpoint.
2458 * Success callback for restoring a notebook to a checkpoint.
2459 *
2459 *
2460 * @method restore_checkpoint_success
2460 * @method restore_checkpoint_success
2461 */
2461 */
2462 Notebook.prototype.restore_checkpoint_success = function () {
2462 Notebook.prototype.restore_checkpoint_success = function () {
2463 this.events.trigger('checkpoint_restored.Notebook');
2463 this.events.trigger('checkpoint_restored.Notebook');
2464 this.load_notebook(this.notebook_path);
2464 this.load_notebook(this.notebook_path);
2465 };
2465 };
2466
2466
2467 /**
2467 /**
2468 * Delete a notebook checkpoint.
2468 * Delete a notebook checkpoint.
2469 *
2469 *
2470 * @method delete_checkpoint
2470 * @method delete_checkpoint
2471 * @param {String} checkpoint ID
2471 * @param {String} checkpoint ID
2472 */
2472 */
2473 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2473 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2474 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2474 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2475 var that = this;
2475 var that = this;
2476 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2476 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2477 $.proxy(this.delete_checkpoint_success, this),
2477 $.proxy(this.delete_checkpoint_success, this),
2478 function (error) {
2478 function (error) {
2479 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2479 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2480 }
2480 }
2481 );
2481 );
2482 };
2482 };
2483
2483
2484 /**
2484 /**
2485 * Success callback for deleting a notebook checkpoint
2485 * Success callback for deleting a notebook checkpoint
2486 *
2486 *
2487 * @method delete_checkpoint_success
2487 * @method delete_checkpoint_success
2488 */
2488 */
2489 Notebook.prototype.delete_checkpoint_success = function () {
2489 Notebook.prototype.delete_checkpoint_success = function () {
2490 this.events.trigger('checkpoint_deleted.Notebook');
2490 this.events.trigger('checkpoint_deleted.Notebook');
2491 this.load_notebook(this.notebook_path);
2491 this.load_notebook(this.notebook_path);
2492 };
2492 };
2493
2493
2494
2494
2495 // For backwards compatability.
2495 // For backwards compatability.
2496 IPython.Notebook = Notebook;
2496 IPython.Notebook = Notebook;
2497
2497
2498 return {'Notebook': Notebook};
2498 return {'Notebook': Notebook};
2499 });
2499 });
General Comments 0
You need to be logged in to leave comments. Login now