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