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