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