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