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