##// END OF EJS Templates
Backport PR #3939: minor checkpoint cleanup...
MinRK -
Show More
@@ -1,270 +1,269
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20
20
21 /**
21 /**
22 * A MenuBar Class to generate the menubar of IPython noteboko
22 * A MenuBar Class to generate the menubar of IPython noteboko
23 * @Class MenuBar
23 * @Class MenuBar
24 *
24 *
25 * @constructor
25 * @constructor
26 *
26 *
27 *
27 *
28 * @param selector {string} selector for the menubar element in DOM
28 * @param selector {string} selector for the menubar element in DOM
29 * @param {object} [options]
29 * @param {object} [options]
30 * @param [options.baseProjectUrl] {String} String to use for the
30 * @param [options.baseProjectUrl] {String} String to use for the
31 * Base Project url, default would be to inspect
31 * Base Project url, default would be to inspect
32 * $('body').data('baseProjectUrl');
32 * $('body').data('baseProjectUrl');
33 * does not support change for now is set through this option
33 * does not support change for now is set through this option
34 */
34 */
35 var MenuBar = function (selector, options) {
35 var MenuBar = function (selector, options) {
36 var options = options || {};
36 var options = options || {};
37 if(options.baseProjectUrl!= undefined){
37 if(options.baseProjectUrl!= undefined){
38 this._baseProjectUrl = options.baseProjectUrl;
38 this._baseProjectUrl = options.baseProjectUrl;
39 }
39 }
40 this.selector = selector;
40 this.selector = selector;
41 if (this.selector !== undefined) {
41 if (this.selector !== undefined) {
42 this.element = $(selector);
42 this.element = $(selector);
43 this.style();
43 this.style();
44 this.bind_events();
44 this.bind_events();
45 }
45 }
46 };
46 };
47
47
48 MenuBar.prototype.baseProjectUrl = function(){
48 MenuBar.prototype.baseProjectUrl = function(){
49 return this._baseProjectUrl || $('body').data('baseProjectUrl');
49 return this._baseProjectUrl || $('body').data('baseProjectUrl');
50 };
50 };
51
51
52
52
53 MenuBar.prototype.style = function () {
53 MenuBar.prototype.style = function () {
54 this.element.addClass('border-box-sizing');
54 this.element.addClass('border-box-sizing');
55 this.element.find("li").click(function (event, ui) {
55 this.element.find("li").click(function (event, ui) {
56 // The selected cell loses focus when the menu is entered, so we
56 // The selected cell loses focus when the menu is entered, so we
57 // re-select it upon selection.
57 // re-select it upon selection.
58 var i = IPython.notebook.get_selected_index();
58 var i = IPython.notebook.get_selected_index();
59 IPython.notebook.select(i);
59 IPython.notebook.select(i);
60 }
60 }
61 );
61 );
62 };
62 };
63
63
64
64
65 MenuBar.prototype.bind_events = function () {
65 MenuBar.prototype.bind_events = function () {
66 // File
66 // File
67 var that = this;
67 var that = this;
68 this.element.find('#new_notebook').click(function () {
68 this.element.find('#new_notebook').click(function () {
69 window.open(that.baseProjectUrl()+'new');
69 window.open(that.baseProjectUrl()+'new');
70 });
70 });
71 this.element.find('#open_notebook').click(function () {
71 this.element.find('#open_notebook').click(function () {
72 window.open(that.baseProjectUrl());
72 window.open(that.baseProjectUrl());
73 });
73 });
74 this.element.find('#rename_notebook').click(function () {
74 this.element.find('#rename_notebook').click(function () {
75 IPython.save_widget.rename_notebook();
75 IPython.save_widget.rename_notebook();
76 });
76 });
77 this.element.find('#copy_notebook').click(function () {
77 this.element.find('#copy_notebook').click(function () {
78 var notebook_id = IPython.notebook.get_notebook_id();
78 var notebook_id = IPython.notebook.get_notebook_id();
79 var url = that.baseProjectUrl() + notebook_id + '/copy';
79 var url = that.baseProjectUrl() + notebook_id + '/copy';
80 window.open(url,'_blank');
80 window.open(url,'_blank');
81 return false;
81 return false;
82 });
82 });
83 this.element.find('#save_checkpoint').click(function () {
83 this.element.find('#save_checkpoint').click(function () {
84 IPython.notebook.save_checkpoint();
84 IPython.notebook.save_checkpoint();
85 });
85 });
86 this.element.find('#restore_checkpoint').click(function () {
86 this.element.find('#restore_checkpoint').click(function () {
87 });
87 });
88 this.element.find('#download_ipynb').click(function () {
88 this.element.find('#download_ipynb').click(function () {
89 var notebook_id = IPython.notebook.get_notebook_id();
89 var notebook_id = IPython.notebook.get_notebook_id();
90 var url = that.baseProjectUrl() + 'notebooks/' +
90 var url = that.baseProjectUrl() + 'notebooks/' +
91 notebook_id + '?format=json';
91 notebook_id + '?format=json';
92 window.location.assign(url);
92 window.location.assign(url);
93 });
93 });
94 this.element.find('#download_py').click(function () {
94 this.element.find('#download_py').click(function () {
95 var notebook_id = IPython.notebook.get_notebook_id();
95 var notebook_id = IPython.notebook.get_notebook_id();
96 var url = that.baseProjectUrl() + 'notebooks/' +
96 var url = that.baseProjectUrl() + 'notebooks/' +
97 notebook_id + '?format=py';
97 notebook_id + '?format=py';
98 window.location.assign(url);
98 window.location.assign(url);
99 });
99 });
100 this.element.find('#kill_and_exit').click(function () {
100 this.element.find('#kill_and_exit').click(function () {
101 IPython.notebook.kernel.kill();
101 IPython.notebook.kernel.kill();
102 setTimeout(function(){window.close();}, 200);
102 setTimeout(function(){window.close();}, 200);
103 });
103 });
104 // Edit
104 // Edit
105 this.element.find('#cut_cell').click(function () {
105 this.element.find('#cut_cell').click(function () {
106 IPython.notebook.cut_cell();
106 IPython.notebook.cut_cell();
107 });
107 });
108 this.element.find('#copy_cell').click(function () {
108 this.element.find('#copy_cell').click(function () {
109 IPython.notebook.copy_cell();
109 IPython.notebook.copy_cell();
110 });
110 });
111 this.element.find('#delete_cell').click(function () {
111 this.element.find('#delete_cell').click(function () {
112 IPython.notebook.delete_cell();
112 IPython.notebook.delete_cell();
113 });
113 });
114 this.element.find('#undelete_cell').click(function () {
114 this.element.find('#undelete_cell').click(function () {
115 IPython.notebook.undelete();
115 IPython.notebook.undelete();
116 });
116 });
117 this.element.find('#split_cell').click(function () {
117 this.element.find('#split_cell').click(function () {
118 IPython.notebook.split_cell();
118 IPython.notebook.split_cell();
119 });
119 });
120 this.element.find('#merge_cell_above').click(function () {
120 this.element.find('#merge_cell_above').click(function () {
121 IPython.notebook.merge_cell_above();
121 IPython.notebook.merge_cell_above();
122 });
122 });
123 this.element.find('#merge_cell_below').click(function () {
123 this.element.find('#merge_cell_below').click(function () {
124 IPython.notebook.merge_cell_below();
124 IPython.notebook.merge_cell_below();
125 });
125 });
126 this.element.find('#move_cell_up').click(function () {
126 this.element.find('#move_cell_up').click(function () {
127 IPython.notebook.move_cell_up();
127 IPython.notebook.move_cell_up();
128 });
128 });
129 this.element.find('#move_cell_down').click(function () {
129 this.element.find('#move_cell_down').click(function () {
130 IPython.notebook.move_cell_down();
130 IPython.notebook.move_cell_down();
131 });
131 });
132 this.element.find('#select_previous').click(function () {
132 this.element.find('#select_previous').click(function () {
133 IPython.notebook.select_prev();
133 IPython.notebook.select_prev();
134 });
134 });
135 this.element.find('#select_next').click(function () {
135 this.element.find('#select_next').click(function () {
136 IPython.notebook.select_next();
136 IPython.notebook.select_next();
137 });
137 });
138 // View
138 // View
139 this.element.find('#toggle_header').click(function () {
139 this.element.find('#toggle_header').click(function () {
140 $('div#header').toggle();
140 $('div#header').toggle();
141 IPython.layout_manager.do_resize();
141 IPython.layout_manager.do_resize();
142 });
142 });
143 this.element.find('#toggle_toolbar').click(function () {
143 this.element.find('#toggle_toolbar').click(function () {
144 $('div#maintoolbar').toggle();
144 $('div#maintoolbar').toggle();
145 IPython.layout_manager.do_resize();
145 IPython.layout_manager.do_resize();
146 });
146 });
147 // Insert
147 // Insert
148 this.element.find('#insert_cell_above').click(function () {
148 this.element.find('#insert_cell_above').click(function () {
149 IPython.notebook.insert_cell_above('code');
149 IPython.notebook.insert_cell_above('code');
150 });
150 });
151 this.element.find('#insert_cell_below').click(function () {
151 this.element.find('#insert_cell_below').click(function () {
152 IPython.notebook.insert_cell_below('code');
152 IPython.notebook.insert_cell_below('code');
153 });
153 });
154 // Cell
154 // Cell
155 this.element.find('#run_cell').click(function () {
155 this.element.find('#run_cell').click(function () {
156 IPython.notebook.execute_selected_cell();
156 IPython.notebook.execute_selected_cell();
157 });
157 });
158 this.element.find('#run_cell_in_place').click(function () {
158 this.element.find('#run_cell_in_place').click(function () {
159 IPython.notebook.execute_selected_cell({terminal:true});
159 IPython.notebook.execute_selected_cell({terminal:true});
160 });
160 });
161 this.element.find('#run_all_cells').click(function () {
161 this.element.find('#run_all_cells').click(function () {
162 IPython.notebook.execute_all_cells();
162 IPython.notebook.execute_all_cells();
163 }).attr('title', 'Run all cells in the notebook');
163 }).attr('title', 'Run all cells in the notebook');
164 this.element.find('#run_all_cells_above').click(function () {
164 this.element.find('#run_all_cells_above').click(function () {
165 IPython.notebook.execute_cells_above();
165 IPython.notebook.execute_cells_above();
166 }).attr('title', 'Run all cells above (but not including) this cell');
166 }).attr('title', 'Run all cells above (but not including) this cell');
167 this.element.find('#run_all_cells_below').click(function () {
167 this.element.find('#run_all_cells_below').click(function () {
168 IPython.notebook.execute_cells_below();
168 IPython.notebook.execute_cells_below();
169 }).attr('title', 'Run this cell and all cells below it');
169 }).attr('title', 'Run this cell and all cells below it');
170 this.element.find('#to_code').click(function () {
170 this.element.find('#to_code').click(function () {
171 IPython.notebook.to_code();
171 IPython.notebook.to_code();
172 });
172 });
173 this.element.find('#to_markdown').click(function () {
173 this.element.find('#to_markdown').click(function () {
174 IPython.notebook.to_markdown();
174 IPython.notebook.to_markdown();
175 });
175 });
176 this.element.find('#to_raw').click(function () {
176 this.element.find('#to_raw').click(function () {
177 IPython.notebook.to_raw();
177 IPython.notebook.to_raw();
178 });
178 });
179 this.element.find('#to_heading1').click(function () {
179 this.element.find('#to_heading1').click(function () {
180 IPython.notebook.to_heading(undefined, 1);
180 IPython.notebook.to_heading(undefined, 1);
181 });
181 });
182 this.element.find('#to_heading2').click(function () {
182 this.element.find('#to_heading2').click(function () {
183 IPython.notebook.to_heading(undefined, 2);
183 IPython.notebook.to_heading(undefined, 2);
184 });
184 });
185 this.element.find('#to_heading3').click(function () {
185 this.element.find('#to_heading3').click(function () {
186 IPython.notebook.to_heading(undefined, 3);
186 IPython.notebook.to_heading(undefined, 3);
187 });
187 });
188 this.element.find('#to_heading4').click(function () {
188 this.element.find('#to_heading4').click(function () {
189 IPython.notebook.to_heading(undefined, 4);
189 IPython.notebook.to_heading(undefined, 4);
190 });
190 });
191 this.element.find('#to_heading5').click(function () {
191 this.element.find('#to_heading5').click(function () {
192 IPython.notebook.to_heading(undefined, 5);
192 IPython.notebook.to_heading(undefined, 5);
193 });
193 });
194 this.element.find('#to_heading6').click(function () {
194 this.element.find('#to_heading6').click(function () {
195 IPython.notebook.to_heading(undefined, 6);
195 IPython.notebook.to_heading(undefined, 6);
196 });
196 });
197 this.element.find('#toggle_output').click(function () {
197 this.element.find('#toggle_output').click(function () {
198 IPython.notebook.toggle_output();
198 IPython.notebook.toggle_output();
199 });
199 });
200 this.element.find('#collapse_all_output').click(function () {
200 this.element.find('#collapse_all_output').click(function () {
201 IPython.notebook.collapse_all_output();
201 IPython.notebook.collapse_all_output();
202 });
202 });
203 this.element.find('#scroll_all_output').click(function () {
203 this.element.find('#scroll_all_output').click(function () {
204 IPython.notebook.scroll_all_output();
204 IPython.notebook.scroll_all_output();
205 });
205 });
206 this.element.find('#expand_all_output').click(function () {
206 this.element.find('#expand_all_output').click(function () {
207 IPython.notebook.expand_all_output();
207 IPython.notebook.expand_all_output();
208 });
208 });
209 this.element.find('#clear_all_output').click(function () {
209 this.element.find('#clear_all_output').click(function () {
210 IPython.notebook.clear_all_output();
210 IPython.notebook.clear_all_output();
211 });
211 });
212 // Kernel
212 // Kernel
213 this.element.find('#int_kernel').click(function () {
213 this.element.find('#int_kernel').click(function () {
214 IPython.notebook.kernel.interrupt();
214 IPython.notebook.kernel.interrupt();
215 });
215 });
216 this.element.find('#restart_kernel').click(function () {
216 this.element.find('#restart_kernel').click(function () {
217 IPython.notebook.restart_kernel();
217 IPython.notebook.restart_kernel();
218 });
218 });
219 // Help
219 // Help
220 this.element.find('#keyboard_shortcuts').click(function () {
220 this.element.find('#keyboard_shortcuts').click(function () {
221 IPython.quick_help.show_keyboard_shortcuts();
221 IPython.quick_help.show_keyboard_shortcuts();
222 });
222 });
223
223
224 this.update_restore_checkpoint(null);
224 this.update_restore_checkpoint(null);
225
225
226 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
226 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
227 that.update_restore_checkpoint(data);
227 that.update_restore_checkpoint(IPython.notebook.checkpoints);
228 });
228 });
229
229
230 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
230 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
231 that.update_restore_checkpoint([data]);
231 that.update_restore_checkpoint(IPython.notebook.checkpoints);
232 });
232 });
233 };
233 };
234
234
235 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
235 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
236 var ul = this.element.find("#restore_checkpoint").find("ul");
236 var ul = this.element.find("#restore_checkpoint").find("ul");
237 ul.empty();
237 ul.empty();
238 if (! checkpoints || checkpoints.length == 0) {
238 if (! checkpoints || checkpoints.length == 0) {
239 ul.append(
239 ul.append(
240 $("<li/>")
240 $("<li/>")
241 .addClass("disabled")
241 .addClass("disabled")
242 .append(
242 .append(
243 $("<a/>")
243 $("<a/>")
244 .text("No checkpoints")
244 .text("No checkpoints")
245 )
245 )
246 );
246 );
247 return;
247 return;
248 };
248 };
249
249
250 for (var i = 0; i < checkpoints.length; i++) {
250 checkpoints.map(function (checkpoint) {
251 var checkpoint = checkpoints[i];
252 var d = new Date(checkpoint.last_modified);
251 var d = new Date(checkpoint.last_modified);
253 ul.append(
252 ul.append(
254 $("<li/>").append(
253 $("<li/>").append(
255 $("<a/>")
254 $("<a/>")
256 .attr("href", "#")
255 .attr("href", "#")
257 .text(d.format("mmm dd HH:MM:ss"))
256 .text(d.format("mmm dd HH:MM:ss"))
258 .click(function () {
257 .click(function () {
259 IPython.notebook.restore_checkpoint_dialog(checkpoint);
258 IPython.notebook.restore_checkpoint_dialog(checkpoint);
260 })
259 })
261 )
260 )
262 );
261 );
263 };
262 });
264 };
263 };
265
264
266 IPython.MenuBar = MenuBar;
265 IPython.MenuBar = MenuBar;
267
266
268 return IPython;
267 return IPython;
269
268
270 }(IPython));
269 }(IPython));
@@ -1,2040 +1,2064
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15 var key = IPython.utils.keycodes;
15 var key = IPython.utils.keycodes;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28
28
29 this.element = $(selector);
29 this.element = $(selector);
30 this.element.scroll();
30 this.element.scroll();
31 this.element.data("notebook", this);
31 this.element.data("notebook", this);
32 this.next_prompt_number = 1;
32 this.next_prompt_number = 1;
33 this.kernel = null;
33 this.kernel = null;
34 this.clipboard = null;
34 this.clipboard = null;
35 this.undelete_backup = null;
35 this.undelete_backup = null;
36 this.undelete_index = null;
36 this.undelete_index = null;
37 this.undelete_below = false;
37 this.undelete_below = false;
38 this.paste_enabled = false;
38 this.paste_enabled = false;
39 this.set_dirty(false);
39 this.set_dirty(false);
40 this.metadata = {};
40 this.metadata = {};
41 this._checkpoint_after_save = false;
41 this._checkpoint_after_save = false;
42 this.last_checkpoint = null;
42 this.last_checkpoint = null;
43 this.checkpoints = [];
43 this.autosave_interval = 0;
44 this.autosave_interval = 0;
44 this.autosave_timer = null;
45 this.autosave_timer = null;
45 // autosave *at most* every two minutes
46 // autosave *at most* every two minutes
46 this.minimum_autosave_interval = 120000;
47 this.minimum_autosave_interval = 120000;
47 // single worksheet for now
48 // single worksheet for now
48 this.worksheet_metadata = {};
49 this.worksheet_metadata = {};
49 this.control_key_active = false;
50 this.control_key_active = false;
50 this.notebook_id = null;
51 this.notebook_id = null;
51 this.notebook_name = null;
52 this.notebook_name = null;
52 this.notebook_name_blacklist_re = /[\/\\:]/;
53 this.notebook_name_blacklist_re = /[\/\\:]/;
53 this.nbformat = 3 // Increment this when changing the nbformat
54 this.nbformat = 3 // Increment this when changing the nbformat
54 this.nbformat_minor = 0 // Increment this when changing the nbformat
55 this.nbformat_minor = 0 // Increment this when changing the nbformat
55 this.style();
56 this.style();
56 this.create_elements();
57 this.create_elements();
57 this.bind_events();
58 this.bind_events();
58 };
59 };
59
60
60 /**
61 /**
61 * Tweak the notebook's CSS style.
62 * Tweak the notebook's CSS style.
62 *
63 *
63 * @method style
64 * @method style
64 */
65 */
65 Notebook.prototype.style = function () {
66 Notebook.prototype.style = function () {
66 $('div#notebook').addClass('border-box-sizing');
67 $('div#notebook').addClass('border-box-sizing');
67 };
68 };
68
69
69 /**
70 /**
70 * Get the root URL of the notebook server.
71 * Get the root URL of the notebook server.
71 *
72 *
72 * @method baseProjectUrl
73 * @method baseProjectUrl
73 * @return {String} The base project URL
74 * @return {String} The base project URL
74 */
75 */
75 Notebook.prototype.baseProjectUrl = function(){
76 Notebook.prototype.baseProjectUrl = function(){
76 return this._baseProjectUrl || $('body').data('baseProjectUrl');
77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
77 };
78 };
78
79
79 /**
80 /**
80 * Create an HTML and CSS representation of the notebook.
81 * Create an HTML and CSS representation of the notebook.
81 *
82 *
82 * @method create_elements
83 * @method create_elements
83 */
84 */
84 Notebook.prototype.create_elements = function () {
85 Notebook.prototype.create_elements = function () {
85 // We add this end_space div to the end of the notebook div to:
86 // We add this end_space div to the end of the notebook div to:
86 // i) provide a margin between the last cell and the end of the notebook
87 // i) provide a margin between the last cell and the end of the notebook
87 // ii) to prevent the div from scrolling up when the last cell is being
88 // ii) to prevent the div from scrolling up when the last cell is being
88 // edited, but is too low on the page, which browsers will do automatically.
89 // edited, but is too low on the page, which browsers will do automatically.
89 var that = this;
90 var that = this;
90 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
91 var end_space = $('<div/>').addClass('end_space');
92 var end_space = $('<div/>').addClass('end_space');
92 end_space.dblclick(function (e) {
93 end_space.dblclick(function (e) {
93 var ncells = that.ncells();
94 var ncells = that.ncells();
94 that.insert_cell_below('code',ncells-1);
95 that.insert_cell_below('code',ncells-1);
95 });
96 });
96 this.element.append(this.container);
97 this.element.append(this.container);
97 this.container.append(end_space);
98 this.container.append(end_space);
98 $('div#notebook').addClass('border-box-sizing');
99 $('div#notebook').addClass('border-box-sizing');
99 };
100 };
100
101
101 /**
102 /**
102 * Bind JavaScript events: key presses and custom IPython events.
103 * Bind JavaScript events: key presses and custom IPython events.
103 *
104 *
104 * @method bind_events
105 * @method bind_events
105 */
106 */
106 Notebook.prototype.bind_events = function () {
107 Notebook.prototype.bind_events = function () {
107 var that = this;
108 var that = this;
108
109
109 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
110 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
110 var index = that.find_cell_index(data.cell);
111 var index = that.find_cell_index(data.cell);
111 var new_cell = that.insert_cell_below('code',index);
112 var new_cell = that.insert_cell_below('code',index);
112 new_cell.set_text(data.text);
113 new_cell.set_text(data.text);
113 that.dirty = true;
114 that.dirty = true;
114 });
115 });
115
116
116 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
117 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
117 that.dirty = data.value;
118 that.dirty = data.value;
118 });
119 });
119
120
120 $([IPython.events]).on('select.Cell', function (event, data) {
121 $([IPython.events]).on('select.Cell', function (event, data) {
121 var index = that.find_cell_index(data.cell);
122 var index = that.find_cell_index(data.cell);
122 that.select(index);
123 that.select(index);
123 });
124 });
124
125
125 $([IPython.events]).on('status_autorestarting.Kernel', function () {
126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
126 IPython.dialog.modal({
127 IPython.dialog.modal({
127 title: "Kernel Restarting",
128 title: "Kernel Restarting",
128 body: "The kernel appears to have died. It will restart automatically.",
129 body: "The kernel appears to have died. It will restart automatically.",
129 buttons: {
130 buttons: {
130 OK : {
131 OK : {
131 class : "btn-primary"
132 class : "btn-primary"
132 }
133 }
133 }
134 }
134 });
135 });
135 });
136 });
136
137
137
138
138 $(document).keydown(function (event) {
139 $(document).keydown(function (event) {
139
140
140 // Save (CTRL+S) or (AppleKey+S)
141 // Save (CTRL+S) or (AppleKey+S)
141 //metaKey = applekey on mac
142 //metaKey = applekey on mac
142 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
143 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
143 that.save_checkpoint();
144 that.save_checkpoint();
144 event.preventDefault();
145 event.preventDefault();
145 return false;
146 return false;
146 } else if (event.which === key.ESC) {
147 } else if (event.which === key.ESC) {
147 // Intercept escape at highest level to avoid closing
148 // Intercept escape at highest level to avoid closing
148 // websocket connection with firefox
149 // websocket connection with firefox
149 IPython.pager.collapse();
150 IPython.pager.collapse();
150 event.preventDefault();
151 event.preventDefault();
151 } else if (event.which === key.SHIFT) {
152 } else if (event.which === key.SHIFT) {
152 // ignore shift keydown
153 // ignore shift keydown
153 return true;
154 return true;
154 }
155 }
155 if (event.which === key.UPARROW && !event.shiftKey) {
156 if (event.which === key.UPARROW && !event.shiftKey) {
156 var cell = that.get_selected_cell();
157 var cell = that.get_selected_cell();
157 if (cell && cell.at_top()) {
158 if (cell && cell.at_top()) {
158 event.preventDefault();
159 event.preventDefault();
159 that.select_prev();
160 that.select_prev();
160 };
161 };
161 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
162 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
162 var cell = that.get_selected_cell();
163 var cell = that.get_selected_cell();
163 if (cell && cell.at_bottom()) {
164 if (cell && cell.at_bottom()) {
164 event.preventDefault();
165 event.preventDefault();
165 that.select_next();
166 that.select_next();
166 };
167 };
167 } else if (event.which === key.ENTER && event.shiftKey) {
168 } else if (event.which === key.ENTER && event.shiftKey) {
168 that.execute_selected_cell();
169 that.execute_selected_cell();
169 return false;
170 return false;
170 } else if (event.which === key.ENTER && event.altKey) {
171 } else if (event.which === key.ENTER && event.altKey) {
171 // Execute code cell, and insert new in place
172 // Execute code cell, and insert new in place
172 that.execute_selected_cell();
173 that.execute_selected_cell();
173 // Only insert a new cell, if we ended up in an already populated cell
174 // Only insert a new cell, if we ended up in an already populated cell
174 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
175 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
175 that.insert_cell_above('code');
176 that.insert_cell_above('code');
176 }
177 }
177 return false;
178 return false;
178 } else if (event.which === key.ENTER && event.ctrlKey) {
179 } else if (event.which === key.ENTER && event.ctrlKey) {
179 that.execute_selected_cell({terminal:true});
180 that.execute_selected_cell({terminal:true});
180 return false;
181 return false;
181 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
182 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
182 that.control_key_active = true;
183 that.control_key_active = true;
183 return false;
184 return false;
184 } else if (event.which === 88 && that.control_key_active) {
185 } else if (event.which === 88 && that.control_key_active) {
185 // Cut selected cell = x
186 // Cut selected cell = x
186 that.cut_cell();
187 that.cut_cell();
187 that.control_key_active = false;
188 that.control_key_active = false;
188 return false;
189 return false;
189 } else if (event.which === 67 && that.control_key_active) {
190 } else if (event.which === 67 && that.control_key_active) {
190 // Copy selected cell = c
191 // Copy selected cell = c
191 that.copy_cell();
192 that.copy_cell();
192 that.control_key_active = false;
193 that.control_key_active = false;
193 return false;
194 return false;
194 } else if (event.which === 86 && that.control_key_active) {
195 } else if (event.which === 86 && that.control_key_active) {
195 // Paste below selected cell = v
196 // Paste below selected cell = v
196 that.paste_cell_below();
197 that.paste_cell_below();
197 that.control_key_active = false;
198 that.control_key_active = false;
198 return false;
199 return false;
199 } else if (event.which === 68 && that.control_key_active) {
200 } else if (event.which === 68 && that.control_key_active) {
200 // Delete selected cell = d
201 // Delete selected cell = d
201 that.delete_cell();
202 that.delete_cell();
202 that.control_key_active = false;
203 that.control_key_active = false;
203 return false;
204 return false;
204 } else if (event.which === 65 && that.control_key_active) {
205 } else if (event.which === 65 && that.control_key_active) {
205 // Insert code cell above selected = a
206 // Insert code cell above selected = a
206 that.insert_cell_above('code');
207 that.insert_cell_above('code');
207 that.control_key_active = false;
208 that.control_key_active = false;
208 return false;
209 return false;
209 } else if (event.which === 66 && that.control_key_active) {
210 } else if (event.which === 66 && that.control_key_active) {
210 // Insert code cell below selected = b
211 // Insert code cell below selected = b
211 that.insert_cell_below('code');
212 that.insert_cell_below('code');
212 that.control_key_active = false;
213 that.control_key_active = false;
213 return false;
214 return false;
214 } else if (event.which === 89 && that.control_key_active) {
215 } else if (event.which === 89 && that.control_key_active) {
215 // To code = y
216 // To code = y
216 that.to_code();
217 that.to_code();
217 that.control_key_active = false;
218 that.control_key_active = false;
218 return false;
219 return false;
219 } else if (event.which === 77 && that.control_key_active) {
220 } else if (event.which === 77 && that.control_key_active) {
220 // To markdown = m
221 // To markdown = m
221 that.to_markdown();
222 that.to_markdown();
222 that.control_key_active = false;
223 that.control_key_active = false;
223 return false;
224 return false;
224 } else if (event.which === 84 && that.control_key_active) {
225 } else if (event.which === 84 && that.control_key_active) {
225 // To Raw = t
226 // To Raw = t
226 that.to_raw();
227 that.to_raw();
227 that.control_key_active = false;
228 that.control_key_active = false;
228 return false;
229 return false;
229 } else if (event.which === 49 && that.control_key_active) {
230 } else if (event.which === 49 && that.control_key_active) {
230 // To Heading 1 = 1
231 // To Heading 1 = 1
231 that.to_heading(undefined, 1);
232 that.to_heading(undefined, 1);
232 that.control_key_active = false;
233 that.control_key_active = false;
233 return false;
234 return false;
234 } else if (event.which === 50 && that.control_key_active) {
235 } else if (event.which === 50 && that.control_key_active) {
235 // To Heading 2 = 2
236 // To Heading 2 = 2
236 that.to_heading(undefined, 2);
237 that.to_heading(undefined, 2);
237 that.control_key_active = false;
238 that.control_key_active = false;
238 return false;
239 return false;
239 } else if (event.which === 51 && that.control_key_active) {
240 } else if (event.which === 51 && that.control_key_active) {
240 // To Heading 3 = 3
241 // To Heading 3 = 3
241 that.to_heading(undefined, 3);
242 that.to_heading(undefined, 3);
242 that.control_key_active = false;
243 that.control_key_active = false;
243 return false;
244 return false;
244 } else if (event.which === 52 && that.control_key_active) {
245 } else if (event.which === 52 && that.control_key_active) {
245 // To Heading 4 = 4
246 // To Heading 4 = 4
246 that.to_heading(undefined, 4);
247 that.to_heading(undefined, 4);
247 that.control_key_active = false;
248 that.control_key_active = false;
248 return false;
249 return false;
249 } else if (event.which === 53 && that.control_key_active) {
250 } else if (event.which === 53 && that.control_key_active) {
250 // To Heading 5 = 5
251 // To Heading 5 = 5
251 that.to_heading(undefined, 5);
252 that.to_heading(undefined, 5);
252 that.control_key_active = false;
253 that.control_key_active = false;
253 return false;
254 return false;
254 } else if (event.which === 54 && that.control_key_active) {
255 } else if (event.which === 54 && that.control_key_active) {
255 // To Heading 6 = 6
256 // To Heading 6 = 6
256 that.to_heading(undefined, 6);
257 that.to_heading(undefined, 6);
257 that.control_key_active = false;
258 that.control_key_active = false;
258 return false;
259 return false;
259 } else if (event.which === 79 && that.control_key_active) {
260 } else if (event.which === 79 && that.control_key_active) {
260 // Toggle output = o
261 // Toggle output = o
261 if (event.shiftKey){
262 if (event.shiftKey){
262 that.toggle_output_scroll();
263 that.toggle_output_scroll();
263 } else {
264 } else {
264 that.toggle_output();
265 that.toggle_output();
265 }
266 }
266 that.control_key_active = false;
267 that.control_key_active = false;
267 return false;
268 return false;
268 } else if (event.which === 83 && that.control_key_active) {
269 } else if (event.which === 83 && that.control_key_active) {
269 // Save notebook = s
270 // Save notebook = s
270 that.save_checkpoint();
271 that.save_checkpoint();
271 that.control_key_active = false;
272 that.control_key_active = false;
272 return false;
273 return false;
273 } else if (event.which === 74 && that.control_key_active) {
274 } else if (event.which === 74 && that.control_key_active) {
274 // Move cell down = j
275 // Move cell down = j
275 that.move_cell_down();
276 that.move_cell_down();
276 that.control_key_active = false;
277 that.control_key_active = false;
277 return false;
278 return false;
278 } else if (event.which === 75 && that.control_key_active) {
279 } else if (event.which === 75 && that.control_key_active) {
279 // Move cell up = k
280 // Move cell up = k
280 that.move_cell_up();
281 that.move_cell_up();
281 that.control_key_active = false;
282 that.control_key_active = false;
282 return false;
283 return false;
283 } else if (event.which === 80 && that.control_key_active) {
284 } else if (event.which === 80 && that.control_key_active) {
284 // Select previous = p
285 // Select previous = p
285 that.select_prev();
286 that.select_prev();
286 that.control_key_active = false;
287 that.control_key_active = false;
287 return false;
288 return false;
288 } else if (event.which === 78 && that.control_key_active) {
289 } else if (event.which === 78 && that.control_key_active) {
289 // Select next = n
290 // Select next = n
290 that.select_next();
291 that.select_next();
291 that.control_key_active = false;
292 that.control_key_active = false;
292 return false;
293 return false;
293 } else if (event.which === 76 && that.control_key_active) {
294 } else if (event.which === 76 && that.control_key_active) {
294 // Toggle line numbers = l
295 // Toggle line numbers = l
295 that.cell_toggle_line_numbers();
296 that.cell_toggle_line_numbers();
296 that.control_key_active = false;
297 that.control_key_active = false;
297 return false;
298 return false;
298 } else if (event.which === 73 && that.control_key_active) {
299 } else if (event.which === 73 && that.control_key_active) {
299 // Interrupt kernel = i
300 // Interrupt kernel = i
300 that.kernel.interrupt();
301 that.kernel.interrupt();
301 that.control_key_active = false;
302 that.control_key_active = false;
302 return false;
303 return false;
303 } else if (event.which === 190 && that.control_key_active) {
304 } else if (event.which === 190 && that.control_key_active) {
304 // Restart kernel = . # matches qt console
305 // Restart kernel = . # matches qt console
305 that.restart_kernel();
306 that.restart_kernel();
306 that.control_key_active = false;
307 that.control_key_active = false;
307 return false;
308 return false;
308 } else if (event.which === 72 && that.control_key_active) {
309 } else if (event.which === 72 && that.control_key_active) {
309 // Show keyboard shortcuts = h
310 // Show keyboard shortcuts = h
310 IPython.quick_help.show_keyboard_shortcuts();
311 IPython.quick_help.show_keyboard_shortcuts();
311 that.control_key_active = false;
312 that.control_key_active = false;
312 return false;
313 return false;
313 } else if (event.which === 90 && that.control_key_active) {
314 } else if (event.which === 90 && that.control_key_active) {
314 // Undo last cell delete = z
315 // Undo last cell delete = z
315 that.undelete();
316 that.undelete();
316 that.control_key_active = false;
317 that.control_key_active = false;
317 return false;
318 return false;
318 } else if (event.which === 189 && that.control_key_active) {
319 } else if (event.which === 189 && that.control_key_active) {
319 // Split cell = -
320 // Split cell = -
320 that.split_cell();
321 that.split_cell();
321 that.control_key_active = false;
322 that.control_key_active = false;
322 return false;
323 return false;
323 } else if (that.control_key_active) {
324 } else if (that.control_key_active) {
324 that.control_key_active = false;
325 that.control_key_active = false;
325 return true;
326 return true;
326 }
327 }
327 return true;
328 return true;
328 });
329 });
329
330
330 var collapse_time = function(time){
331 var collapse_time = function(time){
331 var app_height = $('#ipython-main-app').height(); // content height
332 var app_height = $('#ipython-main-app').height(); // content height
332 var splitter_height = $('div#pager_splitter').outerHeight(true);
333 var splitter_height = $('div#pager_splitter').outerHeight(true);
333 var new_height = app_height - splitter_height;
334 var new_height = app_height - splitter_height;
334 that.element.animate({height : new_height + 'px'}, time);
335 that.element.animate({height : new_height + 'px'}, time);
335 }
336 }
336
337
337 this.element.bind('collapse_pager', function (event,extrap) {
338 this.element.bind('collapse_pager', function (event,extrap) {
338 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
339 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
339 collapse_time(time);
340 collapse_time(time);
340 });
341 });
341
342
342 var expand_time = function(time) {
343 var expand_time = function(time) {
343 var app_height = $('#ipython-main-app').height(); // content height
344 var app_height = $('#ipython-main-app').height(); // content height
344 var splitter_height = $('div#pager_splitter').outerHeight(true);
345 var splitter_height = $('div#pager_splitter').outerHeight(true);
345 var pager_height = $('div#pager').outerHeight(true);
346 var pager_height = $('div#pager').outerHeight(true);
346 var new_height = app_height - pager_height - splitter_height;
347 var new_height = app_height - pager_height - splitter_height;
347 that.element.animate({height : new_height + 'px'}, time);
348 that.element.animate({height : new_height + 'px'}, time);
348 }
349 }
349
350
350 this.element.bind('expand_pager', function (event, extrap) {
351 this.element.bind('expand_pager', function (event, extrap) {
351 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
352 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
352 expand_time(time);
353 expand_time(time);
353 });
354 });
354
355
355 // Firefox 22 broke $(window).on("beforeunload")
356 // Firefox 22 broke $(window).on("beforeunload")
356 // I'm not sure why or how.
357 // I'm not sure why or how.
357 window.onbeforeunload = function (e) {
358 window.onbeforeunload = function (e) {
358 // TODO: Make killing the kernel configurable.
359 // TODO: Make killing the kernel configurable.
359 var kill_kernel = false;
360 var kill_kernel = false;
360 if (kill_kernel) {
361 if (kill_kernel) {
361 that.kernel.kill();
362 that.kernel.kill();
362 }
363 }
363 // if we are autosaving, trigger an autosave on nav-away.
364 // if we are autosaving, trigger an autosave on nav-away.
364 // still warn, because if we don't the autosave may fail.
365 // still warn, because if we don't the autosave may fail.
365 if (that.dirty) {
366 if (that.dirty) {
366 if ( that.autosave_interval ) {
367 if ( that.autosave_interval ) {
367 // schedule autosave in a timeout
368 // schedule autosave in a timeout
368 // this gives you a chance to forcefully discard changes
369 // this gives you a chance to forcefully discard changes
369 // by reloading the page if you *really* want to.
370 // by reloading the page if you *really* want to.
370 // the timer doesn't start until you *dismiss* the dialog.
371 // the timer doesn't start until you *dismiss* the dialog.
371 setTimeout(function () {
372 setTimeout(function () {
372 if (that.dirty) {
373 if (that.dirty) {
373 that.save_notebook();
374 that.save_notebook();
374 }
375 }
375 }, 1000);
376 }, 1000);
376 return "Autosave in progress, latest changes may be lost.";
377 return "Autosave in progress, latest changes may be lost.";
377 } else {
378 } else {
378 return "Unsaved changes will be lost.";
379 return "Unsaved changes will be lost.";
379 }
380 }
380 };
381 };
381 // Null is the *only* return value that will make the browser not
382 // Null is the *only* return value that will make the browser not
382 // pop up the "don't leave" dialog.
383 // pop up the "don't leave" dialog.
383 return null;
384 return null;
384 };
385 };
385 };
386 };
386
387
387 /**
388 /**
388 * Set the dirty flag, and trigger the set_dirty.Notebook event
389 * Set the dirty flag, and trigger the set_dirty.Notebook event
389 *
390 *
390 * @method set_dirty
391 * @method set_dirty
391 */
392 */
392 Notebook.prototype.set_dirty = function (value) {
393 Notebook.prototype.set_dirty = function (value) {
393 if (value === undefined) {
394 if (value === undefined) {
394 value = true;
395 value = true;
395 }
396 }
396 if (this.dirty == value) {
397 if (this.dirty == value) {
397 return;
398 return;
398 }
399 }
399 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
400 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
400 };
401 };
401
402
402 /**
403 /**
403 * Scroll the top of the page to a given cell.
404 * Scroll the top of the page to a given cell.
404 *
405 *
405 * @method scroll_to_cell
406 * @method scroll_to_cell
406 * @param {Number} cell_number An index of the cell to view
407 * @param {Number} cell_number An index of the cell to view
407 * @param {Number} time Animation time in milliseconds
408 * @param {Number} time Animation time in milliseconds
408 * @return {Number} Pixel offset from the top of the container
409 * @return {Number} Pixel offset from the top of the container
409 */
410 */
410 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
411 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
411 var cells = this.get_cells();
412 var cells = this.get_cells();
412 var time = time || 0;
413 var time = time || 0;
413 cell_number = Math.min(cells.length-1,cell_number);
414 cell_number = Math.min(cells.length-1,cell_number);
414 cell_number = Math.max(0 ,cell_number);
415 cell_number = Math.max(0 ,cell_number);
415 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
416 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
416 this.element.animate({scrollTop:scroll_value}, time);
417 this.element.animate({scrollTop:scroll_value}, time);
417 return scroll_value;
418 return scroll_value;
418 };
419 };
419
420
420 /**
421 /**
421 * Scroll to the bottom of the page.
422 * Scroll to the bottom of the page.
422 *
423 *
423 * @method scroll_to_bottom
424 * @method scroll_to_bottom
424 */
425 */
425 Notebook.prototype.scroll_to_bottom = function () {
426 Notebook.prototype.scroll_to_bottom = function () {
426 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
427 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
427 };
428 };
428
429
429 /**
430 /**
430 * Scroll to the top of the page.
431 * Scroll to the top of the page.
431 *
432 *
432 * @method scroll_to_top
433 * @method scroll_to_top
433 */
434 */
434 Notebook.prototype.scroll_to_top = function () {
435 Notebook.prototype.scroll_to_top = function () {
435 this.element.animate({scrollTop:0}, 0);
436 this.element.animate({scrollTop:0}, 0);
436 };
437 };
437
438
438
439
439 // Cell indexing, retrieval, etc.
440 // Cell indexing, retrieval, etc.
440
441
441 /**
442 /**
442 * Get all cell elements in the notebook.
443 * Get all cell elements in the notebook.
443 *
444 *
444 * @method get_cell_elements
445 * @method get_cell_elements
445 * @return {jQuery} A selector of all cell elements
446 * @return {jQuery} A selector of all cell elements
446 */
447 */
447 Notebook.prototype.get_cell_elements = function () {
448 Notebook.prototype.get_cell_elements = function () {
448 return this.container.children("div.cell");
449 return this.container.children("div.cell");
449 };
450 };
450
451
451 /**
452 /**
452 * Get a particular cell element.
453 * Get a particular cell element.
453 *
454 *
454 * @method get_cell_element
455 * @method get_cell_element
455 * @param {Number} index An index of a cell to select
456 * @param {Number} index An index of a cell to select
456 * @return {jQuery} A selector of the given cell.
457 * @return {jQuery} A selector of the given cell.
457 */
458 */
458 Notebook.prototype.get_cell_element = function (index) {
459 Notebook.prototype.get_cell_element = function (index) {
459 var result = null;
460 var result = null;
460 var e = this.get_cell_elements().eq(index);
461 var e = this.get_cell_elements().eq(index);
461 if (e.length !== 0) {
462 if (e.length !== 0) {
462 result = e;
463 result = e;
463 }
464 }
464 return result;
465 return result;
465 };
466 };
466
467
467 /**
468 /**
468 * Count the cells in this notebook.
469 * Count the cells in this notebook.
469 *
470 *
470 * @method ncells
471 * @method ncells
471 * @return {Number} The number of cells in this notebook
472 * @return {Number} The number of cells in this notebook
472 */
473 */
473 Notebook.prototype.ncells = function () {
474 Notebook.prototype.ncells = function () {
474 return this.get_cell_elements().length;
475 return this.get_cell_elements().length;
475 };
476 };
476
477
477 /**
478 /**
478 * Get all Cell objects in this notebook.
479 * Get all Cell objects in this notebook.
479 *
480 *
480 * @method get_cells
481 * @method get_cells
481 * @return {Array} This notebook's Cell objects
482 * @return {Array} This notebook's Cell objects
482 */
483 */
483 // TODO: we are often calling cells as cells()[i], which we should optimize
484 // TODO: we are often calling cells as cells()[i], which we should optimize
484 // to cells(i) or a new method.
485 // to cells(i) or a new method.
485 Notebook.prototype.get_cells = function () {
486 Notebook.prototype.get_cells = function () {
486 return this.get_cell_elements().toArray().map(function (e) {
487 return this.get_cell_elements().toArray().map(function (e) {
487 return $(e).data("cell");
488 return $(e).data("cell");
488 });
489 });
489 };
490 };
490
491
491 /**
492 /**
492 * Get a Cell object from this notebook.
493 * Get a Cell object from this notebook.
493 *
494 *
494 * @method get_cell
495 * @method get_cell
495 * @param {Number} index An index of a cell to retrieve
496 * @param {Number} index An index of a cell to retrieve
496 * @return {Cell} A particular cell
497 * @return {Cell} A particular cell
497 */
498 */
498 Notebook.prototype.get_cell = function (index) {
499 Notebook.prototype.get_cell = function (index) {
499 var result = null;
500 var result = null;
500 var ce = this.get_cell_element(index);
501 var ce = this.get_cell_element(index);
501 if (ce !== null) {
502 if (ce !== null) {
502 result = ce.data('cell');
503 result = ce.data('cell');
503 }
504 }
504 return result;
505 return result;
505 }
506 }
506
507
507 /**
508 /**
508 * Get the cell below a given cell.
509 * Get the cell below a given cell.
509 *
510 *
510 * @method get_next_cell
511 * @method get_next_cell
511 * @param {Cell} cell The provided cell
512 * @param {Cell} cell The provided cell
512 * @return {Cell} The next cell
513 * @return {Cell} The next cell
513 */
514 */
514 Notebook.prototype.get_next_cell = function (cell) {
515 Notebook.prototype.get_next_cell = function (cell) {
515 var result = null;
516 var result = null;
516 var index = this.find_cell_index(cell);
517 var index = this.find_cell_index(cell);
517 if (this.is_valid_cell_index(index+1)) {
518 if (this.is_valid_cell_index(index+1)) {
518 result = this.get_cell(index+1);
519 result = this.get_cell(index+1);
519 }
520 }
520 return result;
521 return result;
521 }
522 }
522
523
523 /**
524 /**
524 * Get the cell above a given cell.
525 * Get the cell above a given cell.
525 *
526 *
526 * @method get_prev_cell
527 * @method get_prev_cell
527 * @param {Cell} cell The provided cell
528 * @param {Cell} cell The provided cell
528 * @return {Cell} The previous cell
529 * @return {Cell} The previous cell
529 */
530 */
530 Notebook.prototype.get_prev_cell = function (cell) {
531 Notebook.prototype.get_prev_cell = function (cell) {
531 // TODO: off-by-one
532 // TODO: off-by-one
532 // nb.get_prev_cell(nb.get_cell(1)) is null
533 // nb.get_prev_cell(nb.get_cell(1)) is null
533 var result = null;
534 var result = null;
534 var index = this.find_cell_index(cell);
535 var index = this.find_cell_index(cell);
535 if (index !== null && index > 1) {
536 if (index !== null && index > 1) {
536 result = this.get_cell(index-1);
537 result = this.get_cell(index-1);
537 }
538 }
538 return result;
539 return result;
539 }
540 }
540
541
541 /**
542 /**
542 * Get the numeric index of a given cell.
543 * Get the numeric index of a given cell.
543 *
544 *
544 * @method find_cell_index
545 * @method find_cell_index
545 * @param {Cell} cell The provided cell
546 * @param {Cell} cell The provided cell
546 * @return {Number} The cell's numeric index
547 * @return {Number} The cell's numeric index
547 */
548 */
548 Notebook.prototype.find_cell_index = function (cell) {
549 Notebook.prototype.find_cell_index = function (cell) {
549 var result = null;
550 var result = null;
550 this.get_cell_elements().filter(function (index) {
551 this.get_cell_elements().filter(function (index) {
551 if ($(this).data("cell") === cell) {
552 if ($(this).data("cell") === cell) {
552 result = index;
553 result = index;
553 };
554 };
554 });
555 });
555 return result;
556 return result;
556 };
557 };
557
558
558 /**
559 /**
559 * Get a given index , or the selected index if none is provided.
560 * Get a given index , or the selected index if none is provided.
560 *
561 *
561 * @method index_or_selected
562 * @method index_or_selected
562 * @param {Number} index A cell's index
563 * @param {Number} index A cell's index
563 * @return {Number} The given index, or selected index if none is provided.
564 * @return {Number} The given index, or selected index if none is provided.
564 */
565 */
565 Notebook.prototype.index_or_selected = function (index) {
566 Notebook.prototype.index_or_selected = function (index) {
566 var i;
567 var i;
567 if (index === undefined || index === null) {
568 if (index === undefined || index === null) {
568 i = this.get_selected_index();
569 i = this.get_selected_index();
569 if (i === null) {
570 if (i === null) {
570 i = 0;
571 i = 0;
571 }
572 }
572 } else {
573 } else {
573 i = index;
574 i = index;
574 }
575 }
575 return i;
576 return i;
576 };
577 };
577
578
578 /**
579 /**
579 * Get the currently selected cell.
580 * Get the currently selected cell.
580 * @method get_selected_cell
581 * @method get_selected_cell
581 * @return {Cell} The selected cell
582 * @return {Cell} The selected cell
582 */
583 */
583 Notebook.prototype.get_selected_cell = function () {
584 Notebook.prototype.get_selected_cell = function () {
584 var index = this.get_selected_index();
585 var index = this.get_selected_index();
585 return this.get_cell(index);
586 return this.get_cell(index);
586 };
587 };
587
588
588 /**
589 /**
589 * Check whether a cell index is valid.
590 * Check whether a cell index is valid.
590 *
591 *
591 * @method is_valid_cell_index
592 * @method is_valid_cell_index
592 * @param {Number} index A cell index
593 * @param {Number} index A cell index
593 * @return True if the index is valid, false otherwise
594 * @return True if the index is valid, false otherwise
594 */
595 */
595 Notebook.prototype.is_valid_cell_index = function (index) {
596 Notebook.prototype.is_valid_cell_index = function (index) {
596 if (index !== null && index >= 0 && index < this.ncells()) {
597 if (index !== null && index >= 0 && index < this.ncells()) {
597 return true;
598 return true;
598 } else {
599 } else {
599 return false;
600 return false;
600 };
601 };
601 }
602 }
602
603
603 /**
604 /**
604 * Get the index of the currently selected cell.
605 * Get the index of the currently selected cell.
605
606
606 * @method get_selected_index
607 * @method get_selected_index
607 * @return {Number} The selected cell's numeric index
608 * @return {Number} The selected cell's numeric index
608 */
609 */
609 Notebook.prototype.get_selected_index = function () {
610 Notebook.prototype.get_selected_index = function () {
610 var result = null;
611 var result = null;
611 this.get_cell_elements().filter(function (index) {
612 this.get_cell_elements().filter(function (index) {
612 if ($(this).data("cell").selected === true) {
613 if ($(this).data("cell").selected === true) {
613 result = index;
614 result = index;
614 };
615 };
615 });
616 });
616 return result;
617 return result;
617 };
618 };
618
619
619
620
620 // Cell selection.
621 // Cell selection.
621
622
622 /**
623 /**
623 * Programmatically select a cell.
624 * Programmatically select a cell.
624 *
625 *
625 * @method select
626 * @method select
626 * @param {Number} index A cell's index
627 * @param {Number} index A cell's index
627 * @return {Notebook} This notebook
628 * @return {Notebook} This notebook
628 */
629 */
629 Notebook.prototype.select = function (index) {
630 Notebook.prototype.select = function (index) {
630 if (this.is_valid_cell_index(index)) {
631 if (this.is_valid_cell_index(index)) {
631 var sindex = this.get_selected_index()
632 var sindex = this.get_selected_index()
632 if (sindex !== null && index !== sindex) {
633 if (sindex !== null && index !== sindex) {
633 this.get_cell(sindex).unselect();
634 this.get_cell(sindex).unselect();
634 };
635 };
635 var cell = this.get_cell(index);
636 var cell = this.get_cell(index);
636 cell.select();
637 cell.select();
637 if (cell.cell_type === 'heading') {
638 if (cell.cell_type === 'heading') {
638 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
639 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
639 {'cell_type':cell.cell_type,level:cell.level}
640 {'cell_type':cell.cell_type,level:cell.level}
640 );
641 );
641 } else {
642 } else {
642 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
643 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
643 {'cell_type':cell.cell_type}
644 {'cell_type':cell.cell_type}
644 );
645 );
645 };
646 };
646 };
647 };
647 return this;
648 return this;
648 };
649 };
649
650
650 /**
651 /**
651 * Programmatically select the next cell.
652 * Programmatically select the next cell.
652 *
653 *
653 * @method select_next
654 * @method select_next
654 * @return {Notebook} This notebook
655 * @return {Notebook} This notebook
655 */
656 */
656 Notebook.prototype.select_next = function () {
657 Notebook.prototype.select_next = function () {
657 var index = this.get_selected_index();
658 var index = this.get_selected_index();
658 this.select(index+1);
659 this.select(index+1);
659 return this;
660 return this;
660 };
661 };
661
662
662 /**
663 /**
663 * Programmatically select the previous cell.
664 * Programmatically select the previous cell.
664 *
665 *
665 * @method select_prev
666 * @method select_prev
666 * @return {Notebook} This notebook
667 * @return {Notebook} This notebook
667 */
668 */
668 Notebook.prototype.select_prev = function () {
669 Notebook.prototype.select_prev = function () {
669 var index = this.get_selected_index();
670 var index = this.get_selected_index();
670 this.select(index-1);
671 this.select(index-1);
671 return this;
672 return this;
672 };
673 };
673
674
674
675
675 // Cell movement
676 // Cell movement
676
677
677 /**
678 /**
678 * Move given (or selected) cell up and select it.
679 * Move given (or selected) cell up and select it.
679 *
680 *
680 * @method move_cell_up
681 * @method move_cell_up
681 * @param [index] {integer} cell index
682 * @param [index] {integer} cell index
682 * @return {Notebook} This notebook
683 * @return {Notebook} This notebook
683 **/
684 **/
684 Notebook.prototype.move_cell_up = function (index) {
685 Notebook.prototype.move_cell_up = function (index) {
685 var i = this.index_or_selected(index);
686 var i = this.index_or_selected(index);
686 if (this.is_valid_cell_index(i) && i > 0) {
687 if (this.is_valid_cell_index(i) && i > 0) {
687 var pivot = this.get_cell_element(i-1);
688 var pivot = this.get_cell_element(i-1);
688 var tomove = this.get_cell_element(i);
689 var tomove = this.get_cell_element(i);
689 if (pivot !== null && tomove !== null) {
690 if (pivot !== null && tomove !== null) {
690 tomove.detach();
691 tomove.detach();
691 pivot.before(tomove);
692 pivot.before(tomove);
692 this.select(i-1);
693 this.select(i-1);
693 };
694 };
694 this.set_dirty(true);
695 this.set_dirty(true);
695 };
696 };
696 return this;
697 return this;
697 };
698 };
698
699
699
700
700 /**
701 /**
701 * Move given (or selected) cell down and select it
702 * Move given (or selected) cell down and select it
702 *
703 *
703 * @method move_cell_down
704 * @method move_cell_down
704 * @param [index] {integer} cell index
705 * @param [index] {integer} cell index
705 * @return {Notebook} This notebook
706 * @return {Notebook} This notebook
706 **/
707 **/
707 Notebook.prototype.move_cell_down = function (index) {
708 Notebook.prototype.move_cell_down = function (index) {
708 var i = this.index_or_selected(index);
709 var i = this.index_or_selected(index);
709 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
710 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
710 var pivot = this.get_cell_element(i+1);
711 var pivot = this.get_cell_element(i+1);
711 var tomove = this.get_cell_element(i);
712 var tomove = this.get_cell_element(i);
712 if (pivot !== null && tomove !== null) {
713 if (pivot !== null && tomove !== null) {
713 tomove.detach();
714 tomove.detach();
714 pivot.after(tomove);
715 pivot.after(tomove);
715 this.select(i+1);
716 this.select(i+1);
716 };
717 };
717 };
718 };
718 this.set_dirty();
719 this.set_dirty();
719 return this;
720 return this;
720 };
721 };
721
722
722
723
723 // Insertion, deletion.
724 // Insertion, deletion.
724
725
725 /**
726 /**
726 * Delete a cell from the notebook.
727 * Delete a cell from the notebook.
727 *
728 *
728 * @method delete_cell
729 * @method delete_cell
729 * @param [index] A cell's numeric index
730 * @param [index] A cell's numeric index
730 * @return {Notebook} This notebook
731 * @return {Notebook} This notebook
731 */
732 */
732 Notebook.prototype.delete_cell = function (index) {
733 Notebook.prototype.delete_cell = function (index) {
733 var i = this.index_or_selected(index);
734 var i = this.index_or_selected(index);
734 var cell = this.get_selected_cell();
735 var cell = this.get_selected_cell();
735 this.undelete_backup = cell.toJSON();
736 this.undelete_backup = cell.toJSON();
736 $('#undelete_cell').removeClass('disabled');
737 $('#undelete_cell').removeClass('disabled');
737 if (this.is_valid_cell_index(i)) {
738 if (this.is_valid_cell_index(i)) {
738 var ce = this.get_cell_element(i);
739 var ce = this.get_cell_element(i);
739 ce.remove();
740 ce.remove();
740 if (i === (this.ncells())) {
741 if (i === (this.ncells())) {
741 this.select(i-1);
742 this.select(i-1);
742 this.undelete_index = i - 1;
743 this.undelete_index = i - 1;
743 this.undelete_below = true;
744 this.undelete_below = true;
744 } else {
745 } else {
745 this.select(i);
746 this.select(i);
746 this.undelete_index = i;
747 this.undelete_index = i;
747 this.undelete_below = false;
748 this.undelete_below = false;
748 };
749 };
749 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
750 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
750 this.set_dirty(true);
751 this.set_dirty(true);
751 };
752 };
752 return this;
753 return this;
753 };
754 };
754
755
755 /**
756 /**
756 * Insert a cell so that after insertion the cell is at given index.
757 * Insert a cell so that after insertion the cell is at given index.
757 *
758 *
758 * Similar to insert_above, but index parameter is mandatory
759 * Similar to insert_above, but index parameter is mandatory
759 *
760 *
760 * Index will be brought back into the accissible range [0,n]
761 * Index will be brought back into the accissible range [0,n]
761 *
762 *
762 * @method insert_cell_at_index
763 * @method insert_cell_at_index
763 * @param type {string} in ['code','markdown','heading']
764 * @param type {string} in ['code','markdown','heading']
764 * @param [index] {int} a valid index where to inser cell
765 * @param [index] {int} a valid index where to inser cell
765 *
766 *
766 * @return cell {cell|null} created cell or null
767 * @return cell {cell|null} created cell or null
767 **/
768 **/
768 Notebook.prototype.insert_cell_at_index = function(type, index){
769 Notebook.prototype.insert_cell_at_index = function(type, index){
769
770
770 var ncells = this.ncells();
771 var ncells = this.ncells();
771 var index = Math.min(index,ncells);
772 var index = Math.min(index,ncells);
772 index = Math.max(index,0);
773 index = Math.max(index,0);
773 var cell = null;
774 var cell = null;
774
775
775 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
776 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
776 if (type === 'code') {
777 if (type === 'code') {
777 cell = new IPython.CodeCell(this.kernel);
778 cell = new IPython.CodeCell(this.kernel);
778 cell.set_input_prompt();
779 cell.set_input_prompt();
779 } else if (type === 'markdown') {
780 } else if (type === 'markdown') {
780 cell = new IPython.MarkdownCell();
781 cell = new IPython.MarkdownCell();
781 } else if (type === 'raw') {
782 } else if (type === 'raw') {
782 cell = new IPython.RawCell();
783 cell = new IPython.RawCell();
783 } else if (type === 'heading') {
784 } else if (type === 'heading') {
784 cell = new IPython.HeadingCell();
785 cell = new IPython.HeadingCell();
785 }
786 }
786
787
787 if(this._insert_element_at_index(cell.element,index)){
788 if(this._insert_element_at_index(cell.element,index)){
788 cell.render();
789 cell.render();
789 this.select(this.find_cell_index(cell));
790 this.select(this.find_cell_index(cell));
790 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
791 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
791 this.set_dirty(true);
792 this.set_dirty(true);
792 }
793 }
793 }
794 }
794 return cell;
795 return cell;
795
796
796 };
797 };
797
798
798 /**
799 /**
799 * Insert an element at given cell index.
800 * Insert an element at given cell index.
800 *
801 *
801 * @method _insert_element_at_index
802 * @method _insert_element_at_index
802 * @param element {dom element} a cell element
803 * @param element {dom element} a cell element
803 * @param [index] {int} a valid index where to inser cell
804 * @param [index] {int} a valid index where to inser cell
804 * @private
805 * @private
805 *
806 *
806 * return true if everything whent fine.
807 * return true if everything whent fine.
807 **/
808 **/
808 Notebook.prototype._insert_element_at_index = function(element, index){
809 Notebook.prototype._insert_element_at_index = function(element, index){
809 if (element === undefined){
810 if (element === undefined){
810 return false;
811 return false;
811 }
812 }
812
813
813 var ncells = this.ncells();
814 var ncells = this.ncells();
814
815
815 if (ncells === 0) {
816 if (ncells === 0) {
816 // special case append if empty
817 // special case append if empty
817 this.element.find('div.end_space').before(element);
818 this.element.find('div.end_space').before(element);
818 } else if ( ncells === index ) {
819 } else if ( ncells === index ) {
819 // special case append it the end, but not empty
820 // special case append it the end, but not empty
820 this.get_cell_element(index-1).after(element);
821 this.get_cell_element(index-1).after(element);
821 } else if (this.is_valid_cell_index(index)) {
822 } else if (this.is_valid_cell_index(index)) {
822 // otherwise always somewhere to append to
823 // otherwise always somewhere to append to
823 this.get_cell_element(index).before(element);
824 this.get_cell_element(index).before(element);
824 } else {
825 } else {
825 return false;
826 return false;
826 }
827 }
827
828
828 if (this.undelete_index !== null && index <= this.undelete_index) {
829 if (this.undelete_index !== null && index <= this.undelete_index) {
829 this.undelete_index = this.undelete_index + 1;
830 this.undelete_index = this.undelete_index + 1;
830 this.set_dirty(true);
831 this.set_dirty(true);
831 }
832 }
832 return true;
833 return true;
833 };
834 };
834
835
835 /**
836 /**
836 * Insert a cell of given type above given index, or at top
837 * Insert a cell of given type above given index, or at top
837 * of notebook if index smaller than 0.
838 * of notebook if index smaller than 0.
838 *
839 *
839 * default index value is the one of currently selected cell
840 * default index value is the one of currently selected cell
840 *
841 *
841 * @method insert_cell_above
842 * @method insert_cell_above
842 * @param type {string} cell type
843 * @param type {string} cell type
843 * @param [index] {integer}
844 * @param [index] {integer}
844 *
845 *
845 * @return handle to created cell or null
846 * @return handle to created cell or null
846 **/
847 **/
847 Notebook.prototype.insert_cell_above = function (type, index) {
848 Notebook.prototype.insert_cell_above = function (type, index) {
848 index = this.index_or_selected(index);
849 index = this.index_or_selected(index);
849 return this.insert_cell_at_index(type, index);
850 return this.insert_cell_at_index(type, index);
850 };
851 };
851
852
852 /**
853 /**
853 * Insert a cell of given type below given index, or at bottom
854 * Insert a cell of given type below given index, or at bottom
854 * of notebook if index greater thatn number of cell
855 * of notebook if index greater thatn number of cell
855 *
856 *
856 * default index value is the one of currently selected cell
857 * default index value is the one of currently selected cell
857 *
858 *
858 * @method insert_cell_below
859 * @method insert_cell_below
859 * @param type {string} cell type
860 * @param type {string} cell type
860 * @param [index] {integer}
861 * @param [index] {integer}
861 *
862 *
862 * @return handle to created cell or null
863 * @return handle to created cell or null
863 *
864 *
864 **/
865 **/
865 Notebook.prototype.insert_cell_below = function (type, index) {
866 Notebook.prototype.insert_cell_below = function (type, index) {
866 index = this.index_or_selected(index);
867 index = this.index_or_selected(index);
867 return this.insert_cell_at_index(type, index+1);
868 return this.insert_cell_at_index(type, index+1);
868 };
869 };
869
870
870
871
871 /**
872 /**
872 * Insert cell at end of notebook
873 * Insert cell at end of notebook
873 *
874 *
874 * @method insert_cell_at_bottom
875 * @method insert_cell_at_bottom
875 * @param {String} type cell type
876 * @param {String} type cell type
876 *
877 *
877 * @return the added cell; or null
878 * @return the added cell; or null
878 **/
879 **/
879 Notebook.prototype.insert_cell_at_bottom = function (type){
880 Notebook.prototype.insert_cell_at_bottom = function (type){
880 var len = this.ncells();
881 var len = this.ncells();
881 return this.insert_cell_below(type,len-1);
882 return this.insert_cell_below(type,len-1);
882 };
883 };
883
884
884 /**
885 /**
885 * Turn a cell into a code cell.
886 * Turn a cell into a code cell.
886 *
887 *
887 * @method to_code
888 * @method to_code
888 * @param {Number} [index] A cell's index
889 * @param {Number} [index] A cell's index
889 */
890 */
890 Notebook.prototype.to_code = function (index) {
891 Notebook.prototype.to_code = function (index) {
891 var i = this.index_or_selected(index);
892 var i = this.index_or_selected(index);
892 if (this.is_valid_cell_index(i)) {
893 if (this.is_valid_cell_index(i)) {
893 var source_element = this.get_cell_element(i);
894 var source_element = this.get_cell_element(i);
894 var source_cell = source_element.data("cell");
895 var source_cell = source_element.data("cell");
895 if (!(source_cell instanceof IPython.CodeCell)) {
896 if (!(source_cell instanceof IPython.CodeCell)) {
896 var target_cell = this.insert_cell_below('code',i);
897 var target_cell = this.insert_cell_below('code',i);
897 var text = source_cell.get_text();
898 var text = source_cell.get_text();
898 if (text === source_cell.placeholder) {
899 if (text === source_cell.placeholder) {
899 text = '';
900 text = '';
900 }
901 }
901 target_cell.set_text(text);
902 target_cell.set_text(text);
902 // make this value the starting point, so that we can only undo
903 // make this value the starting point, so that we can only undo
903 // to this state, instead of a blank cell
904 // to this state, instead of a blank cell
904 target_cell.code_mirror.clearHistory();
905 target_cell.code_mirror.clearHistory();
905 source_element.remove();
906 source_element.remove();
906 this.set_dirty(true);
907 this.set_dirty(true);
907 };
908 };
908 };
909 };
909 };
910 };
910
911
911 /**
912 /**
912 * Turn a cell into a Markdown cell.
913 * Turn a cell into a Markdown cell.
913 *
914 *
914 * @method to_markdown
915 * @method to_markdown
915 * @param {Number} [index] A cell's index
916 * @param {Number} [index] A cell's index
916 */
917 */
917 Notebook.prototype.to_markdown = function (index) {
918 Notebook.prototype.to_markdown = function (index) {
918 var i = this.index_or_selected(index);
919 var i = this.index_or_selected(index);
919 if (this.is_valid_cell_index(i)) {
920 if (this.is_valid_cell_index(i)) {
920 var source_element = this.get_cell_element(i);
921 var source_element = this.get_cell_element(i);
921 var source_cell = source_element.data("cell");
922 var source_cell = source_element.data("cell");
922 if (!(source_cell instanceof IPython.MarkdownCell)) {
923 if (!(source_cell instanceof IPython.MarkdownCell)) {
923 var target_cell = this.insert_cell_below('markdown',i);
924 var target_cell = this.insert_cell_below('markdown',i);
924 var text = source_cell.get_text();
925 var text = source_cell.get_text();
925 if (text === source_cell.placeholder) {
926 if (text === source_cell.placeholder) {
926 text = '';
927 text = '';
927 };
928 };
928 // The edit must come before the set_text.
929 // The edit must come before the set_text.
929 target_cell.edit();
930 target_cell.edit();
930 target_cell.set_text(text);
931 target_cell.set_text(text);
931 // make this value the starting point, so that we can only undo
932 // make this value the starting point, so that we can only undo
932 // to this state, instead of a blank cell
933 // to this state, instead of a blank cell
933 target_cell.code_mirror.clearHistory();
934 target_cell.code_mirror.clearHistory();
934 source_element.remove();
935 source_element.remove();
935 this.set_dirty(true);
936 this.set_dirty(true);
936 };
937 };
937 };
938 };
938 };
939 };
939
940
940 /**
941 /**
941 * Turn a cell into a raw text cell.
942 * Turn a cell into a raw text cell.
942 *
943 *
943 * @method to_raw
944 * @method to_raw
944 * @param {Number} [index] A cell's index
945 * @param {Number} [index] A cell's index
945 */
946 */
946 Notebook.prototype.to_raw = function (index) {
947 Notebook.prototype.to_raw = function (index) {
947 var i = this.index_or_selected(index);
948 var i = this.index_or_selected(index);
948 if (this.is_valid_cell_index(i)) {
949 if (this.is_valid_cell_index(i)) {
949 var source_element = this.get_cell_element(i);
950 var source_element = this.get_cell_element(i);
950 var source_cell = source_element.data("cell");
951 var source_cell = source_element.data("cell");
951 var target_cell = null;
952 var target_cell = null;
952 if (!(source_cell instanceof IPython.RawCell)) {
953 if (!(source_cell instanceof IPython.RawCell)) {
953 target_cell = this.insert_cell_below('raw',i);
954 target_cell = this.insert_cell_below('raw',i);
954 var text = source_cell.get_text();
955 var text = source_cell.get_text();
955 if (text === source_cell.placeholder) {
956 if (text === source_cell.placeholder) {
956 text = '';
957 text = '';
957 };
958 };
958 // The edit must come before the set_text.
959 // The edit must come before the set_text.
959 target_cell.edit();
960 target_cell.edit();
960 target_cell.set_text(text);
961 target_cell.set_text(text);
961 // make this value the starting point, so that we can only undo
962 // make this value the starting point, so that we can only undo
962 // to this state, instead of a blank cell
963 // to this state, instead of a blank cell
963 target_cell.code_mirror.clearHistory();
964 target_cell.code_mirror.clearHistory();
964 source_element.remove();
965 source_element.remove();
965 this.set_dirty(true);
966 this.set_dirty(true);
966 };
967 };
967 };
968 };
968 };
969 };
969
970
970 /**
971 /**
971 * Turn a cell into a heading cell.
972 * Turn a cell into a heading cell.
972 *
973 *
973 * @method to_heading
974 * @method to_heading
974 * @param {Number} [index] A cell's index
975 * @param {Number} [index] A cell's index
975 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
976 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
976 */
977 */
977 Notebook.prototype.to_heading = function (index, level) {
978 Notebook.prototype.to_heading = function (index, level) {
978 level = level || 1;
979 level = level || 1;
979 var i = this.index_or_selected(index);
980 var i = this.index_or_selected(index);
980 if (this.is_valid_cell_index(i)) {
981 if (this.is_valid_cell_index(i)) {
981 var source_element = this.get_cell_element(i);
982 var source_element = this.get_cell_element(i);
982 var source_cell = source_element.data("cell");
983 var source_cell = source_element.data("cell");
983 var target_cell = null;
984 var target_cell = null;
984 if (source_cell instanceof IPython.HeadingCell) {
985 if (source_cell instanceof IPython.HeadingCell) {
985 source_cell.set_level(level);
986 source_cell.set_level(level);
986 } else {
987 } else {
987 target_cell = this.insert_cell_below('heading',i);
988 target_cell = this.insert_cell_below('heading',i);
988 var text = source_cell.get_text();
989 var text = source_cell.get_text();
989 if (text === source_cell.placeholder) {
990 if (text === source_cell.placeholder) {
990 text = '';
991 text = '';
991 };
992 };
992 // The edit must come before the set_text.
993 // The edit must come before the set_text.
993 target_cell.set_level(level);
994 target_cell.set_level(level);
994 target_cell.edit();
995 target_cell.edit();
995 target_cell.set_text(text);
996 target_cell.set_text(text);
996 // make this value the starting point, so that we can only undo
997 // make this value the starting point, so that we can only undo
997 // to this state, instead of a blank cell
998 // to this state, instead of a blank cell
998 target_cell.code_mirror.clearHistory();
999 target_cell.code_mirror.clearHistory();
999 source_element.remove();
1000 source_element.remove();
1000 this.set_dirty(true);
1001 this.set_dirty(true);
1001 };
1002 };
1002 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1003 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1003 {'cell_type':'heading',level:level}
1004 {'cell_type':'heading',level:level}
1004 );
1005 );
1005 };
1006 };
1006 };
1007 };
1007
1008
1008
1009
1009 // Cut/Copy/Paste
1010 // Cut/Copy/Paste
1010
1011
1011 /**
1012 /**
1012 * Enable UI elements for pasting cells.
1013 * Enable UI elements for pasting cells.
1013 *
1014 *
1014 * @method enable_paste
1015 * @method enable_paste
1015 */
1016 */
1016 Notebook.prototype.enable_paste = function () {
1017 Notebook.prototype.enable_paste = function () {
1017 var that = this;
1018 var that = this;
1018 if (!this.paste_enabled) {
1019 if (!this.paste_enabled) {
1019 $('#paste_cell_replace').removeClass('disabled')
1020 $('#paste_cell_replace').removeClass('disabled')
1020 .on('click', function () {that.paste_cell_replace();});
1021 .on('click', function () {that.paste_cell_replace();});
1021 $('#paste_cell_above').removeClass('disabled')
1022 $('#paste_cell_above').removeClass('disabled')
1022 .on('click', function () {that.paste_cell_above();});
1023 .on('click', function () {that.paste_cell_above();});
1023 $('#paste_cell_below').removeClass('disabled')
1024 $('#paste_cell_below').removeClass('disabled')
1024 .on('click', function () {that.paste_cell_below();});
1025 .on('click', function () {that.paste_cell_below();});
1025 this.paste_enabled = true;
1026 this.paste_enabled = true;
1026 };
1027 };
1027 };
1028 };
1028
1029
1029 /**
1030 /**
1030 * Disable UI elements for pasting cells.
1031 * Disable UI elements for pasting cells.
1031 *
1032 *
1032 * @method disable_paste
1033 * @method disable_paste
1033 */
1034 */
1034 Notebook.prototype.disable_paste = function () {
1035 Notebook.prototype.disable_paste = function () {
1035 if (this.paste_enabled) {
1036 if (this.paste_enabled) {
1036 $('#paste_cell_replace').addClass('disabled').off('click');
1037 $('#paste_cell_replace').addClass('disabled').off('click');
1037 $('#paste_cell_above').addClass('disabled').off('click');
1038 $('#paste_cell_above').addClass('disabled').off('click');
1038 $('#paste_cell_below').addClass('disabled').off('click');
1039 $('#paste_cell_below').addClass('disabled').off('click');
1039 this.paste_enabled = false;
1040 this.paste_enabled = false;
1040 };
1041 };
1041 };
1042 };
1042
1043
1043 /**
1044 /**
1044 * Cut a cell.
1045 * Cut a cell.
1045 *
1046 *
1046 * @method cut_cell
1047 * @method cut_cell
1047 */
1048 */
1048 Notebook.prototype.cut_cell = function () {
1049 Notebook.prototype.cut_cell = function () {
1049 this.copy_cell();
1050 this.copy_cell();
1050 this.delete_cell();
1051 this.delete_cell();
1051 }
1052 }
1052
1053
1053 /**
1054 /**
1054 * Copy a cell.
1055 * Copy a cell.
1055 *
1056 *
1056 * @method copy_cell
1057 * @method copy_cell
1057 */
1058 */
1058 Notebook.prototype.copy_cell = function () {
1059 Notebook.prototype.copy_cell = function () {
1059 var cell = this.get_selected_cell();
1060 var cell = this.get_selected_cell();
1060 this.clipboard = cell.toJSON();
1061 this.clipboard = cell.toJSON();
1061 this.enable_paste();
1062 this.enable_paste();
1062 };
1063 };
1063
1064
1064 /**
1065 /**
1065 * Replace the selected cell with a cell in the clipboard.
1066 * Replace the selected cell with a cell in the clipboard.
1066 *
1067 *
1067 * @method paste_cell_replace
1068 * @method paste_cell_replace
1068 */
1069 */
1069 Notebook.prototype.paste_cell_replace = function () {
1070 Notebook.prototype.paste_cell_replace = function () {
1070 if (this.clipboard !== null && this.paste_enabled) {
1071 if (this.clipboard !== null && this.paste_enabled) {
1071 var cell_data = this.clipboard;
1072 var cell_data = this.clipboard;
1072 var new_cell = this.insert_cell_above(cell_data.cell_type);
1073 var new_cell = this.insert_cell_above(cell_data.cell_type);
1073 new_cell.fromJSON(cell_data);
1074 new_cell.fromJSON(cell_data);
1074 var old_cell = this.get_next_cell(new_cell);
1075 var old_cell = this.get_next_cell(new_cell);
1075 this.delete_cell(this.find_cell_index(old_cell));
1076 this.delete_cell(this.find_cell_index(old_cell));
1076 this.select(this.find_cell_index(new_cell));
1077 this.select(this.find_cell_index(new_cell));
1077 };
1078 };
1078 };
1079 };
1079
1080
1080 /**
1081 /**
1081 * Paste a cell from the clipboard above the selected cell.
1082 * Paste a cell from the clipboard above the selected cell.
1082 *
1083 *
1083 * @method paste_cell_above
1084 * @method paste_cell_above
1084 */
1085 */
1085 Notebook.prototype.paste_cell_above = function () {
1086 Notebook.prototype.paste_cell_above = function () {
1086 if (this.clipboard !== null && this.paste_enabled) {
1087 if (this.clipboard !== null && this.paste_enabled) {
1087 var cell_data = this.clipboard;
1088 var cell_data = this.clipboard;
1088 var new_cell = this.insert_cell_above(cell_data.cell_type);
1089 var new_cell = this.insert_cell_above(cell_data.cell_type);
1089 new_cell.fromJSON(cell_data);
1090 new_cell.fromJSON(cell_data);
1090 };
1091 };
1091 };
1092 };
1092
1093
1093 /**
1094 /**
1094 * Paste a cell from the clipboard below the selected cell.
1095 * Paste a cell from the clipboard below the selected cell.
1095 *
1096 *
1096 * @method paste_cell_below
1097 * @method paste_cell_below
1097 */
1098 */
1098 Notebook.prototype.paste_cell_below = function () {
1099 Notebook.prototype.paste_cell_below = function () {
1099 if (this.clipboard !== null && this.paste_enabled) {
1100 if (this.clipboard !== null && this.paste_enabled) {
1100 var cell_data = this.clipboard;
1101 var cell_data = this.clipboard;
1101 var new_cell = this.insert_cell_below(cell_data.cell_type);
1102 var new_cell = this.insert_cell_below(cell_data.cell_type);
1102 new_cell.fromJSON(cell_data);
1103 new_cell.fromJSON(cell_data);
1103 };
1104 };
1104 };
1105 };
1105
1106
1106 // Cell undelete
1107 // Cell undelete
1107
1108
1108 /**
1109 /**
1109 * Restore the most recently deleted cell.
1110 * Restore the most recently deleted cell.
1110 *
1111 *
1111 * @method undelete
1112 * @method undelete
1112 */
1113 */
1113 Notebook.prototype.undelete = function() {
1114 Notebook.prototype.undelete = function() {
1114 if (this.undelete_backup !== null && this.undelete_index !== null) {
1115 if (this.undelete_backup !== null && this.undelete_index !== null) {
1115 var current_index = this.get_selected_index();
1116 var current_index = this.get_selected_index();
1116 if (this.undelete_index < current_index) {
1117 if (this.undelete_index < current_index) {
1117 current_index = current_index + 1;
1118 current_index = current_index + 1;
1118 }
1119 }
1119 if (this.undelete_index >= this.ncells()) {
1120 if (this.undelete_index >= this.ncells()) {
1120 this.select(this.ncells() - 1);
1121 this.select(this.ncells() - 1);
1121 }
1122 }
1122 else {
1123 else {
1123 this.select(this.undelete_index);
1124 this.select(this.undelete_index);
1124 }
1125 }
1125 var cell_data = this.undelete_backup;
1126 var cell_data = this.undelete_backup;
1126 var new_cell = null;
1127 var new_cell = null;
1127 if (this.undelete_below) {
1128 if (this.undelete_below) {
1128 new_cell = this.insert_cell_below(cell_data.cell_type);
1129 new_cell = this.insert_cell_below(cell_data.cell_type);
1129 } else {
1130 } else {
1130 new_cell = this.insert_cell_above(cell_data.cell_type);
1131 new_cell = this.insert_cell_above(cell_data.cell_type);
1131 }
1132 }
1132 new_cell.fromJSON(cell_data);
1133 new_cell.fromJSON(cell_data);
1133 this.select(current_index);
1134 this.select(current_index);
1134 this.undelete_backup = null;
1135 this.undelete_backup = null;
1135 this.undelete_index = null;
1136 this.undelete_index = null;
1136 }
1137 }
1137 $('#undelete_cell').addClass('disabled');
1138 $('#undelete_cell').addClass('disabled');
1138 }
1139 }
1139
1140
1140 // Split/merge
1141 // Split/merge
1141
1142
1142 /**
1143 /**
1143 * Split the selected cell into two, at the cursor.
1144 * Split the selected cell into two, at the cursor.
1144 *
1145 *
1145 * @method split_cell
1146 * @method split_cell
1146 */
1147 */
1147 Notebook.prototype.split_cell = function () {
1148 Notebook.prototype.split_cell = function () {
1148 // Todo: implement spliting for other cell types.
1149 // Todo: implement spliting for other cell types.
1149 var cell = this.get_selected_cell();
1150 var cell = this.get_selected_cell();
1150 if (cell.is_splittable()) {
1151 if (cell.is_splittable()) {
1151 var texta = cell.get_pre_cursor();
1152 var texta = cell.get_pre_cursor();
1152 var textb = cell.get_post_cursor();
1153 var textb = cell.get_post_cursor();
1153 if (cell instanceof IPython.CodeCell) {
1154 if (cell instanceof IPython.CodeCell) {
1154 cell.set_text(texta);
1155 cell.set_text(texta);
1155 var new_cell = this.insert_cell_below('code');
1156 var new_cell = this.insert_cell_below('code');
1156 new_cell.set_text(textb);
1157 new_cell.set_text(textb);
1157 } else if (cell instanceof IPython.MarkdownCell) {
1158 } else if (cell instanceof IPython.MarkdownCell) {
1158 cell.set_text(texta);
1159 cell.set_text(texta);
1159 cell.render();
1160 cell.render();
1160 var new_cell = this.insert_cell_below('markdown');
1161 var new_cell = this.insert_cell_below('markdown');
1161 new_cell.edit(); // editor must be visible to call set_text
1162 new_cell.edit(); // editor must be visible to call set_text
1162 new_cell.set_text(textb);
1163 new_cell.set_text(textb);
1163 new_cell.render();
1164 new_cell.render();
1164 }
1165 }
1165 };
1166 };
1166 };
1167 };
1167
1168
1168 /**
1169 /**
1169 * Combine the selected cell into the cell above it.
1170 * Combine the selected cell into the cell above it.
1170 *
1171 *
1171 * @method merge_cell_above
1172 * @method merge_cell_above
1172 */
1173 */
1173 Notebook.prototype.merge_cell_above = function () {
1174 Notebook.prototype.merge_cell_above = function () {
1174 var index = this.get_selected_index();
1175 var index = this.get_selected_index();
1175 var cell = this.get_cell(index);
1176 var cell = this.get_cell(index);
1176 if (index > 0) {
1177 if (index > 0) {
1177 var upper_cell = this.get_cell(index-1);
1178 var upper_cell = this.get_cell(index-1);
1178 var upper_text = upper_cell.get_text();
1179 var upper_text = upper_cell.get_text();
1179 var text = cell.get_text();
1180 var text = cell.get_text();
1180 if (cell instanceof IPython.CodeCell) {
1181 if (cell instanceof IPython.CodeCell) {
1181 cell.set_text(upper_text+'\n'+text);
1182 cell.set_text(upper_text+'\n'+text);
1182 } else if (cell instanceof IPython.MarkdownCell) {
1183 } else if (cell instanceof IPython.MarkdownCell) {
1183 cell.edit();
1184 cell.edit();
1184 cell.set_text(upper_text+'\n'+text);
1185 cell.set_text(upper_text+'\n'+text);
1185 cell.render();
1186 cell.render();
1186 };
1187 };
1187 this.delete_cell(index-1);
1188 this.delete_cell(index-1);
1188 this.select(this.find_cell_index(cell));
1189 this.select(this.find_cell_index(cell));
1189 };
1190 };
1190 };
1191 };
1191
1192
1192 /**
1193 /**
1193 * Combine the selected cell into the cell below it.
1194 * Combine the selected cell into the cell below it.
1194 *
1195 *
1195 * @method merge_cell_below
1196 * @method merge_cell_below
1196 */
1197 */
1197 Notebook.prototype.merge_cell_below = function () {
1198 Notebook.prototype.merge_cell_below = function () {
1198 var index = this.get_selected_index();
1199 var index = this.get_selected_index();
1199 var cell = this.get_cell(index);
1200 var cell = this.get_cell(index);
1200 if (index < this.ncells()-1) {
1201 if (index < this.ncells()-1) {
1201 var lower_cell = this.get_cell(index+1);
1202 var lower_cell = this.get_cell(index+1);
1202 var lower_text = lower_cell.get_text();
1203 var lower_text = lower_cell.get_text();
1203 var text = cell.get_text();
1204 var text = cell.get_text();
1204 if (cell instanceof IPython.CodeCell) {
1205 if (cell instanceof IPython.CodeCell) {
1205 cell.set_text(text+'\n'+lower_text);
1206 cell.set_text(text+'\n'+lower_text);
1206 } else if (cell instanceof IPython.MarkdownCell) {
1207 } else if (cell instanceof IPython.MarkdownCell) {
1207 cell.edit();
1208 cell.edit();
1208 cell.set_text(text+'\n'+lower_text);
1209 cell.set_text(text+'\n'+lower_text);
1209 cell.render();
1210 cell.render();
1210 };
1211 };
1211 this.delete_cell(index+1);
1212 this.delete_cell(index+1);
1212 this.select(this.find_cell_index(cell));
1213 this.select(this.find_cell_index(cell));
1213 };
1214 };
1214 };
1215 };
1215
1216
1216
1217
1217 // Cell collapsing and output clearing
1218 // Cell collapsing and output clearing
1218
1219
1219 /**
1220 /**
1220 * Hide a cell's output.
1221 * Hide a cell's output.
1221 *
1222 *
1222 * @method collapse
1223 * @method collapse
1223 * @param {Number} index A cell's numeric index
1224 * @param {Number} index A cell's numeric index
1224 */
1225 */
1225 Notebook.prototype.collapse = function (index) {
1226 Notebook.prototype.collapse = function (index) {
1226 var i = this.index_or_selected(index);
1227 var i = this.index_or_selected(index);
1227 this.get_cell(i).collapse();
1228 this.get_cell(i).collapse();
1228 this.set_dirty(true);
1229 this.set_dirty(true);
1229 };
1230 };
1230
1231
1231 /**
1232 /**
1232 * Show a cell's output.
1233 * Show a cell's output.
1233 *
1234 *
1234 * @method expand
1235 * @method expand
1235 * @param {Number} index A cell's numeric index
1236 * @param {Number} index A cell's numeric index
1236 */
1237 */
1237 Notebook.prototype.expand = function (index) {
1238 Notebook.prototype.expand = function (index) {
1238 var i = this.index_or_selected(index);
1239 var i = this.index_or_selected(index);
1239 this.get_cell(i).expand();
1240 this.get_cell(i).expand();
1240 this.set_dirty(true);
1241 this.set_dirty(true);
1241 };
1242 };
1242
1243
1243 /** Toggle whether a cell's output is collapsed or expanded.
1244 /** Toggle whether a cell's output is collapsed or expanded.
1244 *
1245 *
1245 * @method toggle_output
1246 * @method toggle_output
1246 * @param {Number} index A cell's numeric index
1247 * @param {Number} index A cell's numeric index
1247 */
1248 */
1248 Notebook.prototype.toggle_output = function (index) {
1249 Notebook.prototype.toggle_output = function (index) {
1249 var i = this.index_or_selected(index);
1250 var i = this.index_or_selected(index);
1250 this.get_cell(i).toggle_output();
1251 this.get_cell(i).toggle_output();
1251 this.set_dirty(true);
1252 this.set_dirty(true);
1252 };
1253 };
1253
1254
1254 /**
1255 /**
1255 * Toggle a scrollbar for long cell outputs.
1256 * Toggle a scrollbar for long cell outputs.
1256 *
1257 *
1257 * @method toggle_output_scroll
1258 * @method toggle_output_scroll
1258 * @param {Number} index A cell's numeric index
1259 * @param {Number} index A cell's numeric index
1259 */
1260 */
1260 Notebook.prototype.toggle_output_scroll = function (index) {
1261 Notebook.prototype.toggle_output_scroll = function (index) {
1261 var i = this.index_or_selected(index);
1262 var i = this.index_or_selected(index);
1262 this.get_cell(i).toggle_output_scroll();
1263 this.get_cell(i).toggle_output_scroll();
1263 };
1264 };
1264
1265
1265 /**
1266 /**
1266 * Hide each code cell's output area.
1267 * Hide each code cell's output area.
1267 *
1268 *
1268 * @method collapse_all_output
1269 * @method collapse_all_output
1269 */
1270 */
1270 Notebook.prototype.collapse_all_output = function () {
1271 Notebook.prototype.collapse_all_output = function () {
1271 var ncells = this.ncells();
1272 var ncells = this.ncells();
1272 var cells = this.get_cells();
1273 var cells = this.get_cells();
1273 for (var i=0; i<ncells; i++) {
1274 for (var i=0; i<ncells; i++) {
1274 if (cells[i] instanceof IPython.CodeCell) {
1275 if (cells[i] instanceof IPython.CodeCell) {
1275 cells[i].output_area.collapse();
1276 cells[i].output_area.collapse();
1276 }
1277 }
1277 };
1278 };
1278 // this should not be set if the `collapse` key is removed from nbformat
1279 // this should not be set if the `collapse` key is removed from nbformat
1279 this.set_dirty(true);
1280 this.set_dirty(true);
1280 };
1281 };
1281
1282
1282 /**
1283 /**
1283 * Expand each code cell's output area, and add a scrollbar for long output.
1284 * Expand each code cell's output area, and add a scrollbar for long output.
1284 *
1285 *
1285 * @method scroll_all_output
1286 * @method scroll_all_output
1286 */
1287 */
1287 Notebook.prototype.scroll_all_output = function () {
1288 Notebook.prototype.scroll_all_output = function () {
1288 var ncells = this.ncells();
1289 var ncells = this.ncells();
1289 var cells = this.get_cells();
1290 var cells = this.get_cells();
1290 for (var i=0; i<ncells; i++) {
1291 for (var i=0; i<ncells; i++) {
1291 if (cells[i] instanceof IPython.CodeCell) {
1292 if (cells[i] instanceof IPython.CodeCell) {
1292 cells[i].output_area.expand();
1293 cells[i].output_area.expand();
1293 cells[i].output_area.scroll_if_long();
1294 cells[i].output_area.scroll_if_long();
1294 }
1295 }
1295 };
1296 };
1296 // this should not be set if the `collapse` key is removed from nbformat
1297 // this should not be set if the `collapse` key is removed from nbformat
1297 this.set_dirty(true);
1298 this.set_dirty(true);
1298 };
1299 };
1299
1300
1300 /**
1301 /**
1301 * Expand each code cell's output area, and remove scrollbars.
1302 * Expand each code cell's output area, and remove scrollbars.
1302 *
1303 *
1303 * @method expand_all_output
1304 * @method expand_all_output
1304 */
1305 */
1305 Notebook.prototype.expand_all_output = function () {
1306 Notebook.prototype.expand_all_output = function () {
1306 var ncells = this.ncells();
1307 var ncells = this.ncells();
1307 var cells = this.get_cells();
1308 var cells = this.get_cells();
1308 for (var i=0; i<ncells; i++) {
1309 for (var i=0; i<ncells; i++) {
1309 if (cells[i] instanceof IPython.CodeCell) {
1310 if (cells[i] instanceof IPython.CodeCell) {
1310 cells[i].output_area.expand();
1311 cells[i].output_area.expand();
1311 cells[i].output_area.unscroll_area();
1312 cells[i].output_area.unscroll_area();
1312 }
1313 }
1313 };
1314 };
1314 // this should not be set if the `collapse` key is removed from nbformat
1315 // this should not be set if the `collapse` key is removed from nbformat
1315 this.set_dirty(true);
1316 this.set_dirty(true);
1316 };
1317 };
1317
1318
1318 /**
1319 /**
1319 * Clear each code cell's output area.
1320 * Clear each code cell's output area.
1320 *
1321 *
1321 * @method clear_all_output
1322 * @method clear_all_output
1322 */
1323 */
1323 Notebook.prototype.clear_all_output = function () {
1324 Notebook.prototype.clear_all_output = function () {
1324 var ncells = this.ncells();
1325 var ncells = this.ncells();
1325 var cells = this.get_cells();
1326 var cells = this.get_cells();
1326 for (var i=0; i<ncells; i++) {
1327 for (var i=0; i<ncells; i++) {
1327 if (cells[i] instanceof IPython.CodeCell) {
1328 if (cells[i] instanceof IPython.CodeCell) {
1328 cells[i].clear_output(true,true,true);
1329 cells[i].clear_output(true,true,true);
1329 // Make all In[] prompts blank, as well
1330 // Make all In[] prompts blank, as well
1330 // TODO: make this configurable (via checkbox?)
1331 // TODO: make this configurable (via checkbox?)
1331 cells[i].set_input_prompt();
1332 cells[i].set_input_prompt();
1332 }
1333 }
1333 };
1334 };
1334 this.set_dirty(true);
1335 this.set_dirty(true);
1335 };
1336 };
1336
1337
1337
1338
1338 // Other cell functions: line numbers, ...
1339 // Other cell functions: line numbers, ...
1339
1340
1340 /**
1341 /**
1341 * Toggle line numbers in the selected cell's input area.
1342 * Toggle line numbers in the selected cell's input area.
1342 *
1343 *
1343 * @method cell_toggle_line_numbers
1344 * @method cell_toggle_line_numbers
1344 */
1345 */
1345 Notebook.prototype.cell_toggle_line_numbers = function() {
1346 Notebook.prototype.cell_toggle_line_numbers = function() {
1346 this.get_selected_cell().toggle_line_numbers();
1347 this.get_selected_cell().toggle_line_numbers();
1347 };
1348 };
1348
1349
1349 // Kernel related things
1350 // Kernel related things
1350
1351
1351 /**
1352 /**
1352 * Start a new kernel and set it on each code cell.
1353 * Start a new kernel and set it on each code cell.
1353 *
1354 *
1354 * @method start_kernel
1355 * @method start_kernel
1355 */
1356 */
1356 Notebook.prototype.start_kernel = function () {
1357 Notebook.prototype.start_kernel = function () {
1357 var base_url = $('body').data('baseKernelUrl') + "kernels";
1358 var base_url = $('body').data('baseKernelUrl') + "kernels";
1358 this.kernel = new IPython.Kernel(base_url);
1359 this.kernel = new IPython.Kernel(base_url);
1359 this.kernel.start(this.notebook_id);
1360 this.kernel.start(this.notebook_id);
1360 // Now that the kernel has been created, tell the CodeCells about it.
1361 // Now that the kernel has been created, tell the CodeCells about it.
1361 var ncells = this.ncells();
1362 var ncells = this.ncells();
1362 for (var i=0; i<ncells; i++) {
1363 for (var i=0; i<ncells; i++) {
1363 var cell = this.get_cell(i);
1364 var cell = this.get_cell(i);
1364 if (cell instanceof IPython.CodeCell) {
1365 if (cell instanceof IPython.CodeCell) {
1365 cell.set_kernel(this.kernel)
1366 cell.set_kernel(this.kernel)
1366 };
1367 };
1367 };
1368 };
1368 };
1369 };
1369
1370
1370 /**
1371 /**
1371 * Prompt the user to restart the IPython kernel.
1372 * Prompt the user to restart the IPython kernel.
1372 *
1373 *
1373 * @method restart_kernel
1374 * @method restart_kernel
1374 */
1375 */
1375 Notebook.prototype.restart_kernel = function () {
1376 Notebook.prototype.restart_kernel = function () {
1376 var that = this;
1377 var that = this;
1377 IPython.dialog.modal({
1378 IPython.dialog.modal({
1378 title : "Restart kernel or continue running?",
1379 title : "Restart kernel or continue running?",
1379 body : $("<p/>").html(
1380 body : $("<p/>").html(
1380 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1381 ),
1382 ),
1382 buttons : {
1383 buttons : {
1383 "Continue running" : {},
1384 "Continue running" : {},
1384 "Restart" : {
1385 "Restart" : {
1385 "class" : "btn-danger",
1386 "class" : "btn-danger",
1386 "click" : function() {
1387 "click" : function() {
1387 that.kernel.restart();
1388 that.kernel.restart();
1388 }
1389 }
1389 }
1390 }
1390 }
1391 }
1391 });
1392 });
1392 };
1393 };
1393
1394
1394 /**
1395 /**
1395 * Run the selected cell.
1396 * Run the selected cell.
1396 *
1397 *
1397 * Execute or render cell outputs.
1398 * Execute or render cell outputs.
1398 *
1399 *
1399 * @method execute_selected_cell
1400 * @method execute_selected_cell
1400 * @param {Object} options Customize post-execution behavior
1401 * @param {Object} options Customize post-execution behavior
1401 */
1402 */
1402 Notebook.prototype.execute_selected_cell = function (options) {
1403 Notebook.prototype.execute_selected_cell = function (options) {
1403 // add_new: should a new cell be added if we are at the end of the nb
1404 // add_new: should a new cell be added if we are at the end of the nb
1404 // terminal: execute in terminal mode, which stays in the current cell
1405 // terminal: execute in terminal mode, which stays in the current cell
1405 var default_options = {terminal: false, add_new: true};
1406 var default_options = {terminal: false, add_new: true};
1406 $.extend(default_options, options);
1407 $.extend(default_options, options);
1407 var that = this;
1408 var that = this;
1408 var cell = that.get_selected_cell();
1409 var cell = that.get_selected_cell();
1409 var cell_index = that.find_cell_index(cell);
1410 var cell_index = that.find_cell_index(cell);
1410 if (cell instanceof IPython.CodeCell) {
1411 if (cell instanceof IPython.CodeCell) {
1411 cell.execute();
1412 cell.execute();
1412 }
1413 }
1413 if (default_options.terminal) {
1414 if (default_options.terminal) {
1414 cell.select_all();
1415 cell.select_all();
1415 } else {
1416 } else {
1416 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1417 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1417 that.insert_cell_below('code');
1418 that.insert_cell_below('code');
1418 // If we are adding a new cell at the end, scroll down to show it.
1419 // If we are adding a new cell at the end, scroll down to show it.
1419 that.scroll_to_bottom();
1420 that.scroll_to_bottom();
1420 } else {
1421 } else {
1421 that.select(cell_index+1);
1422 that.select(cell_index+1);
1422 };
1423 };
1423 };
1424 };
1424 this.set_dirty(true);
1425 this.set_dirty(true);
1425 };
1426 };
1426
1427
1427 /**
1428 /**
1428 * Execute all cells below the selected cell.
1429 * Execute all cells below the selected cell.
1429 *
1430 *
1430 * @method execute_cells_below
1431 * @method execute_cells_below
1431 */
1432 */
1432 Notebook.prototype.execute_cells_below = function () {
1433 Notebook.prototype.execute_cells_below = function () {
1433 this.execute_cell_range(this.get_selected_index(), this.ncells());
1434 this.execute_cell_range(this.get_selected_index(), this.ncells());
1434 this.scroll_to_bottom();
1435 this.scroll_to_bottom();
1435 };
1436 };
1436
1437
1437 /**
1438 /**
1438 * Execute all cells above the selected cell.
1439 * Execute all cells above the selected cell.
1439 *
1440 *
1440 * @method execute_cells_above
1441 * @method execute_cells_above
1441 */
1442 */
1442 Notebook.prototype.execute_cells_above = function () {
1443 Notebook.prototype.execute_cells_above = function () {
1443 this.execute_cell_range(0, this.get_selected_index());
1444 this.execute_cell_range(0, this.get_selected_index());
1444 };
1445 };
1445
1446
1446 /**
1447 /**
1447 * Execute all cells.
1448 * Execute all cells.
1448 *
1449 *
1449 * @method execute_all_cells
1450 * @method execute_all_cells
1450 */
1451 */
1451 Notebook.prototype.execute_all_cells = function () {
1452 Notebook.prototype.execute_all_cells = function () {
1452 this.execute_cell_range(0, this.ncells());
1453 this.execute_cell_range(0, this.ncells());
1453 this.scroll_to_bottom();
1454 this.scroll_to_bottom();
1454 };
1455 };
1455
1456
1456 /**
1457 /**
1457 * Execute a contiguous range of cells.
1458 * Execute a contiguous range of cells.
1458 *
1459 *
1459 * @method execute_cell_range
1460 * @method execute_cell_range
1460 * @param {Number} start Index of the first cell to execute (inclusive)
1461 * @param {Number} start Index of the first cell to execute (inclusive)
1461 * @param {Number} end Index of the last cell to execute (exclusive)
1462 * @param {Number} end Index of the last cell to execute (exclusive)
1462 */
1463 */
1463 Notebook.prototype.execute_cell_range = function (start, end) {
1464 Notebook.prototype.execute_cell_range = function (start, end) {
1464 for (var i=start; i<end; i++) {
1465 for (var i=start; i<end; i++) {
1465 this.select(i);
1466 this.select(i);
1466 this.execute_selected_cell({add_new:false});
1467 this.execute_selected_cell({add_new:false});
1467 };
1468 };
1468 };
1469 };
1469
1470
1470 // Persistance and loading
1471 // Persistance and loading
1471
1472
1472 /**
1473 /**
1473 * Getter method for this notebook's ID.
1474 * Getter method for this notebook's ID.
1474 *
1475 *
1475 * @method get_notebook_id
1476 * @method get_notebook_id
1476 * @return {String} This notebook's ID
1477 * @return {String} This notebook's ID
1477 */
1478 */
1478 Notebook.prototype.get_notebook_id = function () {
1479 Notebook.prototype.get_notebook_id = function () {
1479 return this.notebook_id;
1480 return this.notebook_id;
1480 };
1481 };
1481
1482
1482 /**
1483 /**
1483 * Getter method for this notebook's name.
1484 * Getter method for this notebook's name.
1484 *
1485 *
1485 * @method get_notebook_name
1486 * @method get_notebook_name
1486 * @return {String} This notebook's name
1487 * @return {String} This notebook's name
1487 */
1488 */
1488 Notebook.prototype.get_notebook_name = function () {
1489 Notebook.prototype.get_notebook_name = function () {
1489 return this.notebook_name;
1490 return this.notebook_name;
1490 };
1491 };
1491
1492
1492 /**
1493 /**
1493 * Setter method for this notebook's name.
1494 * Setter method for this notebook's name.
1494 *
1495 *
1495 * @method set_notebook_name
1496 * @method set_notebook_name
1496 * @param {String} name A new name for this notebook
1497 * @param {String} name A new name for this notebook
1497 */
1498 */
1498 Notebook.prototype.set_notebook_name = function (name) {
1499 Notebook.prototype.set_notebook_name = function (name) {
1499 this.notebook_name = name;
1500 this.notebook_name = name;
1500 };
1501 };
1501
1502
1502 /**
1503 /**
1503 * Check that a notebook's name is valid.
1504 * Check that a notebook's name is valid.
1504 *
1505 *
1505 * @method test_notebook_name
1506 * @method test_notebook_name
1506 * @param {String} nbname A name for this notebook
1507 * @param {String} nbname A name for this notebook
1507 * @return {Boolean} True if the name is valid, false if invalid
1508 * @return {Boolean} True if the name is valid, false if invalid
1508 */
1509 */
1509 Notebook.prototype.test_notebook_name = function (nbname) {
1510 Notebook.prototype.test_notebook_name = function (nbname) {
1510 nbname = nbname || '';
1511 nbname = nbname || '';
1511 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1512 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1512 return true;
1513 return true;
1513 } else {
1514 } else {
1514 return false;
1515 return false;
1515 };
1516 };
1516 };
1517 };
1517
1518
1518 /**
1519 /**
1519 * Load a notebook from JSON (.ipynb).
1520 * Load a notebook from JSON (.ipynb).
1520 *
1521 *
1521 * This currently handles one worksheet: others are deleted.
1522 * This currently handles one worksheet: others are deleted.
1522 *
1523 *
1523 * @method fromJSON
1524 * @method fromJSON
1524 * @param {Object} data JSON representation of a notebook
1525 * @param {Object} data JSON representation of a notebook
1525 */
1526 */
1526 Notebook.prototype.fromJSON = function (data) {
1527 Notebook.prototype.fromJSON = function (data) {
1527 var ncells = this.ncells();
1528 var ncells = this.ncells();
1528 var i;
1529 var i;
1529 for (i=0; i<ncells; i++) {
1530 for (i=0; i<ncells; i++) {
1530 // Always delete cell 0 as they get renumbered as they are deleted.
1531 // Always delete cell 0 as they get renumbered as they are deleted.
1531 this.delete_cell(0);
1532 this.delete_cell(0);
1532 };
1533 };
1533 // Save the metadata and name.
1534 // Save the metadata and name.
1534 this.metadata = data.metadata;
1535 this.metadata = data.metadata;
1535 this.notebook_name = data.metadata.name;
1536 this.notebook_name = data.metadata.name;
1536 // Only handle 1 worksheet for now.
1537 // Only handle 1 worksheet for now.
1537 var worksheet = data.worksheets[0];
1538 var worksheet = data.worksheets[0];
1538 if (worksheet !== undefined) {
1539 if (worksheet !== undefined) {
1539 if (worksheet.metadata) {
1540 if (worksheet.metadata) {
1540 this.worksheet_metadata = worksheet.metadata;
1541 this.worksheet_metadata = worksheet.metadata;
1541 }
1542 }
1542 var new_cells = worksheet.cells;
1543 var new_cells = worksheet.cells;
1543 ncells = new_cells.length;
1544 ncells = new_cells.length;
1544 var cell_data = null;
1545 var cell_data = null;
1545 var new_cell = null;
1546 var new_cell = null;
1546 for (i=0; i<ncells; i++) {
1547 for (i=0; i<ncells; i++) {
1547 cell_data = new_cells[i];
1548 cell_data = new_cells[i];
1548 // VERSIONHACK: plaintext -> raw
1549 // VERSIONHACK: plaintext -> raw
1549 // handle never-released plaintext name for raw cells
1550 // handle never-released plaintext name for raw cells
1550 if (cell_data.cell_type === 'plaintext'){
1551 if (cell_data.cell_type === 'plaintext'){
1551 cell_data.cell_type = 'raw';
1552 cell_data.cell_type = 'raw';
1552 }
1553 }
1553
1554
1554 new_cell = this.insert_cell_below(cell_data.cell_type);
1555 new_cell = this.insert_cell_below(cell_data.cell_type);
1555 new_cell.fromJSON(cell_data);
1556 new_cell.fromJSON(cell_data);
1556 };
1557 };
1557 };
1558 };
1558 if (data.worksheets.length > 1) {
1559 if (data.worksheets.length > 1) {
1559 IPython.dialog.modal({
1560 IPython.dialog.modal({
1560 title : "Multiple worksheets",
1561 title : "Multiple worksheets",
1561 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1562 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1562 "but this version of IPython can only handle the first. " +
1563 "but this version of IPython can only handle the first. " +
1563 "If you save this notebook, worksheets after the first will be lost.",
1564 "If you save this notebook, worksheets after the first will be lost.",
1564 buttons : {
1565 buttons : {
1565 OK : {
1566 OK : {
1566 class : "btn-danger"
1567 class : "btn-danger"
1567 }
1568 }
1568 }
1569 }
1569 });
1570 });
1570 }
1571 }
1571 };
1572 };
1572
1573
1573 /**
1574 /**
1574 * Dump this notebook into a JSON-friendly object.
1575 * Dump this notebook into a JSON-friendly object.
1575 *
1576 *
1576 * @method toJSON
1577 * @method toJSON
1577 * @return {Object} A JSON-friendly representation of this notebook.
1578 * @return {Object} A JSON-friendly representation of this notebook.
1578 */
1579 */
1579 Notebook.prototype.toJSON = function () {
1580 Notebook.prototype.toJSON = function () {
1580 var cells = this.get_cells();
1581 var cells = this.get_cells();
1581 var ncells = cells.length;
1582 var ncells = cells.length;
1582 var cell_array = new Array(ncells);
1583 var cell_array = new Array(ncells);
1583 for (var i=0; i<ncells; i++) {
1584 for (var i=0; i<ncells; i++) {
1584 cell_array[i] = cells[i].toJSON();
1585 cell_array[i] = cells[i].toJSON();
1585 };
1586 };
1586 var data = {
1587 var data = {
1587 // Only handle 1 worksheet for now.
1588 // Only handle 1 worksheet for now.
1588 worksheets : [{
1589 worksheets : [{
1589 cells: cell_array,
1590 cells: cell_array,
1590 metadata: this.worksheet_metadata
1591 metadata: this.worksheet_metadata
1591 }],
1592 }],
1592 metadata : this.metadata
1593 metadata : this.metadata
1593 };
1594 };
1594 return data;
1595 return data;
1595 };
1596 };
1596
1597
1597 /**
1598 /**
1598 * Start an autosave timer, for periodically saving the notebook.
1599 * Start an autosave timer, for periodically saving the notebook.
1599 *
1600 *
1600 * @method set_autosave_interval
1601 * @method set_autosave_interval
1601 * @param {Integer} interval the autosave interval in milliseconds
1602 * @param {Integer} interval the autosave interval in milliseconds
1602 */
1603 */
1603 Notebook.prototype.set_autosave_interval = function (interval) {
1604 Notebook.prototype.set_autosave_interval = function (interval) {
1604 var that = this;
1605 var that = this;
1605 // clear previous interval, so we don't get simultaneous timers
1606 // clear previous interval, so we don't get simultaneous timers
1606 if (this.autosave_timer) {
1607 if (this.autosave_timer) {
1607 clearInterval(this.autosave_timer);
1608 clearInterval(this.autosave_timer);
1608 }
1609 }
1609
1610
1610 this.autosave_interval = this.minimum_autosave_interval = interval;
1611 this.autosave_interval = this.minimum_autosave_interval = interval;
1611 if (interval) {
1612 if (interval) {
1612 this.autosave_timer = setInterval(function() {
1613 this.autosave_timer = setInterval(function() {
1613 if (that.dirty) {
1614 if (that.dirty) {
1614 that.save_notebook();
1615 that.save_notebook();
1615 }
1616 }
1616 }, interval);
1617 }, interval);
1617 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1618 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1618 } else {
1619 } else {
1619 this.autosave_timer = null;
1620 this.autosave_timer = null;
1620 $([IPython.events]).trigger("autosave_disabled.Notebook");
1621 $([IPython.events]).trigger("autosave_disabled.Notebook");
1621 };
1622 };
1622 };
1623 };
1623
1624
1624 /**
1625 /**
1625 * Save this notebook on the server.
1626 * Save this notebook on the server.
1626 *
1627 *
1627 * @method save_notebook
1628 * @method save_notebook
1628 */
1629 */
1629 Notebook.prototype.save_notebook = function () {
1630 Notebook.prototype.save_notebook = function () {
1630 // We may want to move the name/id/nbformat logic inside toJSON?
1631 // We may want to move the name/id/nbformat logic inside toJSON?
1631 var data = this.toJSON();
1632 var data = this.toJSON();
1632 data.metadata.name = this.notebook_name;
1633 data.metadata.name = this.notebook_name;
1633 data.nbformat = this.nbformat;
1634 data.nbformat = this.nbformat;
1634 data.nbformat_minor = this.nbformat_minor;
1635 data.nbformat_minor = this.nbformat_minor;
1635
1636
1636 // time the ajax call for autosave tuning purposes.
1637 // time the ajax call for autosave tuning purposes.
1637 var start = new Date().getTime();
1638 var start = new Date().getTime();
1638
1639
1639 // We do the call with settings so we can set cache to false.
1640 // We do the call with settings so we can set cache to false.
1640 var settings = {
1641 var settings = {
1641 processData : false,
1642 processData : false,
1642 cache : false,
1643 cache : false,
1643 type : "PUT",
1644 type : "PUT",
1644 data : JSON.stringify(data),
1645 data : JSON.stringify(data),
1645 headers : {'Content-Type': 'application/json'},
1646 headers : {'Content-Type': 'application/json'},
1646 success : $.proxy(this.save_notebook_success, this, start),
1647 success : $.proxy(this.save_notebook_success, this, start),
1647 error : $.proxy(this.save_notebook_error, this)
1648 error : $.proxy(this.save_notebook_error, this)
1648 };
1649 };
1649 $([IPython.events]).trigger('notebook_saving.Notebook');
1650 $([IPython.events]).trigger('notebook_saving.Notebook');
1650 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1651 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1651 $.ajax(url, settings);
1652 $.ajax(url, settings);
1652 };
1653 };
1653
1654
1654 /**
1655 /**
1655 * Success callback for saving a notebook.
1656 * Success callback for saving a notebook.
1656 *
1657 *
1657 * @method save_notebook_success
1658 * @method save_notebook_success
1658 * @param {Integer} start the time when the save request started
1659 * @param {Integer} start the time when the save request started
1659 * @param {Object} data JSON representation of a notebook
1660 * @param {Object} data JSON representation of a notebook
1660 * @param {String} status Description of response status
1661 * @param {String} status Description of response status
1661 * @param {jqXHR} xhr jQuery Ajax object
1662 * @param {jqXHR} xhr jQuery Ajax object
1662 */
1663 */
1663 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1664 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1664 this.set_dirty(false);
1665 this.set_dirty(false);
1665 $([IPython.events]).trigger('notebook_saved.Notebook');
1666 $([IPython.events]).trigger('notebook_saved.Notebook');
1666 this._update_autosave_interval(start);
1667 this._update_autosave_interval(start);
1667 if (this._checkpoint_after_save) {
1668 if (this._checkpoint_after_save) {
1668 this.create_checkpoint();
1669 this.create_checkpoint();
1669 this._checkpoint_after_save = false;
1670 this._checkpoint_after_save = false;
1670 };
1671 };
1671 };
1672 };
1672
1673
1673 /**
1674 /**
1674 * update the autosave interval based on how long the last save took
1675 * update the autosave interval based on how long the last save took
1675 *
1676 *
1676 * @method _update_autosave_interval
1677 * @method _update_autosave_interval
1677 * @param {Integer} timestamp when the save request started
1678 * @param {Integer} timestamp when the save request started
1678 */
1679 */
1679 Notebook.prototype._update_autosave_interval = function (start) {
1680 Notebook.prototype._update_autosave_interval = function (start) {
1680 var duration = (new Date().getTime() - start);
1681 var duration = (new Date().getTime() - start);
1681 if (this.autosave_interval) {
1682 if (this.autosave_interval) {
1682 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1683 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1683 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1684 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1684 // round to 10 seconds, otherwise we will be setting a new interval too often
1685 // round to 10 seconds, otherwise we will be setting a new interval too often
1685 interval = 10000 * Math.round(interval / 10000);
1686 interval = 10000 * Math.round(interval / 10000);
1686 // set new interval, if it's changed
1687 // set new interval, if it's changed
1687 if (interval != this.autosave_interval) {
1688 if (interval != this.autosave_interval) {
1688 this.set_autosave_interval(interval);
1689 this.set_autosave_interval(interval);
1689 }
1690 }
1690 }
1691 }
1691 };
1692 };
1692
1693
1693 /**
1694 /**
1694 * Failure callback for saving a notebook.
1695 * Failure callback for saving a notebook.
1695 *
1696 *
1696 * @method save_notebook_error
1697 * @method save_notebook_error
1697 * @param {jqXHR} xhr jQuery Ajax object
1698 * @param {jqXHR} xhr jQuery Ajax object
1698 * @param {String} status Description of response status
1699 * @param {String} status Description of response status
1699 * @param {String} error_msg HTTP error message
1700 * @param {String} error_msg HTTP error message
1700 */
1701 */
1701 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1702 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1702 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1703 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1703 };
1704 };
1704
1705
1705 /**
1706 /**
1706 * Request a notebook's data from the server.
1707 * Request a notebook's data from the server.
1707 *
1708 *
1708 * @method load_notebook
1709 * @method load_notebook
1709 * @param {String} notebook_id A notebook to load
1710 * @param {String} notebook_id A notebook to load
1710 */
1711 */
1711 Notebook.prototype.load_notebook = function (notebook_id) {
1712 Notebook.prototype.load_notebook = function (notebook_id) {
1712 var that = this;
1713 var that = this;
1713 this.notebook_id = notebook_id;
1714 this.notebook_id = notebook_id;
1714 // We do the call with settings so we can set cache to false.
1715 // We do the call with settings so we can set cache to false.
1715 var settings = {
1716 var settings = {
1716 processData : false,
1717 processData : false,
1717 cache : false,
1718 cache : false,
1718 type : "GET",
1719 type : "GET",
1719 dataType : "json",
1720 dataType : "json",
1720 success : $.proxy(this.load_notebook_success,this),
1721 success : $.proxy(this.load_notebook_success,this),
1721 error : $.proxy(this.load_notebook_error,this),
1722 error : $.proxy(this.load_notebook_error,this),
1722 };
1723 };
1723 $([IPython.events]).trigger('notebook_loading.Notebook');
1724 $([IPython.events]).trigger('notebook_loading.Notebook');
1724 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1725 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1725 $.ajax(url, settings);
1726 $.ajax(url, settings);
1726 };
1727 };
1727
1728
1728 /**
1729 /**
1729 * Success callback for loading a notebook from the server.
1730 * Success callback for loading a notebook from the server.
1730 *
1731 *
1731 * Load notebook data from the JSON response.
1732 * Load notebook data from the JSON response.
1732 *
1733 *
1733 * @method load_notebook_success
1734 * @method load_notebook_success
1734 * @param {Object} data JSON representation of a notebook
1735 * @param {Object} data JSON representation of a notebook
1735 * @param {String} status Description of response status
1736 * @param {String} status Description of response status
1736 * @param {jqXHR} xhr jQuery Ajax object
1737 * @param {jqXHR} xhr jQuery Ajax object
1737 */
1738 */
1738 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1739 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1739 this.fromJSON(data);
1740 this.fromJSON(data);
1740 if (this.ncells() === 0) {
1741 if (this.ncells() === 0) {
1741 this.insert_cell_below('code');
1742 this.insert_cell_below('code');
1742 };
1743 };
1743 this.set_dirty(false);
1744 this.set_dirty(false);
1744 this.select(0);
1745 this.select(0);
1745 this.scroll_to_top();
1746 this.scroll_to_top();
1746 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1747 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1747 var msg = "This notebook has been converted from an older " +
1748 var msg = "This notebook has been converted from an older " +
1748 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1749 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1749 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1750 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1750 "newer notebook format will be used and older versions of IPython " +
1751 "newer notebook format will be used and older versions of IPython " +
1751 "may not be able to read it. To keep the older version, close the " +
1752 "may not be able to read it. To keep the older version, close the " +
1752 "notebook without saving it.";
1753 "notebook without saving it.";
1753 IPython.dialog.modal({
1754 IPython.dialog.modal({
1754 title : "Notebook converted",
1755 title : "Notebook converted",
1755 body : msg,
1756 body : msg,
1756 buttons : {
1757 buttons : {
1757 OK : {
1758 OK : {
1758 class : "btn-primary"
1759 class : "btn-primary"
1759 }
1760 }
1760 }
1761 }
1761 });
1762 });
1762 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1763 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1763 var that = this;
1764 var that = this;
1764 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1765 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1765 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1766 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1766 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1767 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1767 this_vs + ". You can still work with this notebook, but some features " +
1768 this_vs + ". You can still work with this notebook, but some features " +
1768 "introduced in later notebook versions may not be available."
1769 "introduced in later notebook versions may not be available."
1769
1770
1770 IPython.dialog.modal({
1771 IPython.dialog.modal({
1771 title : "Newer Notebook",
1772 title : "Newer Notebook",
1772 body : msg,
1773 body : msg,
1773 buttons : {
1774 buttons : {
1774 OK : {
1775 OK : {
1775 class : "btn-danger"
1776 class : "btn-danger"
1776 }
1777 }
1777 }
1778 }
1778 });
1779 });
1779
1780
1780 }
1781 }
1781
1782
1782 // Create the kernel after the notebook is completely loaded to prevent
1783 // Create the kernel after the notebook is completely loaded to prevent
1783 // code execution upon loading, which is a security risk.
1784 // code execution upon loading, which is a security risk.
1784 this.start_kernel();
1785 this.start_kernel();
1785 // load our checkpoint list
1786 // load our checkpoint list
1786 IPython.notebook.list_checkpoints();
1787 IPython.notebook.list_checkpoints();
1787
1788
1788 $([IPython.events]).trigger('notebook_loaded.Notebook');
1789 $([IPython.events]).trigger('notebook_loaded.Notebook');
1789 };
1790 };
1790
1791
1791 /**
1792 /**
1792 * Failure callback for loading a notebook from the server.
1793 * Failure callback for loading a notebook from the server.
1793 *
1794 *
1794 * @method load_notebook_error
1795 * @method load_notebook_error
1795 * @param {jqXHR} xhr jQuery Ajax object
1796 * @param {jqXHR} xhr jQuery Ajax object
1796 * @param {String} textStatus Description of response status
1797 * @param {String} textStatus Description of response status
1797 * @param {String} errorThrow HTTP error message
1798 * @param {String} errorThrow HTTP error message
1798 */
1799 */
1799 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1800 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1800 if (xhr.status === 400) {
1801 if (xhr.status === 400) {
1801 var msg = errorThrow;
1802 var msg = errorThrow;
1802 } else if (xhr.status === 500) {
1803 } else if (xhr.status === 500) {
1803 var msg = "An unknown error occurred while loading this notebook. " +
1804 var msg = "An unknown error occurred while loading this notebook. " +
1804 "This version can load notebook formats " +
1805 "This version can load notebook formats " +
1805 "v" + this.nbformat + " or earlier.";
1806 "v" + this.nbformat + " or earlier.";
1806 }
1807 }
1807 IPython.dialog.modal({
1808 IPython.dialog.modal({
1808 title: "Error loading notebook",
1809 title: "Error loading notebook",
1809 body : msg,
1810 body : msg,
1810 buttons : {
1811 buttons : {
1811 "OK": {}
1812 "OK": {}
1812 }
1813 }
1813 });
1814 });
1814 }
1815 }
1815
1816
1816 /********************* checkpoint-related *********************/
1817 /********************* checkpoint-related *********************/
1817
1818
1818 /**
1819 /**
1819 * Save the notebook then immediately create a checkpoint.
1820 * Save the notebook then immediately create a checkpoint.
1820 *
1821 *
1821 * @method save_checkpoint
1822 * @method save_checkpoint
1822 */
1823 */
1823 Notebook.prototype.save_checkpoint = function () {
1824 Notebook.prototype.save_checkpoint = function () {
1824 this._checkpoint_after_save = true;
1825 this._checkpoint_after_save = true;
1825 this.save_notebook();
1826 this.save_notebook();
1826 };
1827 };
1827
1828
1828 /**
1829 /**
1830 * Add a checkpoint for this notebook.
1831 * for use as a callback from checkpoint creation.
1832 *
1833 * @method add_checkpoint
1834 */
1835 Notebook.prototype.add_checkpoint = function (checkpoint) {
1836 var found = false;
1837 for (var i = 0; i < this.checkpoints.length; i++) {
1838 var existing = this.checkpoints[i];
1839 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1840 found = true;
1841 this.checkpoints[i] = checkpoint;
1842 break;
1843 }
1844 }
1845 if (!found) {
1846 this.checkpoints.push(checkpoint);
1847 }
1848 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1849 };
1850
1851 /**
1829 * List checkpoints for this notebook.
1852 * List checkpoints for this notebook.
1830 *
1853 *
1831 * @method list_checkpoint
1854 * @method list_checkpoints
1832 */
1855 */
1833 Notebook.prototype.list_checkpoints = function () {
1856 Notebook.prototype.list_checkpoints = function () {
1834 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1857 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1835 $.get(url).done(
1858 $.get(url).done(
1836 $.proxy(this.list_checkpoints_success, this)
1859 $.proxy(this.list_checkpoints_success, this)
1837 ).fail(
1860 ).fail(
1838 $.proxy(this.list_checkpoints_error, this)
1861 $.proxy(this.list_checkpoints_error, this)
1839 );
1862 );
1840 };
1863 };
1841
1864
1842 /**
1865 /**
1843 * Success callback for listing checkpoints.
1866 * Success callback for listing checkpoints.
1844 *
1867 *
1845 * @method list_checkpoint_success
1868 * @method list_checkpoint_success
1846 * @param {Object} data JSON representation of a checkpoint
1869 * @param {Object} data JSON representation of a checkpoint
1847 * @param {String} status Description of response status
1870 * @param {String} status Description of response status
1848 * @param {jqXHR} xhr jQuery Ajax object
1871 * @param {jqXHR} xhr jQuery Ajax object
1849 */
1872 */
1850 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1873 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1851 var data = $.parseJSON(data);
1874 var data = $.parseJSON(data);
1875 this.checkpoints = data;
1852 if (data.length) {
1876 if (data.length) {
1853 this.last_checkpoint = data[0];
1877 this.last_checkpoint = data[data.length - 1];
1854 } else {
1878 } else {
1855 this.last_checkpoint = null;
1879 this.last_checkpoint = null;
1856 }
1880 }
1857 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1881 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1858 };
1882 };
1859
1883
1860 /**
1884 /**
1861 * Failure callback for listing a checkpoint.
1885 * Failure callback for listing a checkpoint.
1862 *
1886 *
1863 * @method list_checkpoint_error
1887 * @method list_checkpoint_error
1864 * @param {jqXHR} xhr jQuery Ajax object
1888 * @param {jqXHR} xhr jQuery Ajax object
1865 * @param {String} status Description of response status
1889 * @param {String} status Description of response status
1866 * @param {String} error_msg HTTP error message
1890 * @param {String} error_msg HTTP error message
1867 */
1891 */
1868 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1892 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1869 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1893 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1870 };
1894 };
1871
1895
1872 /**
1896 /**
1873 * Create a checkpoint of this notebook on the server from the most recent save.
1897 * Create a checkpoint of this notebook on the server from the most recent save.
1874 *
1898 *
1875 * @method create_checkpoint
1899 * @method create_checkpoint
1876 */
1900 */
1877 Notebook.prototype.create_checkpoint = function () {
1901 Notebook.prototype.create_checkpoint = function () {
1878 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1902 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1879 $.post(url).done(
1903 $.post(url).done(
1880 $.proxy(this.create_checkpoint_success, this)
1904 $.proxy(this.create_checkpoint_success, this)
1881 ).fail(
1905 ).fail(
1882 $.proxy(this.create_checkpoint_error, this)
1906 $.proxy(this.create_checkpoint_error, this)
1883 );
1907 );
1884 };
1908 };
1885
1909
1886 /**
1910 /**
1887 * Success callback for creating a checkpoint.
1911 * Success callback for creating a checkpoint.
1888 *
1912 *
1889 * @method create_checkpoint_success
1913 * @method create_checkpoint_success
1890 * @param {Object} data JSON representation of a checkpoint
1914 * @param {Object} data JSON representation of a checkpoint
1891 * @param {String} status Description of response status
1915 * @param {String} status Description of response status
1892 * @param {jqXHR} xhr jQuery Ajax object
1916 * @param {jqXHR} xhr jQuery Ajax object
1893 */
1917 */
1894 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1918 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1895 var data = $.parseJSON(data);
1919 var data = $.parseJSON(data);
1896 this.last_checkpoint = data;
1920 this.add_checkpoint(data);
1897 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1921 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1898 };
1922 };
1899
1923
1900 /**
1924 /**
1901 * Failure callback for creating a checkpoint.
1925 * Failure callback for creating a checkpoint.
1902 *
1926 *
1903 * @method create_checkpoint_error
1927 * @method create_checkpoint_error
1904 * @param {jqXHR} xhr jQuery Ajax object
1928 * @param {jqXHR} xhr jQuery Ajax object
1905 * @param {String} status Description of response status
1929 * @param {String} status Description of response status
1906 * @param {String} error_msg HTTP error message
1930 * @param {String} error_msg HTTP error message
1907 */
1931 */
1908 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1932 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1909 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1933 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1910 };
1934 };
1911
1935
1912 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1936 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1913 var that = this;
1937 var that = this;
1914 var checkpoint = checkpoint || this.last_checkpoint;
1938 var checkpoint = checkpoint || this.last_checkpoint;
1915 if ( ! checkpoint ) {
1939 if ( ! checkpoint ) {
1916 console.log("restore dialog, but no checkpoint to restore to!");
1940 console.log("restore dialog, but no checkpoint to restore to!");
1917 return;
1941 return;
1918 }
1942 }
1919 var body = $('<div/>').append(
1943 var body = $('<div/>').append(
1920 $('<p/>').addClass("p-space").text(
1944 $('<p/>').addClass("p-space").text(
1921 "Are you sure you want to revert the notebook to " +
1945 "Are you sure you want to revert the notebook to " +
1922 "the latest checkpoint?"
1946 "the latest checkpoint?"
1923 ).append(
1947 ).append(
1924 $("<strong/>").text(
1948 $("<strong/>").text(
1925 " This cannot be undone."
1949 " This cannot be undone."
1926 )
1950 )
1927 )
1951 )
1928 ).append(
1952 ).append(
1929 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1953 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1930 ).append(
1954 ).append(
1931 $('<p/>').addClass("p-space").text(
1955 $('<p/>').addClass("p-space").text(
1932 Date(checkpoint.last_modified)
1956 Date(checkpoint.last_modified)
1933 ).css("text-align", "center")
1957 ).css("text-align", "center")
1934 );
1958 );
1935
1959
1936 IPython.dialog.modal({
1960 IPython.dialog.modal({
1937 title : "Revert notebook to checkpoint",
1961 title : "Revert notebook to checkpoint",
1938 body : body,
1962 body : body,
1939 buttons : {
1963 buttons : {
1940 Revert : {
1964 Revert : {
1941 class : "btn-danger",
1965 class : "btn-danger",
1942 click : function () {
1966 click : function () {
1943 that.restore_checkpoint(checkpoint.checkpoint_id);
1967 that.restore_checkpoint(checkpoint.checkpoint_id);
1944 }
1968 }
1945 },
1969 },
1946 Cancel : {}
1970 Cancel : {}
1947 }
1971 }
1948 });
1972 });
1949 }
1973 }
1950
1974
1951 /**
1975 /**
1952 * Restore the notebook to a checkpoint state.
1976 * Restore the notebook to a checkpoint state.
1953 *
1977 *
1954 * @method restore_checkpoint
1978 * @method restore_checkpoint
1955 * @param {String} checkpoint ID
1979 * @param {String} checkpoint ID
1956 */
1980 */
1957 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1981 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1958 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1982 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1959 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1983 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1960 $.post(url).done(
1984 $.post(url).done(
1961 $.proxy(this.restore_checkpoint_success, this)
1985 $.proxy(this.restore_checkpoint_success, this)
1962 ).fail(
1986 ).fail(
1963 $.proxy(this.restore_checkpoint_error, this)
1987 $.proxy(this.restore_checkpoint_error, this)
1964 );
1988 );
1965 };
1989 };
1966
1990
1967 /**
1991 /**
1968 * Success callback for restoring a notebook to a checkpoint.
1992 * Success callback for restoring a notebook to a checkpoint.
1969 *
1993 *
1970 * @method restore_checkpoint_success
1994 * @method restore_checkpoint_success
1971 * @param {Object} data (ignored, should be empty)
1995 * @param {Object} data (ignored, should be empty)
1972 * @param {String} status Description of response status
1996 * @param {String} status Description of response status
1973 * @param {jqXHR} xhr jQuery Ajax object
1997 * @param {jqXHR} xhr jQuery Ajax object
1974 */
1998 */
1975 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1999 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1976 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2000 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1977 this.load_notebook(this.notebook_id);
2001 this.load_notebook(this.notebook_id);
1978 };
2002 };
1979
2003
1980 /**
2004 /**
1981 * Failure callback for restoring a notebook to a checkpoint.
2005 * Failure callback for restoring a notebook to a checkpoint.
1982 *
2006 *
1983 * @method restore_checkpoint_error
2007 * @method restore_checkpoint_error
1984 * @param {jqXHR} xhr jQuery Ajax object
2008 * @param {jqXHR} xhr jQuery Ajax object
1985 * @param {String} status Description of response status
2009 * @param {String} status Description of response status
1986 * @param {String} error_msg HTTP error message
2010 * @param {String} error_msg HTTP error message
1987 */
2011 */
1988 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2012 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1989 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2013 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1990 };
2014 };
1991
2015
1992 /**
2016 /**
1993 * Delete a notebook checkpoint.
2017 * Delete a notebook checkpoint.
1994 *
2018 *
1995 * @method delete_checkpoint
2019 * @method delete_checkpoint
1996 * @param {String} checkpoint ID
2020 * @param {String} checkpoint ID
1997 */
2021 */
1998 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2022 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1999 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2023 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2000 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2024 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2001 $.ajax(url, {
2025 $.ajax(url, {
2002 type: 'DELETE',
2026 type: 'DELETE',
2003 success: $.proxy(this.delete_checkpoint_success, this),
2027 success: $.proxy(this.delete_checkpoint_success, this),
2004 error: $.proxy(this.delete_notebook_error,this)
2028 error: $.proxy(this.delete_notebook_error,this)
2005 });
2029 });
2006 };
2030 };
2007
2031
2008 /**
2032 /**
2009 * Success callback for deleting a notebook checkpoint
2033 * Success callback for deleting a notebook checkpoint
2010 *
2034 *
2011 * @method delete_checkpoint_success
2035 * @method delete_checkpoint_success
2012 * @param {Object} data (ignored, should be empty)
2036 * @param {Object} data (ignored, should be empty)
2013 * @param {String} status Description of response status
2037 * @param {String} status Description of response status
2014 * @param {jqXHR} xhr jQuery Ajax object
2038 * @param {jqXHR} xhr jQuery Ajax object
2015 */
2039 */
2016 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2040 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2017 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2041 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2018 this.load_notebook(this.notebook_id);
2042 this.load_notebook(this.notebook_id);
2019 };
2043 };
2020
2044
2021 /**
2045 /**
2022 * Failure callback for deleting a notebook checkpoint.
2046 * Failure callback for deleting a notebook checkpoint.
2023 *
2047 *
2024 * @method delete_checkpoint_error
2048 * @method delete_checkpoint_error
2025 * @param {jqXHR} xhr jQuery Ajax object
2049 * @param {jqXHR} xhr jQuery Ajax object
2026 * @param {String} status Description of response status
2050 * @param {String} status Description of response status
2027 * @param {String} error_msg HTTP error message
2051 * @param {String} error_msg HTTP error message
2028 */
2052 */
2029 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2053 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2030 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2054 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2031 };
2055 };
2032
2056
2033
2057
2034 IPython.Notebook = Notebook;
2058 IPython.Notebook = Notebook;
2035
2059
2036
2060
2037 return IPython;
2061 return IPython;
2038
2062
2039 }(IPython));
2063 }(IPython));
2040
2064
General Comments 0
You need to be logged in to leave comments. Login now