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