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