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