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