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