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