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