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