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