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