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