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