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