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