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