##// END OF EJS Templates
s/base_project_url/base_url/...
MinRK -
Show More
@@ -1,62 +1,62
1 """Tornado handlers logging into the notebook.
1 """Tornado handlers logging into the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import uuid
19 import uuid
20
20
21 from tornado.escape import url_escape
21 from tornado.escape import url_escape
22
22
23 from IPython.lib.security import passwd_check
23 from IPython.lib.security import passwd_check
24
24
25 from ..base.handlers import IPythonHandler
25 from ..base.handlers import IPythonHandler
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Handler
28 # Handler
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class LoginHandler(IPythonHandler):
31 class LoginHandler(IPythonHandler):
32
32
33 def _render(self, message=None):
33 def _render(self, message=None):
34 self.write(self.render_template('login.html',
34 self.write(self.render_template('login.html',
35 next=url_escape(self.get_argument('next', default=self.base_project_url)),
35 next=url_escape(self.get_argument('next', default=self.base_url)),
36 message=message,
36 message=message,
37 ))
37 ))
38
38
39 def get(self):
39 def get(self):
40 if self.current_user:
40 if self.current_user:
41 self.redirect(self.get_argument('next', default=self.base_project_url))
41 self.redirect(self.get_argument('next', default=self.base_url))
42 else:
42 else:
43 self._render()
43 self._render()
44
44
45 def post(self):
45 def post(self):
46 pwd = self.get_argument('password', default=u'')
46 pwd = self.get_argument('password', default=u'')
47 if self.login_available:
47 if self.login_available:
48 if passwd_check(self.password, pwd):
48 if passwd_check(self.password, pwd):
49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 else:
50 else:
51 self._render(message={'error': 'Invalid password'})
51 self._render(message={'error': 'Invalid password'})
52 return
52 return
53
53
54 self.redirect(self.get_argument('next', default=self.base_project_url))
54 self.redirect(self.get_argument('next', default=self.base_url))
55
55
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61
61
62 default_handlers = [(r"/login", LoginHandler)]
62 default_handlers = [(r"/login", LoginHandler)]
@@ -1,387 +1,387
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import functools
20 import functools
21 import json
21 import json
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 import traceback
26 import traceback
27 try:
27 try:
28 # py3
28 # py3
29 from http.client import responses
29 from http.client import responses
30 except ImportError:
30 except ImportError:
31 from httplib import responses
31 from httplib import responses
32
32
33 from jinja2 import TemplateNotFound
33 from jinja2 import TemplateNotFound
34 from tornado import web
34 from tornado import web
35
35
36 try:
36 try:
37 from tornado.log import app_log
37 from tornado.log import app_log
38 except ImportError:
38 except ImportError:
39 app_log = logging.getLogger()
39 app_log = logging.getLogger()
40
40
41 from IPython.config import Application
41 from IPython.config import Application
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import string_types
43 from IPython.utils.py3compat import string_types
44 from IPython.html.utils import is_hidden
44 from IPython.html.utils import is_hidden
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Top-level handlers
47 # Top-level handlers
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
50
50
51 class AuthenticatedHandler(web.RequestHandler):
51 class AuthenticatedHandler(web.RequestHandler):
52 """A RequestHandler with an authenticated user."""
52 """A RequestHandler with an authenticated user."""
53
53
54 def clear_login_cookie(self):
54 def clear_login_cookie(self):
55 self.clear_cookie(self.cookie_name)
55 self.clear_cookie(self.cookie_name)
56
56
57 def get_current_user(self):
57 def get_current_user(self):
58 user_id = self.get_secure_cookie(self.cookie_name)
58 user_id = self.get_secure_cookie(self.cookie_name)
59 # For now the user_id should not return empty, but it could eventually
59 # For now the user_id should not return empty, but it could eventually
60 if user_id == '':
60 if user_id == '':
61 user_id = 'anonymous'
61 user_id = 'anonymous'
62 if user_id is None:
62 if user_id is None:
63 # prevent extra Invalid cookie sig warnings:
63 # prevent extra Invalid cookie sig warnings:
64 self.clear_login_cookie()
64 self.clear_login_cookie()
65 if not self.login_available:
65 if not self.login_available:
66 user_id = 'anonymous'
66 user_id = 'anonymous'
67 return user_id
67 return user_id
68
68
69 @property
69 @property
70 def cookie_name(self):
70 def cookie_name(self):
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
72 self.request.host
72 self.request.host
73 ))
73 ))
74 return self.settings.get('cookie_name', default_cookie_name)
74 return self.settings.get('cookie_name', default_cookie_name)
75
75
76 @property
76 @property
77 def password(self):
77 def password(self):
78 """our password"""
78 """our password"""
79 return self.settings.get('password', '')
79 return self.settings.get('password', '')
80
80
81 @property
81 @property
82 def logged_in(self):
82 def logged_in(self):
83 """Is a user currently logged in?
83 """Is a user currently logged in?
84
84
85 """
85 """
86 user = self.get_current_user()
86 user = self.get_current_user()
87 return (user and not user == 'anonymous')
87 return (user and not user == 'anonymous')
88
88
89 @property
89 @property
90 def login_available(self):
90 def login_available(self):
91 """May a user proceed to log in?
91 """May a user proceed to log in?
92
92
93 This returns True if login capability is available, irrespective of
93 This returns True if login capability is available, irrespective of
94 whether the user is already logged in or not.
94 whether the user is already logged in or not.
95
95
96 """
96 """
97 return bool(self.settings.get('password', ''))
97 return bool(self.settings.get('password', ''))
98
98
99
99
100 class IPythonHandler(AuthenticatedHandler):
100 class IPythonHandler(AuthenticatedHandler):
101 """IPython-specific extensions to authenticated handling
101 """IPython-specific extensions to authenticated handling
102
102
103 Mostly property shortcuts to IPython-specific settings.
103 Mostly property shortcuts to IPython-specific settings.
104 """
104 """
105
105
106 @property
106 @property
107 def config(self):
107 def config(self):
108 return self.settings.get('config', None)
108 return self.settings.get('config', None)
109
109
110 @property
110 @property
111 def log(self):
111 def log(self):
112 """use the IPython log by default, falling back on tornado's logger"""
112 """use the IPython log by default, falling back on tornado's logger"""
113 if Application.initialized():
113 if Application.initialized():
114 return Application.instance().log
114 return Application.instance().log
115 else:
115 else:
116 return app_log
116 return app_log
117
117
118 #---------------------------------------------------------------
118 #---------------------------------------------------------------
119 # URLs
119 # URLs
120 #---------------------------------------------------------------
120 #---------------------------------------------------------------
121
121
122 @property
122 @property
123 def ws_url(self):
123 def ws_url(self):
124 """websocket url matching the current request
124 """websocket url matching the current request
125
125
126 By default, this is just `''`, indicating that it should match
126 By default, this is just `''`, indicating that it should match
127 the same host, protocol, port, etc.
127 the same host, protocol, port, etc.
128 """
128 """
129 return self.settings.get('websocket_url', '')
129 return self.settings.get('websocket_url', '')
130
130
131 @property
131 @property
132 def mathjax_url(self):
132 def mathjax_url(self):
133 return self.settings.get('mathjax_url', '')
133 return self.settings.get('mathjax_url', '')
134
134
135 @property
135 @property
136 def base_project_url(self):
136 def base_url(self):
137 return self.settings.get('base_project_url', '/')
137 return self.settings.get('base_url', '/')
138
138
139 @property
139 @property
140 def base_kernel_url(self):
140 def base_kernel_url(self):
141 return self.settings.get('base_kernel_url', '/')
141 return self.settings.get('base_kernel_url', '/')
142
142
143 #---------------------------------------------------------------
143 #---------------------------------------------------------------
144 # Manager objects
144 # Manager objects
145 #---------------------------------------------------------------
145 #---------------------------------------------------------------
146
146
147 @property
147 @property
148 def kernel_manager(self):
148 def kernel_manager(self):
149 return self.settings['kernel_manager']
149 return self.settings['kernel_manager']
150
150
151 @property
151 @property
152 def notebook_manager(self):
152 def notebook_manager(self):
153 return self.settings['notebook_manager']
153 return self.settings['notebook_manager']
154
154
155 @property
155 @property
156 def cluster_manager(self):
156 def cluster_manager(self):
157 return self.settings['cluster_manager']
157 return self.settings['cluster_manager']
158
158
159 @property
159 @property
160 def session_manager(self):
160 def session_manager(self):
161 return self.settings['session_manager']
161 return self.settings['session_manager']
162
162
163 @property
163 @property
164 def project_dir(self):
164 def project_dir(self):
165 return self.notebook_manager.notebook_dir
165 return self.notebook_manager.notebook_dir
166
166
167 #---------------------------------------------------------------
167 #---------------------------------------------------------------
168 # template rendering
168 # template rendering
169 #---------------------------------------------------------------
169 #---------------------------------------------------------------
170
170
171 def get_template(self, name):
171 def get_template(self, name):
172 """Return the jinja template object for a given name"""
172 """Return the jinja template object for a given name"""
173 return self.settings['jinja2_env'].get_template(name)
173 return self.settings['jinja2_env'].get_template(name)
174
174
175 def render_template(self, name, **ns):
175 def render_template(self, name, **ns):
176 ns.update(self.template_namespace)
176 ns.update(self.template_namespace)
177 template = self.get_template(name)
177 template = self.get_template(name)
178 return template.render(**ns)
178 return template.render(**ns)
179
179
180 @property
180 @property
181 def template_namespace(self):
181 def template_namespace(self):
182 return dict(
182 return dict(
183 base_project_url=self.base_project_url,
183 base_url=self.base_url,
184 base_kernel_url=self.base_kernel_url,
184 base_kernel_url=self.base_kernel_url,
185 logged_in=self.logged_in,
185 logged_in=self.logged_in,
186 login_available=self.login_available,
186 login_available=self.login_available,
187 static_url=self.static_url,
187 static_url=self.static_url,
188 )
188 )
189
189
190 def get_json_body(self):
190 def get_json_body(self):
191 """Return the body of the request as JSON data."""
191 """Return the body of the request as JSON data."""
192 if not self.request.body:
192 if not self.request.body:
193 return None
193 return None
194 # Do we need to call body.decode('utf-8') here?
194 # Do we need to call body.decode('utf-8') here?
195 body = self.request.body.strip().decode(u'utf-8')
195 body = self.request.body.strip().decode(u'utf-8')
196 try:
196 try:
197 model = json.loads(body)
197 model = json.loads(body)
198 except Exception:
198 except Exception:
199 self.log.debug("Bad JSON: %r", body)
199 self.log.debug("Bad JSON: %r", body)
200 self.log.error("Couldn't parse JSON", exc_info=True)
200 self.log.error("Couldn't parse JSON", exc_info=True)
201 raise web.HTTPError(400, u'Invalid JSON in body of request')
201 raise web.HTTPError(400, u'Invalid JSON in body of request')
202 return model
202 return model
203
203
204 def get_error_html(self, status_code, **kwargs):
204 def get_error_html(self, status_code, **kwargs):
205 """render custom error pages"""
205 """render custom error pages"""
206 exception = kwargs.get('exception')
206 exception = kwargs.get('exception')
207 message = ''
207 message = ''
208 status_message = responses.get(status_code, 'Unknown HTTP Error')
208 status_message = responses.get(status_code, 'Unknown HTTP Error')
209 if exception:
209 if exception:
210 # get the custom message, if defined
210 # get the custom message, if defined
211 try:
211 try:
212 message = exception.log_message % exception.args
212 message = exception.log_message % exception.args
213 except Exception:
213 except Exception:
214 pass
214 pass
215
215
216 # construct the custom reason, if defined
216 # construct the custom reason, if defined
217 reason = getattr(exception, 'reason', '')
217 reason = getattr(exception, 'reason', '')
218 if reason:
218 if reason:
219 status_message = reason
219 status_message = reason
220
220
221 # build template namespace
221 # build template namespace
222 ns = dict(
222 ns = dict(
223 status_code=status_code,
223 status_code=status_code,
224 status_message=status_message,
224 status_message=status_message,
225 message=message,
225 message=message,
226 exception=exception,
226 exception=exception,
227 )
227 )
228
228
229 # render the template
229 # render the template
230 try:
230 try:
231 html = self.render_template('%s.html' % status_code, **ns)
231 html = self.render_template('%s.html' % status_code, **ns)
232 except TemplateNotFound:
232 except TemplateNotFound:
233 self.log.debug("No template for %d", status_code)
233 self.log.debug("No template for %d", status_code)
234 html = self.render_template('error.html', **ns)
234 html = self.render_template('error.html', **ns)
235 return html
235 return html
236
236
237
237
238 class Template404(IPythonHandler):
238 class Template404(IPythonHandler):
239 """Render our 404 template"""
239 """Render our 404 template"""
240 def prepare(self):
240 def prepare(self):
241 raise web.HTTPError(404)
241 raise web.HTTPError(404)
242
242
243
243
244 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
244 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
245 """static files should only be accessible when logged in"""
245 """static files should only be accessible when logged in"""
246
246
247 @web.authenticated
247 @web.authenticated
248 def get(self, path):
248 def get(self, path):
249 if os.path.splitext(path)[1] == '.ipynb':
249 if os.path.splitext(path)[1] == '.ipynb':
250 name = os.path.basename(path)
250 name = os.path.basename(path)
251 self.set_header('Content-Type', 'application/json')
251 self.set_header('Content-Type', 'application/json')
252 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
252 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
253
253
254 return web.StaticFileHandler.get(self, path)
254 return web.StaticFileHandler.get(self, path)
255
255
256 def compute_etag(self):
256 def compute_etag(self):
257 return None
257 return None
258
258
259 def validate_absolute_path(self, root, absolute_path):
259 def validate_absolute_path(self, root, absolute_path):
260 """Validate and return the absolute path.
260 """Validate and return the absolute path.
261
261
262 Requires tornado 3.1
262 Requires tornado 3.1
263
263
264 Adding to tornado's own handling, forbids the serving of hidden files.
264 Adding to tornado's own handling, forbids the serving of hidden files.
265 """
265 """
266 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
266 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
267 abs_root = os.path.abspath(root)
267 abs_root = os.path.abspath(root)
268 if is_hidden(abs_path, abs_root):
268 if is_hidden(abs_path, abs_root):
269 raise web.HTTPError(404)
269 raise web.HTTPError(404)
270 return abs_path
270 return abs_path
271
271
272
272
273 def json_errors(method):
273 def json_errors(method):
274 """Decorate methods with this to return GitHub style JSON errors.
274 """Decorate methods with this to return GitHub style JSON errors.
275
275
276 This should be used on any JSON API on any handler method that can raise HTTPErrors.
276 This should be used on any JSON API on any handler method that can raise HTTPErrors.
277
277
278 This will grab the latest HTTPError exception using sys.exc_info
278 This will grab the latest HTTPError exception using sys.exc_info
279 and then:
279 and then:
280
280
281 1. Set the HTTP status code based on the HTTPError
281 1. Set the HTTP status code based on the HTTPError
282 2. Create and return a JSON body with a message field describing
282 2. Create and return a JSON body with a message field describing
283 the error in a human readable form.
283 the error in a human readable form.
284 """
284 """
285 @functools.wraps(method)
285 @functools.wraps(method)
286 def wrapper(self, *args, **kwargs):
286 def wrapper(self, *args, **kwargs):
287 try:
287 try:
288 result = method(self, *args, **kwargs)
288 result = method(self, *args, **kwargs)
289 except web.HTTPError as e:
289 except web.HTTPError as e:
290 status = e.status_code
290 status = e.status_code
291 message = e.log_message
291 message = e.log_message
292 self.set_status(e.status_code)
292 self.set_status(e.status_code)
293 self.finish(json.dumps(dict(message=message)))
293 self.finish(json.dumps(dict(message=message)))
294 except Exception:
294 except Exception:
295 self.log.error("Unhandled error in API request", exc_info=True)
295 self.log.error("Unhandled error in API request", exc_info=True)
296 status = 500
296 status = 500
297 message = "Unknown server error"
297 message = "Unknown server error"
298 t, value, tb = sys.exc_info()
298 t, value, tb = sys.exc_info()
299 self.set_status(status)
299 self.set_status(status)
300 tb_text = ''.join(traceback.format_exception(t, value, tb))
300 tb_text = ''.join(traceback.format_exception(t, value, tb))
301 reply = dict(message=message, traceback=tb_text)
301 reply = dict(message=message, traceback=tb_text)
302 self.finish(json.dumps(reply))
302 self.finish(json.dumps(reply))
303 else:
303 else:
304 return result
304 return result
305 return wrapper
305 return wrapper
306
306
307
307
308
308
309 #-----------------------------------------------------------------------------
309 #-----------------------------------------------------------------------------
310 # File handler
310 # File handler
311 #-----------------------------------------------------------------------------
311 #-----------------------------------------------------------------------------
312
312
313 # to minimize subclass changes:
313 # to minimize subclass changes:
314 HTTPError = web.HTTPError
314 HTTPError = web.HTTPError
315
315
316 class FileFindHandler(web.StaticFileHandler):
316 class FileFindHandler(web.StaticFileHandler):
317 """subclass of StaticFileHandler for serving files from a search path"""
317 """subclass of StaticFileHandler for serving files from a search path"""
318
318
319 # cache search results, don't search for files more than once
319 # cache search results, don't search for files more than once
320 _static_paths = {}
320 _static_paths = {}
321
321
322 def initialize(self, path, default_filename=None):
322 def initialize(self, path, default_filename=None):
323 if isinstance(path, string_types):
323 if isinstance(path, string_types):
324 path = [path]
324 path = [path]
325
325
326 self.root = tuple(
326 self.root = tuple(
327 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
327 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
328 )
328 )
329 self.default_filename = default_filename
329 self.default_filename = default_filename
330
330
331 def compute_etag(self):
331 def compute_etag(self):
332 return None
332 return None
333
333
334 @classmethod
334 @classmethod
335 def get_absolute_path(cls, roots, path):
335 def get_absolute_path(cls, roots, path):
336 """locate a file to serve on our static file search path"""
336 """locate a file to serve on our static file search path"""
337 with cls._lock:
337 with cls._lock:
338 if path in cls._static_paths:
338 if path in cls._static_paths:
339 return cls._static_paths[path]
339 return cls._static_paths[path]
340 try:
340 try:
341 abspath = os.path.abspath(filefind(path, roots))
341 abspath = os.path.abspath(filefind(path, roots))
342 except IOError:
342 except IOError:
343 # IOError means not found
343 # IOError means not found
344 return ''
344 return ''
345
345
346 cls._static_paths[path] = abspath
346 cls._static_paths[path] = abspath
347 return abspath
347 return abspath
348
348
349 def validate_absolute_path(self, root, absolute_path):
349 def validate_absolute_path(self, root, absolute_path):
350 """check if the file should be served (raises 404, 403, etc.)"""
350 """check if the file should be served (raises 404, 403, etc.)"""
351 if absolute_path == '':
351 if absolute_path == '':
352 raise web.HTTPError(404)
352 raise web.HTTPError(404)
353
353
354 for root in self.root:
354 for root in self.root:
355 if (absolute_path + os.sep).startswith(root):
355 if (absolute_path + os.sep).startswith(root):
356 break
356 break
357
357
358 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
358 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
359
359
360
360
361 class TrailingSlashHandler(web.RequestHandler):
361 class TrailingSlashHandler(web.RequestHandler):
362 """Simple redirect handler that strips trailing slashes
362 """Simple redirect handler that strips trailing slashes
363
363
364 This should be the first, highest priority handler.
364 This should be the first, highest priority handler.
365 """
365 """
366
366
367 SUPPORTED_METHODS = ['GET']
367 SUPPORTED_METHODS = ['GET']
368
368
369 def get(self):
369 def get(self):
370 self.redirect(self.request.uri.rstrip('/'))
370 self.redirect(self.request.uri.rstrip('/'))
371
371
372 #-----------------------------------------------------------------------------
372 #-----------------------------------------------------------------------------
373 # URL pattern fragments for re-use
373 # URL pattern fragments for re-use
374 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
375
375
376 path_regex = r"(?P<path>(?:/.*)*)"
376 path_regex = r"(?P<path>(?:/.*)*)"
377 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
377 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
378 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
378 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
379
379
380 #-----------------------------------------------------------------------------
380 #-----------------------------------------------------------------------------
381 # URL to handler mappings
381 # URL to handler mappings
382 #-----------------------------------------------------------------------------
382 #-----------------------------------------------------------------------------
383
383
384
384
385 default_handlers = [
385 default_handlers = [
386 (r".*/", TrailingSlashHandler)
386 (r".*/", TrailingSlashHandler)
387 ]
387 ]
@@ -1,90 +1,90
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
24 from ..utils import url_path_join, url_escape
24 from ..utils import url_path_join, url_escape
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Handlers
27 # Handlers
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 class NotebookHandler(IPythonHandler):
31 class NotebookHandler(IPythonHandler):
32
32
33 @web.authenticated
33 @web.authenticated
34 def get(self, path='', name=None):
34 def get(self, path='', name=None):
35 """get renders the notebook template if a name is given, or
35 """get renders the notebook template if a name is given, or
36 redirects to the '/files/' handler if the name is not given."""
36 redirects to the '/files/' handler if the name is not given."""
37 path = path.strip('/')
37 path = path.strip('/')
38 nbm = self.notebook_manager
38 nbm = self.notebook_manager
39 if name is None:
39 if name is None:
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41
41
42 # a .ipynb filename was given
42 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
43 if not nbm.notebook_exists(name, path):
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 name = url_escape(name)
45 name = url_escape(name)
46 path = url_escape(path)
46 path = url_escape(path)
47 self.write(self.render_template('notebook.html',
47 self.write(self.render_template('notebook.html',
48 project=self.project_dir,
48 project=self.project_dir,
49 notebook_path=path,
49 notebook_path=path,
50 notebook_name=name,
50 notebook_name=name,
51 kill_kernel=False,
51 kill_kernel=False,
52 mathjax_url=self.mathjax_url,
52 mathjax_url=self.mathjax_url,
53 )
53 )
54 )
54 )
55
55
56 class NotebookRedirectHandler(IPythonHandler):
56 class NotebookRedirectHandler(IPythonHandler):
57 def get(self, path=''):
57 def get(self, path=''):
58 nbm = self.notebook_manager
58 nbm = self.notebook_manager
59 if nbm.path_exists(path):
59 if nbm.path_exists(path):
60 # it's a *directory*, redirect to /tree
60 # it's a *directory*, redirect to /tree
61 url = url_path_join(self.base_project_url, 'tree', path)
61 url = url_path_join(self.base_url, 'tree', path)
62 else:
62 else:
63 # otherwise, redirect to /files
63 # otherwise, redirect to /files
64 if '/files/' in path:
64 if '/files/' in path:
65 # redirect without files/ iff it would 404
65 # redirect without files/ iff it would 404
66 # this preserves pre-2.0-style 'files/' links
66 # this preserves pre-2.0-style 'files/' links
67 # FIXME: this is hardcoded based on notebook_path,
67 # FIXME: this is hardcoded based on notebook_path,
68 # but so is the files handler itself,
68 # but so is the files handler itself,
69 # so it should work until both are cleaned up.
69 # so it should work until both are cleaned up.
70 parts = path.split('/')
70 parts = path.split('/')
71 files_path = os.path.join(nbm.notebook_dir, *parts)
71 files_path = os.path.join(nbm.notebook_dir, *parts)
72 self.log.warn("filespath: %s", files_path)
72 self.log.warn("filespath: %s", files_path)
73 if not os.path.exists(files_path):
73 if not os.path.exists(files_path):
74 path = path.replace('/files/', '/', 1)
74 path = path.replace('/files/', '/', 1)
75
75
76 url = url_path_join(self.base_project_url, 'files', path)
76 url = url_path_join(self.base_url, 'files', path)
77 url = url_escape(url)
77 url = url_escape(url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
79 self.redirect(url)
79 self.redirect(url)
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # URL to handler mappings
82 # URL to handler mappings
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85
85
86 default_handlers = [
86 default_handlers = [
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
89 ]
89 ]
90
90
@@ -1,837 +1,842
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import select
27 import select
28 import signal
28 import signal
29 import socket
29 import socket
30 import sys
30 import sys
31 import threading
31 import threading
32 import time
32 import time
33 import webbrowser
33 import webbrowser
34
34
35
35
36 # Third party
36 # Third party
37 # check for pyzmq 2.1.11
37 # check for pyzmq 2.1.11
38 from IPython.utils.zmqrelated import check_for_zmq
38 from IPython.utils.zmqrelated import check_for_zmq
39 check_for_zmq('2.1.11', 'IPython.html')
39 check_for_zmq('2.1.11', 'IPython.html')
40
40
41 from jinja2 import Environment, FileSystemLoader
41 from jinja2 import Environment, FileSystemLoader
42
42
43 # Install the pyzmq ioloop. This has to be done before anything else from
43 # Install the pyzmq ioloop. This has to be done before anything else from
44 # tornado is imported.
44 # tornado is imported.
45 from zmq.eventloop import ioloop
45 from zmq.eventloop import ioloop
46 ioloop.install()
46 ioloop.install()
47
47
48 # check for tornado 3.1.0
48 # check for tornado 3.1.0
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 try:
50 try:
51 import tornado
51 import tornado
52 except ImportError:
52 except ImportError:
53 raise ImportError(msg)
53 raise ImportError(msg)
54 try:
54 try:
55 version_info = tornado.version_info
55 version_info = tornado.version_info
56 except AttributeError:
56 except AttributeError:
57 raise ImportError(msg + ", but you have < 1.1.0")
57 raise ImportError(msg + ", but you have < 1.1.0")
58 if version_info < (3,1,0):
58 if version_info < (3,1,0):
59 raise ImportError(msg + ", but you have %s" % tornado.version)
59 raise ImportError(msg + ", but you have %s" % tornado.version)
60
60
61 from tornado import httpserver
61 from tornado import httpserver
62 from tornado import web
62 from tornado import web
63
63
64 # Our own libraries
64 # Our own libraries
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from .base.handlers import Template404
66 from .base.handlers import Template404
67 from .log import log_request
67 from .log import log_request
68 from .services.kernels.kernelmanager import MappingKernelManager
68 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.notebooks.nbmanager import NotebookManager
69 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.clusters.clustermanager import ClusterManager
71 from .services.clusters.clustermanager import ClusterManager
72 from .services.sessions.sessionmanager import SessionManager
72 from .services.sessions.sessionmanager import SessionManager
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config.application import catch_config_error, boolean_flag
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
78 from IPython.core.profiledir import ProfileDir
79 from IPython.consoleapp import IPythonConsoleApp
79 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.kernel import swallow_argv
80 from IPython.kernel import swallow_argv
81 from IPython.kernel.zmq.session import default_secure
81 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.kernelapp import (
82 from IPython.kernel.zmq.kernelapp import (
83 kernel_flags,
83 kernel_flags,
84 kernel_aliases,
84 kernel_aliases,
85 )
85 )
86 from IPython.utils.importstring import import_item
86 from IPython.utils.importstring import import_item
87 from IPython.utils.localinterfaces import localhost
87 from IPython.utils.localinterfaces import localhost
88 from IPython.utils import submodule
88 from IPython.utils import submodule
89 from IPython.utils.traitlets import (
89 from IPython.utils.traitlets import (
90 Dict, Unicode, Integer, List, Bool, Bytes,
90 Dict, Unicode, Integer, List, Bool, Bytes,
91 DottedObjectName
91 DottedObjectName
92 )
92 )
93 from IPython.utils import py3compat
93 from IPython.utils import py3compat
94 from IPython.utils.path import filefind, get_ipython_dir
94 from IPython.utils.path import filefind, get_ipython_dir
95
95
96 from .utils import url_path_join
96 from .utils import url_path_join
97
97
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99 # Module globals
99 # Module globals
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101
101
102 _examples = """
102 _examples = """
103 ipython notebook # start the notebook
103 ipython notebook # start the notebook
104 ipython notebook --profile=sympy # use the sympy profile
104 ipython notebook --profile=sympy # use the sympy profile
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
106 """
106 """
107
107
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109 # Helper functions
109 # Helper functions
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111
111
112 def random_ports(port, n):
112 def random_ports(port, n):
113 """Generate a list of n random ports near the given port.
113 """Generate a list of n random ports near the given port.
114
114
115 The first 5 ports will be sequential, and the remaining n-5 will be
115 The first 5 ports will be sequential, and the remaining n-5 will be
116 randomly selected in the range [port-2*n, port+2*n].
116 randomly selected in the range [port-2*n, port+2*n].
117 """
117 """
118 for i in range(min(5, n)):
118 for i in range(min(5, n)):
119 yield port + i
119 yield port + i
120 for i in range(n-5):
120 for i in range(n-5):
121 yield max(1, port + random.randint(-2*n, 2*n))
121 yield max(1, port + random.randint(-2*n, 2*n))
122
122
123 def load_handlers(name):
123 def load_handlers(name):
124 """Load the (URL pattern, handler) tuples for each component."""
124 """Load the (URL pattern, handler) tuples for each component."""
125 name = 'IPython.html.' + name
125 name = 'IPython.html.' + name
126 mod = __import__(name, fromlist=['default_handlers'])
126 mod = __import__(name, fromlist=['default_handlers'])
127 return mod.default_handlers
127 return mod.default_handlers
128
128
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130 # The Tornado web application
130 # The Tornado web application
131 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
132
132
133 class NotebookWebApplication(web.Application):
133 class NotebookWebApplication(web.Application):
134
134
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, log, base_project_url,
136 cluster_manager, session_manager, log, base_url,
137 settings_overrides):
137 settings_overrides):
138
138
139 settings = self.init_settings(
139 settings = self.init_settings(
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 session_manager, log, base_project_url, settings_overrides)
141 session_manager, log, base_url, settings_overrides)
142 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
143
143
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
145
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 cluster_manager, session_manager, log, base_project_url,
147 cluster_manager, session_manager, log, base_url,
148 settings_overrides):
148 settings_overrides):
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # base_project_url will always be unicode, which will in turn
150 # base_url will always be unicode, which will in turn
151 # make the patterns unicode, and ultimately result in unicode
151 # make the patterns unicode, and ultimately result in unicode
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # This enforces that base_project_url be ascii in that situation.
153 # This enforces that base_url be ascii in that situation.
154 #
154 #
155 # Note that the URLs these patterns check against are escaped,
155 # Note that the URLs these patterns check against are escaped,
156 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
156 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
157 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
157 base_url = py3compat.unicode_to_str(base_url, 'ascii')
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 settings = dict(
159 settings = dict(
160 # basics
160 # basics
161 log_function=log_request,
161 log_function=log_request,
162 base_project_url=base_project_url,
162 base_url=base_url,
163 base_kernel_url=ipython_app.base_kernel_url,
163 base_kernel_url=ipython_app.base_kernel_url,
164 template_path=template_path,
164 template_path=template_path,
165 static_path=ipython_app.static_file_path,
165 static_path=ipython_app.static_file_path,
166 static_handler_class = FileFindHandler,
166 static_handler_class = FileFindHandler,
167 static_url_prefix = url_path_join(base_project_url,'/static/'),
167 static_url_prefix = url_path_join(base_url,'/static/'),
168
168
169 # authentication
169 # authentication
170 cookie_secret=ipython_app.cookie_secret,
170 cookie_secret=ipython_app.cookie_secret,
171 login_url=url_path_join(base_project_url,'/login'),
171 login_url=url_path_join(base_url,'/login'),
172 password=ipython_app.password,
172 password=ipython_app.password,
173
173
174 # managers
174 # managers
175 kernel_manager=kernel_manager,
175 kernel_manager=kernel_manager,
176 notebook_manager=notebook_manager,
176 notebook_manager=notebook_manager,
177 cluster_manager=cluster_manager,
177 cluster_manager=cluster_manager,
178 session_manager=session_manager,
178 session_manager=session_manager,
179
179
180 # IPython stuff
180 # IPython stuff
181 nbextensions_path = ipython_app.nbextensions_path,
181 nbextensions_path = ipython_app.nbextensions_path,
182 mathjax_url=ipython_app.mathjax_url,
182 mathjax_url=ipython_app.mathjax_url,
183 config=ipython_app.config,
183 config=ipython_app.config,
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
185 )
185 )
186
186
187 # allow custom overrides for the tornado web app.
187 # allow custom overrides for the tornado web app.
188 settings.update(settings_overrides)
188 settings.update(settings_overrides)
189 return settings
189 return settings
190
190
191 def init_handlers(self, settings):
191 def init_handlers(self, settings):
192 # Load the (URL pattern, handler) tuples for each component.
192 # Load the (URL pattern, handler) tuples for each component.
193 handlers = []
193 handlers = []
194 handlers.extend(load_handlers('base.handlers'))
194 handlers.extend(load_handlers('base.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('auth.login'))
196 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.logout'))
197 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('notebook.handlers'))
198 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
205 handlers.extend([
205 handlers.extend([
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 ])
208 ])
209 # prepend base_project_url onto the patterns that we match
209 # prepend base_url onto the patterns that we match
210 new_handlers = []
210 new_handlers = []
211 for handler in handlers:
211 for handler in handlers:
212 pattern = url_path_join(settings['base_project_url'], handler[0])
212 pattern = url_path_join(settings['base_url'], handler[0])
213 new_handler = tuple([pattern] + list(handler[1:]))
213 new_handler = tuple([pattern] + list(handler[1:]))
214 new_handlers.append(new_handler)
214 new_handlers.append(new_handler)
215 # add 404 on the end, which will catch everything that falls through
215 # add 404 on the end, which will catch everything that falls through
216 new_handlers.append((r'(.*)', Template404))
216 new_handlers.append((r'(.*)', Template404))
217 return new_handlers
217 return new_handlers
218
218
219
219
220 class NbserverListApp(BaseIPythonApplication):
220 class NbserverListApp(BaseIPythonApplication):
221
221
222 description="List currently running notebook servers in this profile."
222 description="List currently running notebook servers in this profile."
223
223
224 flags = dict(
224 flags = dict(
225 json=({'NbserverListApp': {'json': True}},
225 json=({'NbserverListApp': {'json': True}},
226 "Produce machine-readable JSON output."),
226 "Produce machine-readable JSON output."),
227 )
227 )
228
228
229 json = Bool(False, config=True,
229 json = Bool(False, config=True,
230 help="If True, each line of output will be a JSON object with the "
230 help="If True, each line of output will be a JSON object with the "
231 "details from the server info file.")
231 "details from the server info file.")
232
232
233 def start(self):
233 def start(self):
234 if not self.json:
234 if not self.json:
235 print("Currently running servers:")
235 print("Currently running servers:")
236 for serverinfo in list_running_servers(self.profile):
236 for serverinfo in list_running_servers(self.profile):
237 if self.json:
237 if self.json:
238 print(json.dumps(serverinfo))
238 print(json.dumps(serverinfo))
239 else:
239 else:
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
241
241
242 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
243 # Aliases and Flags
243 # Aliases and Flags
244 #-----------------------------------------------------------------------------
244 #-----------------------------------------------------------------------------
245
245
246 flags = dict(kernel_flags)
246 flags = dict(kernel_flags)
247 flags['no-browser']=(
247 flags['no-browser']=(
248 {'NotebookApp' : {'open_browser' : False}},
248 {'NotebookApp' : {'open_browser' : False}},
249 "Don't open the notebook in a browser after startup."
249 "Don't open the notebook in a browser after startup."
250 )
250 )
251 flags['no-mathjax']=(
251 flags['no-mathjax']=(
252 {'NotebookApp' : {'enable_mathjax' : False}},
252 {'NotebookApp' : {'enable_mathjax' : False}},
253 """Disable MathJax
253 """Disable MathJax
254
254
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
256 very large, so you may want to disable it if you have a slow internet
256 very large, so you may want to disable it if you have a slow internet
257 connection, or for offline use of the notebook.
257 connection, or for offline use of the notebook.
258
258
259 When disabled, equations etc. will appear as their untransformed TeX source.
259 When disabled, equations etc. will appear as their untransformed TeX source.
260 """
260 """
261 )
261 )
262
262
263 # Add notebook manager flags
263 # Add notebook manager flags
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
266 'Do not auto-save .py scripts for every notebook'))
266 'Do not auto-save .py scripts for every notebook'))
267
267
268 # the flags that are specific to the frontend
268 # the flags that are specific to the frontend
269 # these must be scrubbed before being passed to the kernel,
269 # these must be scrubbed before being passed to the kernel,
270 # or it will raise an error on unrecognized flags
270 # or it will raise an error on unrecognized flags
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
272
272
273 aliases = dict(kernel_aliases)
273 aliases = dict(kernel_aliases)
274
274
275 aliases.update({
275 aliases.update({
276 'ip': 'NotebookApp.ip',
276 'ip': 'NotebookApp.ip',
277 'port': 'NotebookApp.port',
277 'port': 'NotebookApp.port',
278 'port-retries': 'NotebookApp.port_retries',
278 'port-retries': 'NotebookApp.port_retries',
279 'transport': 'KernelManager.transport',
279 'transport': 'KernelManager.transport',
280 'keyfile': 'NotebookApp.keyfile',
280 'keyfile': 'NotebookApp.keyfile',
281 'certfile': 'NotebookApp.certfile',
281 'certfile': 'NotebookApp.certfile',
282 'notebook-dir': 'NotebookManager.notebook_dir',
282 'notebook-dir': 'NotebookManager.notebook_dir',
283 'browser': 'NotebookApp.browser',
283 'browser': 'NotebookApp.browser',
284 })
284 })
285
285
286 # remove ipkernel flags that are singletons, and don't make sense in
286 # remove ipkernel flags that are singletons, and don't make sense in
287 # multi-kernel evironment:
287 # multi-kernel evironment:
288 aliases.pop('f', None)
288 aliases.pop('f', None)
289
289
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
291 u'notebook-dir', u'profile', u'profile-dir']
291 u'notebook-dir', u'profile', u'profile-dir']
292
292
293 #-----------------------------------------------------------------------------
293 #-----------------------------------------------------------------------------
294 # NotebookApp
294 # NotebookApp
295 #-----------------------------------------------------------------------------
295 #-----------------------------------------------------------------------------
296
296
297 class NotebookApp(BaseIPythonApplication):
297 class NotebookApp(BaseIPythonApplication):
298
298
299 name = 'ipython-notebook'
299 name = 'ipython-notebook'
300
300
301 description = """
301 description = """
302 The IPython HTML Notebook.
302 The IPython HTML Notebook.
303
303
304 This launches a Tornado based HTML Notebook Server that serves up an
304 This launches a Tornado based HTML Notebook Server that serves up an
305 HTML5/Javascript Notebook client.
305 HTML5/Javascript Notebook client.
306 """
306 """
307 examples = _examples
307 examples = _examples
308
308
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
310 FileNotebookManager]
310 FileNotebookManager]
311 flags = Dict(flags)
311 flags = Dict(flags)
312 aliases = Dict(aliases)
312 aliases = Dict(aliases)
313
313
314 subcommands = dict(
314 subcommands = dict(
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
316 )
316 )
317
317
318 kernel_argv = List(Unicode)
318 kernel_argv = List(Unicode)
319
319
320 def _log_level_default(self):
320 def _log_level_default(self):
321 return logging.INFO
321 return logging.INFO
322
322
323 def _log_format_default(self):
323 def _log_format_default(self):
324 """override default log format to include time"""
324 """override default log format to include time"""
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
326
326
327 # create requested profiles by default, if they don't exist:
327 # create requested profiles by default, if they don't exist:
328 auto_create = Bool(True)
328 auto_create = Bool(True)
329
329
330 # file to be opened in the notebook server
330 # file to be opened in the notebook server
331 file_to_run = Unicode('')
331 file_to_run = Unicode('')
332
332
333 # Network related information.
333 # Network related information.
334
334
335 ip = Unicode(config=True,
335 ip = Unicode(config=True,
336 help="The IP address the notebook server will listen on."
336 help="The IP address the notebook server will listen on."
337 )
337 )
338 def _ip_default(self):
338 def _ip_default(self):
339 return localhost()
339 return localhost()
340
340
341 def _ip_changed(self, name, old, new):
341 def _ip_changed(self, name, old, new):
342 if new == u'*': self.ip = u''
342 if new == u'*': self.ip = u''
343
343
344 port = Integer(8888, config=True,
344 port = Integer(8888, config=True,
345 help="The port the notebook server will listen on."
345 help="The port the notebook server will listen on."
346 )
346 )
347 port_retries = Integer(50, config=True,
347 port_retries = Integer(50, config=True,
348 help="The number of additional ports to try if the specified port is not available."
348 help="The number of additional ports to try if the specified port is not available."
349 )
349 )
350
350
351 certfile = Unicode(u'', config=True,
351 certfile = Unicode(u'', config=True,
352 help="""The full path to an SSL/TLS certificate file."""
352 help="""The full path to an SSL/TLS certificate file."""
353 )
353 )
354
354
355 keyfile = Unicode(u'', config=True,
355 keyfile = Unicode(u'', config=True,
356 help="""The full path to a private key file for usage with SSL/TLS."""
356 help="""The full path to a private key file for usage with SSL/TLS."""
357 )
357 )
358
358
359 cookie_secret = Bytes(b'', config=True,
359 cookie_secret = Bytes(b'', config=True,
360 help="""The random bytes used to secure cookies.
360 help="""The random bytes used to secure cookies.
361 By default this is a new random number every time you start the Notebook.
361 By default this is a new random number every time you start the Notebook.
362 Set it to a value in a config file to enable logins to persist across server sessions.
362 Set it to a value in a config file to enable logins to persist across server sessions.
363
363
364 Note: Cookie secrets should be kept private, do not share config files with
364 Note: Cookie secrets should be kept private, do not share config files with
365 cookie_secret stored in plaintext (you can read the value from a file).
365 cookie_secret stored in plaintext (you can read the value from a file).
366 """
366 """
367 )
367 )
368 def _cookie_secret_default(self):
368 def _cookie_secret_default(self):
369 return os.urandom(1024)
369 return os.urandom(1024)
370
370
371 password = Unicode(u'', config=True,
371 password = Unicode(u'', config=True,
372 help="""Hashed password to use for web authentication.
372 help="""Hashed password to use for web authentication.
373
373
374 To generate, type in a python/IPython shell:
374 To generate, type in a python/IPython shell:
375
375
376 from IPython.lib import passwd; passwd()
376 from IPython.lib import passwd; passwd()
377
377
378 The string should be of the form type:salt:hashed-password.
378 The string should be of the form type:salt:hashed-password.
379 """
379 """
380 )
380 )
381
381
382 open_browser = Bool(True, config=True,
382 open_browser = Bool(True, config=True,
383 help="""Whether to open in a browser after starting.
383 help="""Whether to open in a browser after starting.
384 The specific browser used is platform dependent and
384 The specific browser used is platform dependent and
385 determined by the python standard library `webbrowser`
385 determined by the python standard library `webbrowser`
386 module, unless it is overridden using the --browser
386 module, unless it is overridden using the --browser
387 (NotebookApp.browser) configuration option.
387 (NotebookApp.browser) configuration option.
388 """)
388 """)
389
389
390 browser = Unicode(u'', config=True,
390 browser = Unicode(u'', config=True,
391 help="""Specify what command to use to invoke a web
391 help="""Specify what command to use to invoke a web
392 browser when opening the notebook. If not specified, the
392 browser when opening the notebook. If not specified, the
393 default browser will be determined by the `webbrowser`
393 default browser will be determined by the `webbrowser`
394 standard library module, which allows setting of the
394 standard library module, which allows setting of the
395 BROWSER environment variable to override it.
395 BROWSER environment variable to override it.
396 """)
396 """)
397
397
398 webapp_settings = Dict(config=True,
398 webapp_settings = Dict(config=True,
399 help="Supply overrides for the tornado.web.Application that the "
399 help="Supply overrides for the tornado.web.Application that the "
400 "IPython notebook uses.")
400 "IPython notebook uses.")
401
401
402 enable_mathjax = Bool(True, config=True,
402 enable_mathjax = Bool(True, config=True,
403 help="""Whether to enable MathJax for typesetting math/TeX
403 help="""Whether to enable MathJax for typesetting math/TeX
404
404
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
406 very large, so you may want to disable it if you have a slow internet
406 very large, so you may want to disable it if you have a slow internet
407 connection, or for offline use of the notebook.
407 connection, or for offline use of the notebook.
408
408
409 When disabled, equations etc. will appear as their untransformed TeX source.
409 When disabled, equations etc. will appear as their untransformed TeX source.
410 """
410 """
411 )
411 )
412 def _enable_mathjax_changed(self, name, old, new):
412 def _enable_mathjax_changed(self, name, old, new):
413 """set mathjax url to empty if mathjax is disabled"""
413 """set mathjax url to empty if mathjax is disabled"""
414 if not new:
414 if not new:
415 self.mathjax_url = u''
415 self.mathjax_url = u''
416
416
417 base_project_url = Unicode('/', config=True,
417 base_url = Unicode('/', config=True,
418 help='''The base URL for the notebook server.
418 help='''The base URL for the notebook server.
419
419
420 Leading and trailing slashes can be omitted,
420 Leading and trailing slashes can be omitted,
421 and will automatically be added.
421 and will automatically be added.
422 ''')
422 ''')
423 def _base_project_url_changed(self, name, old, new):
423 def _base_url_changed(self, name, old, new):
424 if not new.startswith('/'):
424 if not new.startswith('/'):
425 self.base_project_url = '/'+new
425 self.base_url = '/'+new
426 elif not new.endswith('/'):
426 elif not new.endswith('/'):
427 self.base_project_url = new+'/'
427 self.base_url = new+'/'
428
429 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
430 def _base_project_url_changed(self, name, old, new):
431 self.log.warn("base_project_url is deprecated, use base_url")
432 self.base_url = new
428
433
429 base_kernel_url = Unicode('/', config=True,
434 base_kernel_url = Unicode('/', config=True,
430 help='''The base URL for the kernel server
435 help='''The base URL for the kernel server
431
436
432 Leading and trailing slashes can be omitted,
437 Leading and trailing slashes can be omitted,
433 and will automatically be added.
438 and will automatically be added.
434 ''')
439 ''')
435 def _base_kernel_url_changed(self, name, old, new):
440 def _base_kernel_url_changed(self, name, old, new):
436 if not new.startswith('/'):
441 if not new.startswith('/'):
437 self.base_kernel_url = '/'+new
442 self.base_kernel_url = '/'+new
438 elif not new.endswith('/'):
443 elif not new.endswith('/'):
439 self.base_kernel_url = new+'/'
444 self.base_kernel_url = new+'/'
440
445
441 websocket_url = Unicode("", config=True,
446 websocket_url = Unicode("", config=True,
442 help="""The base URL for the websocket server,
447 help="""The base URL for the websocket server,
443 if it differs from the HTTP server (hint: it almost certainly doesn't).
448 if it differs from the HTTP server (hint: it almost certainly doesn't).
444
449
445 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
450 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
446 """
451 """
447 )
452 )
448
453
449 extra_static_paths = List(Unicode, config=True,
454 extra_static_paths = List(Unicode, config=True,
450 help="""Extra paths to search for serving static files.
455 help="""Extra paths to search for serving static files.
451
456
452 This allows adding javascript/css to be available from the notebook server machine,
457 This allows adding javascript/css to be available from the notebook server machine,
453 or overriding individual files in the IPython"""
458 or overriding individual files in the IPython"""
454 )
459 )
455 def _extra_static_paths_default(self):
460 def _extra_static_paths_default(self):
456 return [os.path.join(self.profile_dir.location, 'static')]
461 return [os.path.join(self.profile_dir.location, 'static')]
457
462
458 @property
463 @property
459 def static_file_path(self):
464 def static_file_path(self):
460 """return extra paths + the default location"""
465 """return extra paths + the default location"""
461 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
466 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
462
467
463 nbextensions_path = List(Unicode, config=True,
468 nbextensions_path = List(Unicode, config=True,
464 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
469 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
465 )
470 )
466 def _nbextensions_path_default(self):
471 def _nbextensions_path_default(self):
467 return [os.path.join(get_ipython_dir(), 'nbextensions')]
472 return [os.path.join(get_ipython_dir(), 'nbextensions')]
468
473
469 mathjax_url = Unicode("", config=True,
474 mathjax_url = Unicode("", config=True,
470 help="""The url for MathJax.js."""
475 help="""The url for MathJax.js."""
471 )
476 )
472 def _mathjax_url_default(self):
477 def _mathjax_url_default(self):
473 if not self.enable_mathjax:
478 if not self.enable_mathjax:
474 return u''
479 return u''
475 static_url_prefix = self.webapp_settings.get("static_url_prefix",
480 static_url_prefix = self.webapp_settings.get("static_url_prefix",
476 url_path_join(self.base_project_url, "static")
481 url_path_join(self.base_url, "static")
477 )
482 )
478
483
479 # try local mathjax, either in nbextensions/mathjax or static/mathjax
484 # try local mathjax, either in nbextensions/mathjax or static/mathjax
480 for (url_prefix, search_path) in [
485 for (url_prefix, search_path) in [
481 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
486 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
482 (static_url_prefix, self.static_file_path),
487 (static_url_prefix, self.static_file_path),
483 ]:
488 ]:
484 self.log.debug("searching for local mathjax in %s", search_path)
489 self.log.debug("searching for local mathjax in %s", search_path)
485 try:
490 try:
486 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
491 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
487 except IOError:
492 except IOError:
488 continue
493 continue
489 else:
494 else:
490 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
495 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
491 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
496 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
492 return url
497 return url
493
498
494 # no local mathjax, serve from CDN
499 # no local mathjax, serve from CDN
495 if self.certfile:
500 if self.certfile:
496 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
501 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
497 host = u"https://c328740.ssl.cf1.rackcdn.com"
502 host = u"https://c328740.ssl.cf1.rackcdn.com"
498 else:
503 else:
499 host = u"http://cdn.mathjax.org"
504 host = u"http://cdn.mathjax.org"
500
505
501 url = host + u"/mathjax/latest/MathJax.js"
506 url = host + u"/mathjax/latest/MathJax.js"
502 self.log.info("Using MathJax from CDN: %s", url)
507 self.log.info("Using MathJax from CDN: %s", url)
503 return url
508 return url
504
509
505 def _mathjax_url_changed(self, name, old, new):
510 def _mathjax_url_changed(self, name, old, new):
506 if new and not self.enable_mathjax:
511 if new and not self.enable_mathjax:
507 # enable_mathjax=False overrides mathjax_url
512 # enable_mathjax=False overrides mathjax_url
508 self.mathjax_url = u''
513 self.mathjax_url = u''
509 else:
514 else:
510 self.log.info("Using MathJax: %s", new)
515 self.log.info("Using MathJax: %s", new)
511
516
512 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
517 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
513 config=True,
518 config=True,
514 help='The notebook manager class to use.')
519 help='The notebook manager class to use.')
515
520
516 trust_xheaders = Bool(False, config=True,
521 trust_xheaders = Bool(False, config=True,
517 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
522 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
518 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
523 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
519 )
524 )
520
525
521 info_file = Unicode()
526 info_file = Unicode()
522
527
523 def _info_file_default(self):
528 def _info_file_default(self):
524 info_file = "nbserver-%s.json"%os.getpid()
529 info_file = "nbserver-%s.json"%os.getpid()
525 return os.path.join(self.profile_dir.security_dir, info_file)
530 return os.path.join(self.profile_dir.security_dir, info_file)
526
531
527 def parse_command_line(self, argv=None):
532 def parse_command_line(self, argv=None):
528 super(NotebookApp, self).parse_command_line(argv)
533 super(NotebookApp, self).parse_command_line(argv)
529
534
530 if self.extra_args:
535 if self.extra_args:
531 arg0 = self.extra_args[0]
536 arg0 = self.extra_args[0]
532 f = os.path.abspath(arg0)
537 f = os.path.abspath(arg0)
533 self.argv.remove(arg0)
538 self.argv.remove(arg0)
534 if not os.path.exists(f):
539 if not os.path.exists(f):
535 self.log.critical("No such file or directory: %s", f)
540 self.log.critical("No such file or directory: %s", f)
536 self.exit(1)
541 self.exit(1)
537 if os.path.isdir(f):
542 if os.path.isdir(f):
538 self.config.FileNotebookManager.notebook_dir = f
543 self.config.FileNotebookManager.notebook_dir = f
539 elif os.path.isfile(f):
544 elif os.path.isfile(f):
540 self.file_to_run = f
545 self.file_to_run = f
541
546
542 def init_kernel_argv(self):
547 def init_kernel_argv(self):
543 """construct the kernel arguments"""
548 """construct the kernel arguments"""
544 # Scrub frontend-specific flags
549 # Scrub frontend-specific flags
545 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
550 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
546 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
551 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
547 self.log.warn('\n '.join([
552 self.log.warn('\n '.join([
548 "Starting all kernels in pylab mode is not recommended,",
553 "Starting all kernels in pylab mode is not recommended,",
549 "and will be disabled in a future release.",
554 "and will be disabled in a future release.",
550 "Please use the %matplotlib magic to enable matplotlib instead.",
555 "Please use the %matplotlib magic to enable matplotlib instead.",
551 "pylab implies many imports, which can have confusing side effects",
556 "pylab implies many imports, which can have confusing side effects",
552 "and harm the reproducibility of your notebooks.",
557 "and harm the reproducibility of your notebooks.",
553 ]))
558 ]))
554 # Kernel should inherit default config file from frontend
559 # Kernel should inherit default config file from frontend
555 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
560 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
556 # Kernel should get *absolute* path to profile directory
561 # Kernel should get *absolute* path to profile directory
557 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
562 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
558
563
559 def init_configurables(self):
564 def init_configurables(self):
560 # force Session default to be secure
565 # force Session default to be secure
561 default_secure(self.config)
566 default_secure(self.config)
562 self.kernel_manager = MappingKernelManager(
567 self.kernel_manager = MappingKernelManager(
563 parent=self, log=self.log, kernel_argv=self.kernel_argv,
568 parent=self, log=self.log, kernel_argv=self.kernel_argv,
564 connection_dir = self.profile_dir.security_dir,
569 connection_dir = self.profile_dir.security_dir,
565 )
570 )
566 kls = import_item(self.notebook_manager_class)
571 kls = import_item(self.notebook_manager_class)
567 self.notebook_manager = kls(parent=self, log=self.log)
572 self.notebook_manager = kls(parent=self, log=self.log)
568 self.session_manager = SessionManager(parent=self, log=self.log)
573 self.session_manager = SessionManager(parent=self, log=self.log)
569 self.cluster_manager = ClusterManager(parent=self, log=self.log)
574 self.cluster_manager = ClusterManager(parent=self, log=self.log)
570 self.cluster_manager.update_profiles()
575 self.cluster_manager.update_profiles()
571
576
572 def init_logging(self):
577 def init_logging(self):
573 # This prevents double log messages because tornado use a root logger that
578 # This prevents double log messages because tornado use a root logger that
574 # self.log is a child of. The logging module dipatches log messages to a log
579 # self.log is a child of. The logging module dipatches log messages to a log
575 # and all of its ancenstors until propagate is set to False.
580 # and all of its ancenstors until propagate is set to False.
576 self.log.propagate = False
581 self.log.propagate = False
577
582
578 # hook up tornado 3's loggers to our app handlers
583 # hook up tornado 3's loggers to our app handlers
579 for name in ('access', 'application', 'general'):
584 for name in ('access', 'application', 'general'):
580 logger = logging.getLogger('tornado.%s' % name)
585 logger = logging.getLogger('tornado.%s' % name)
581 logger.parent = self.log
586 logger.parent = self.log
582 logger.setLevel(self.log.level)
587 logger.setLevel(self.log.level)
583
588
584 def init_webapp(self):
589 def init_webapp(self):
585 """initialize tornado webapp and httpserver"""
590 """initialize tornado webapp and httpserver"""
586 self.web_app = NotebookWebApplication(
591 self.web_app = NotebookWebApplication(
587 self, self.kernel_manager, self.notebook_manager,
592 self, self.kernel_manager, self.notebook_manager,
588 self.cluster_manager, self.session_manager,
593 self.cluster_manager, self.session_manager,
589 self.log, self.base_project_url, self.webapp_settings
594 self.log, self.base_url, self.webapp_settings
590 )
595 )
591 if self.certfile:
596 if self.certfile:
592 ssl_options = dict(certfile=self.certfile)
597 ssl_options = dict(certfile=self.certfile)
593 if self.keyfile:
598 if self.keyfile:
594 ssl_options['keyfile'] = self.keyfile
599 ssl_options['keyfile'] = self.keyfile
595 else:
600 else:
596 ssl_options = None
601 ssl_options = None
597 self.web_app.password = self.password
602 self.web_app.password = self.password
598 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
603 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
599 xheaders=self.trust_xheaders)
604 xheaders=self.trust_xheaders)
600 if not self.ip:
605 if not self.ip:
601 warning = "WARNING: The notebook server is listening on all IP addresses"
606 warning = "WARNING: The notebook server is listening on all IP addresses"
602 if ssl_options is None:
607 if ssl_options is None:
603 self.log.critical(warning + " and not using encryption. This "
608 self.log.critical(warning + " and not using encryption. This "
604 "is not recommended.")
609 "is not recommended.")
605 if not self.password:
610 if not self.password:
606 self.log.critical(warning + " and not using authentication. "
611 self.log.critical(warning + " and not using authentication. "
607 "This is highly insecure and not recommended.")
612 "This is highly insecure and not recommended.")
608 success = None
613 success = None
609 for port in random_ports(self.port, self.port_retries+1):
614 for port in random_ports(self.port, self.port_retries+1):
610 try:
615 try:
611 self.http_server.listen(port, self.ip)
616 self.http_server.listen(port, self.ip)
612 except socket.error as e:
617 except socket.error as e:
613 if e.errno == errno.EADDRINUSE:
618 if e.errno == errno.EADDRINUSE:
614 self.log.info('The port %i is already in use, trying another random port.' % port)
619 self.log.info('The port %i is already in use, trying another random port.' % port)
615 continue
620 continue
616 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
621 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
617 self.log.warn("Permission to listen on port %i denied" % port)
622 self.log.warn("Permission to listen on port %i denied" % port)
618 continue
623 continue
619 else:
624 else:
620 raise
625 raise
621 else:
626 else:
622 self.port = port
627 self.port = port
623 success = True
628 success = True
624 break
629 break
625 if not success:
630 if not success:
626 self.log.critical('ERROR: the notebook server could not be started because '
631 self.log.critical('ERROR: the notebook server could not be started because '
627 'no available port could be found.')
632 'no available port could be found.')
628 self.exit(1)
633 self.exit(1)
629
634
630 @property
635 @property
631 def display_url(self):
636 def display_url(self):
632 ip = self.ip if self.ip else '[all ip addresses on your system]'
637 ip = self.ip if self.ip else '[all ip addresses on your system]'
633 return self._url(ip)
638 return self._url(ip)
634
639
635 @property
640 @property
636 def connection_url(self):
641 def connection_url(self):
637 ip = self.ip if self.ip else localhost()
642 ip = self.ip if self.ip else localhost()
638 return self._url(ip)
643 return self._url(ip)
639
644
640 def _url(self, ip):
645 def _url(self, ip):
641 proto = 'https' if self.certfile else 'http'
646 proto = 'https' if self.certfile else 'http'
642 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
647 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
643
648
644 def init_signal(self):
649 def init_signal(self):
645 if not sys.platform.startswith('win'):
650 if not sys.platform.startswith('win'):
646 signal.signal(signal.SIGINT, self._handle_sigint)
651 signal.signal(signal.SIGINT, self._handle_sigint)
647 signal.signal(signal.SIGTERM, self._signal_stop)
652 signal.signal(signal.SIGTERM, self._signal_stop)
648 if hasattr(signal, 'SIGUSR1'):
653 if hasattr(signal, 'SIGUSR1'):
649 # Windows doesn't support SIGUSR1
654 # Windows doesn't support SIGUSR1
650 signal.signal(signal.SIGUSR1, self._signal_info)
655 signal.signal(signal.SIGUSR1, self._signal_info)
651 if hasattr(signal, 'SIGINFO'):
656 if hasattr(signal, 'SIGINFO'):
652 # only on BSD-based systems
657 # only on BSD-based systems
653 signal.signal(signal.SIGINFO, self._signal_info)
658 signal.signal(signal.SIGINFO, self._signal_info)
654
659
655 def _handle_sigint(self, sig, frame):
660 def _handle_sigint(self, sig, frame):
656 """SIGINT handler spawns confirmation dialog"""
661 """SIGINT handler spawns confirmation dialog"""
657 # register more forceful signal handler for ^C^C case
662 # register more forceful signal handler for ^C^C case
658 signal.signal(signal.SIGINT, self._signal_stop)
663 signal.signal(signal.SIGINT, self._signal_stop)
659 # request confirmation dialog in bg thread, to avoid
664 # request confirmation dialog in bg thread, to avoid
660 # blocking the App
665 # blocking the App
661 thread = threading.Thread(target=self._confirm_exit)
666 thread = threading.Thread(target=self._confirm_exit)
662 thread.daemon = True
667 thread.daemon = True
663 thread.start()
668 thread.start()
664
669
665 def _restore_sigint_handler(self):
670 def _restore_sigint_handler(self):
666 """callback for restoring original SIGINT handler"""
671 """callback for restoring original SIGINT handler"""
667 signal.signal(signal.SIGINT, self._handle_sigint)
672 signal.signal(signal.SIGINT, self._handle_sigint)
668
673
669 def _confirm_exit(self):
674 def _confirm_exit(self):
670 """confirm shutdown on ^C
675 """confirm shutdown on ^C
671
676
672 A second ^C, or answering 'y' within 5s will cause shutdown,
677 A second ^C, or answering 'y' within 5s will cause shutdown,
673 otherwise original SIGINT handler will be restored.
678 otherwise original SIGINT handler will be restored.
674
679
675 This doesn't work on Windows.
680 This doesn't work on Windows.
676 """
681 """
677 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
682 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
678 time.sleep(0.1)
683 time.sleep(0.1)
679 info = self.log.info
684 info = self.log.info
680 info('interrupted')
685 info('interrupted')
681 print(self.notebook_info())
686 print(self.notebook_info())
682 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
687 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
683 sys.stdout.flush()
688 sys.stdout.flush()
684 r,w,x = select.select([sys.stdin], [], [], 5)
689 r,w,x = select.select([sys.stdin], [], [], 5)
685 if r:
690 if r:
686 line = sys.stdin.readline()
691 line = sys.stdin.readline()
687 if line.lower().startswith('y'):
692 if line.lower().startswith('y'):
688 self.log.critical("Shutdown confirmed")
693 self.log.critical("Shutdown confirmed")
689 ioloop.IOLoop.instance().stop()
694 ioloop.IOLoop.instance().stop()
690 return
695 return
691 else:
696 else:
692 print("No answer for 5s:", end=' ')
697 print("No answer for 5s:", end=' ')
693 print("resuming operation...")
698 print("resuming operation...")
694 # no answer, or answer is no:
699 # no answer, or answer is no:
695 # set it back to original SIGINT handler
700 # set it back to original SIGINT handler
696 # use IOLoop.add_callback because signal.signal must be called
701 # use IOLoop.add_callback because signal.signal must be called
697 # from main thread
702 # from main thread
698 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
703 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
699
704
700 def _signal_stop(self, sig, frame):
705 def _signal_stop(self, sig, frame):
701 self.log.critical("received signal %s, stopping", sig)
706 self.log.critical("received signal %s, stopping", sig)
702 ioloop.IOLoop.instance().stop()
707 ioloop.IOLoop.instance().stop()
703
708
704 def _signal_info(self, sig, frame):
709 def _signal_info(self, sig, frame):
705 print(self.notebook_info())
710 print(self.notebook_info())
706
711
707 def init_components(self):
712 def init_components(self):
708 """Check the components submodule, and warn if it's unclean"""
713 """Check the components submodule, and warn if it's unclean"""
709 status = submodule.check_submodule_status()
714 status = submodule.check_submodule_status()
710 if status == 'missing':
715 if status == 'missing':
711 self.log.warn("components submodule missing, running `git submodule update`")
716 self.log.warn("components submodule missing, running `git submodule update`")
712 submodule.update_submodules(submodule.ipython_parent())
717 submodule.update_submodules(submodule.ipython_parent())
713 elif status == 'unclean':
718 elif status == 'unclean':
714 self.log.warn("components submodule unclean, you may see 404s on static/components")
719 self.log.warn("components submodule unclean, you may see 404s on static/components")
715 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
720 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
716
721
717 @catch_config_error
722 @catch_config_error
718 def initialize(self, argv=None):
723 def initialize(self, argv=None):
719 super(NotebookApp, self).initialize(argv)
724 super(NotebookApp, self).initialize(argv)
720 self.init_logging()
725 self.init_logging()
721 self.init_kernel_argv()
726 self.init_kernel_argv()
722 self.init_configurables()
727 self.init_configurables()
723 self.init_components()
728 self.init_components()
724 self.init_webapp()
729 self.init_webapp()
725 self.init_signal()
730 self.init_signal()
726
731
727 def cleanup_kernels(self):
732 def cleanup_kernels(self):
728 """Shutdown all kernels.
733 """Shutdown all kernels.
729
734
730 The kernels will shutdown themselves when this process no longer exists,
735 The kernels will shutdown themselves when this process no longer exists,
731 but explicit shutdown allows the KernelManagers to cleanup the connection files.
736 but explicit shutdown allows the KernelManagers to cleanup the connection files.
732 """
737 """
733 self.log.info('Shutting down kernels')
738 self.log.info('Shutting down kernels')
734 self.kernel_manager.shutdown_all()
739 self.kernel_manager.shutdown_all()
735
740
736 def notebook_info(self):
741 def notebook_info(self):
737 "Return the current working directory and the server url information"
742 "Return the current working directory and the server url information"
738 info = self.notebook_manager.info_string() + "\n"
743 info = self.notebook_manager.info_string() + "\n"
739 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
744 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
740 return info + "The IPython Notebook is running at: %s" % self.display_url
745 return info + "The IPython Notebook is running at: %s" % self.display_url
741
746
742 def server_info(self):
747 def server_info(self):
743 """Return a JSONable dict of information about this server."""
748 """Return a JSONable dict of information about this server."""
744 return {'url': self.connection_url,
749 return {'url': self.connection_url,
745 'hostname': self.ip if self.ip else 'localhost',
750 'hostname': self.ip if self.ip else 'localhost',
746 'port': self.port,
751 'port': self.port,
747 'secure': bool(self.certfile),
752 'secure': bool(self.certfile),
748 'base_project_url': self.base_project_url,
753 'base_url': self.base_url,
749 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
754 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
750 }
755 }
751
756
752 def write_server_info_file(self):
757 def write_server_info_file(self):
753 """Write the result of server_info() to the JSON file info_file."""
758 """Write the result of server_info() to the JSON file info_file."""
754 with open(self.info_file, 'w') as f:
759 with open(self.info_file, 'w') as f:
755 json.dump(self.server_info(), f, indent=2)
760 json.dump(self.server_info(), f, indent=2)
756
761
757 def remove_server_info_file(self):
762 def remove_server_info_file(self):
758 """Remove the nbserver-<pid>.json file created for this server.
763 """Remove the nbserver-<pid>.json file created for this server.
759
764
760 Ignores the error raised when the file has already been removed.
765 Ignores the error raised when the file has already been removed.
761 """
766 """
762 try:
767 try:
763 os.unlink(self.info_file)
768 os.unlink(self.info_file)
764 except OSError as e:
769 except OSError as e:
765 if e.errno != errno.ENOENT:
770 if e.errno != errno.ENOENT:
766 raise
771 raise
767
772
768 def start(self):
773 def start(self):
769 """ Start the IPython Notebook server app, after initialization
774 """ Start the IPython Notebook server app, after initialization
770
775
771 This method takes no arguments so all configuration and initialization
776 This method takes no arguments so all configuration and initialization
772 must be done prior to calling this method."""
777 must be done prior to calling this method."""
773 if self.subapp is not None:
778 if self.subapp is not None:
774 return self.subapp.start()
779 return self.subapp.start()
775
780
776 info = self.log.info
781 info = self.log.info
777 for line in self.notebook_info().split("\n"):
782 for line in self.notebook_info().split("\n"):
778 info(line)
783 info(line)
779 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
784 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
780
785
781 self.write_server_info_file()
786 self.write_server_info_file()
782
787
783 if self.open_browser or self.file_to_run:
788 if self.open_browser or self.file_to_run:
784 try:
789 try:
785 browser = webbrowser.get(self.browser or None)
790 browser = webbrowser.get(self.browser or None)
786 except webbrowser.Error as e:
791 except webbrowser.Error as e:
787 self.log.warn('No web browser found: %s.' % e)
792 self.log.warn('No web browser found: %s.' % e)
788 browser = None
793 browser = None
789
794
790 f = self.file_to_run
795 f = self.file_to_run
791 if f:
796 if f:
792 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
797 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
793 if f.startswith(nbdir):
798 if f.startswith(nbdir):
794 f = f[len(nbdir):]
799 f = f[len(nbdir):]
795 else:
800 else:
796 self.log.warn(
801 self.log.warn(
797 "Probably won't be able to open notebook %s "
802 "Probably won't be able to open notebook %s "
798 "because it is not in notebook_dir %s",
803 "because it is not in notebook_dir %s",
799 f, nbdir,
804 f, nbdir,
800 )
805 )
801
806
802 if os.path.isfile(self.file_to_run):
807 if os.path.isfile(self.file_to_run):
803 url = url_path_join('notebooks', f)
808 url = url_path_join('notebooks', f)
804 else:
809 else:
805 url = url_path_join('tree', f)
810 url = url_path_join('tree', f)
806 if browser:
811 if browser:
807 b = lambda : browser.open("%s%s" % (self.connection_url, url),
812 b = lambda : browser.open("%s%s" % (self.connection_url, url),
808 new=2)
813 new=2)
809 threading.Thread(target=b).start()
814 threading.Thread(target=b).start()
810 try:
815 try:
811 ioloop.IOLoop.instance().start()
816 ioloop.IOLoop.instance().start()
812 except KeyboardInterrupt:
817 except KeyboardInterrupt:
813 info("Interrupted...")
818 info("Interrupted...")
814 finally:
819 finally:
815 self.cleanup_kernels()
820 self.cleanup_kernels()
816 self.remove_server_info_file()
821 self.remove_server_info_file()
817
822
818
823
819 def list_running_servers(profile='default'):
824 def list_running_servers(profile='default'):
820 """Iterate over the server info files of running notebook servers.
825 """Iterate over the server info files of running notebook servers.
821
826
822 Given a profile name, find nbserver-* files in the security directory of
827 Given a profile name, find nbserver-* files in the security directory of
823 that profile, and yield dicts of their information, each one pertaining to
828 that profile, and yield dicts of their information, each one pertaining to
824 a currently running notebook server instance.
829 a currently running notebook server instance.
825 """
830 """
826 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
831 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
827 for file in os.listdir(pd.security_dir):
832 for file in os.listdir(pd.security_dir):
828 if file.startswith('nbserver-'):
833 if file.startswith('nbserver-'):
829 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
834 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
830 yield json.load(f)
835 yield json.load(f)
831
836
832 #-----------------------------------------------------------------------------
837 #-----------------------------------------------------------------------------
833 # Main entry point
838 # Main entry point
834 #-----------------------------------------------------------------------------
839 #-----------------------------------------------------------------------------
835
840
836 launch_new_instance = NotebookApp.launch_instance
841 launch_new_instance = NotebookApp.launch_instance
837
842
@@ -1,290 +1,290
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the notebooks web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import json
19 import json
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.html.utils import url_path_join, url_escape
23 from IPython.html.utils import url_path_join, url_escape
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25
25
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
27 notebook_path_regex, path_regex,
27 notebook_path_regex, path_regex,
28 notebook_name_regex)
28 notebook_name_regex)
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Notebook web service handlers
31 # Notebook web service handlers
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class NotebookHandler(IPythonHandler):
35 class NotebookHandler(IPythonHandler):
36
36
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
38
38
39 def notebook_location(self, name, path=''):
39 def notebook_location(self, name, path=''):
40 """Return the full URL location of a notebook based.
40 """Return the full URL location of a notebook based.
41
41
42 Parameters
42 Parameters
43 ----------
43 ----------
44 name : unicode
44 name : unicode
45 The base name of the notebook, such as "foo.ipynb".
45 The base name of the notebook, such as "foo.ipynb".
46 path : unicode
46 path : unicode
47 The URL path of the notebook.
47 The URL path of the notebook.
48 """
48 """
49 return url_escape(url_path_join(
49 return url_escape(url_path_join(
50 self.base_project_url, 'api', 'notebooks', path, name
50 self.base_url, 'api', 'notebooks', path, name
51 ))
51 ))
52
52
53 def _finish_model(self, model, location=True):
53 def _finish_model(self, model, location=True):
54 """Finish a JSON request with a model, setting relevant headers, etc."""
54 """Finish a JSON request with a model, setting relevant headers, etc."""
55 if location:
55 if location:
56 location = self.notebook_location(model['name'], model['path'])
56 location = self.notebook_location(model['name'], model['path'])
57 self.set_header('Location', location)
57 self.set_header('Location', location)
58 self.set_header('Last-Modified', model['last_modified'])
58 self.set_header('Last-Modified', model['last_modified'])
59 self.finish(json.dumps(model, default=date_default))
59 self.finish(json.dumps(model, default=date_default))
60
60
61 @web.authenticated
61 @web.authenticated
62 @json_errors
62 @json_errors
63 def get(self, path='', name=None):
63 def get(self, path='', name=None):
64 """Return a Notebook or list of notebooks.
64 """Return a Notebook or list of notebooks.
65
65
66 * GET with path and no notebook name lists notebooks in a directory
66 * GET with path and no notebook name lists notebooks in a directory
67 * GET with path and notebook name returns notebook JSON
67 * GET with path and notebook name returns notebook JSON
68 """
68 """
69 nbm = self.notebook_manager
69 nbm = self.notebook_manager
70 # Check to see if a notebook name was given
70 # Check to see if a notebook name was given
71 if name is None:
71 if name is None:
72 # TODO: Remove this after we create the contents web service and directories are
72 # TODO: Remove this after we create the contents web service and directories are
73 # no longer listed by the notebook web service. This should only handle notebooks
73 # no longer listed by the notebook web service. This should only handle notebooks
74 # and not directories.
74 # and not directories.
75 dirs = nbm.list_dirs(path)
75 dirs = nbm.list_dirs(path)
76 notebooks = []
76 notebooks = []
77 index = []
77 index = []
78 for nb in nbm.list_notebooks(path):
78 for nb in nbm.list_notebooks(path):
79 if nb['name'].lower() == 'index.ipynb':
79 if nb['name'].lower() == 'index.ipynb':
80 index.append(nb)
80 index.append(nb)
81 else:
81 else:
82 notebooks.append(nb)
82 notebooks.append(nb)
83 notebooks = index + dirs + notebooks
83 notebooks = index + dirs + notebooks
84 self.finish(json.dumps(notebooks, default=date_default))
84 self.finish(json.dumps(notebooks, default=date_default))
85 return
85 return
86 # get and return notebook representation
86 # get and return notebook representation
87 model = nbm.get_notebook_model(name, path)
87 model = nbm.get_notebook_model(name, path)
88 self._finish_model(model, location=False)
88 self._finish_model(model, location=False)
89
89
90 @web.authenticated
90 @web.authenticated
91 @json_errors
91 @json_errors
92 def patch(self, path='', name=None):
92 def patch(self, path='', name=None):
93 """PATCH renames a notebook without re-uploading content."""
93 """PATCH renames a notebook without re-uploading content."""
94 nbm = self.notebook_manager
94 nbm = self.notebook_manager
95 if name is None:
95 if name is None:
96 raise web.HTTPError(400, u'Notebook name missing')
96 raise web.HTTPError(400, u'Notebook name missing')
97 model = self.get_json_body()
97 model = self.get_json_body()
98 if model is None:
98 if model is None:
99 raise web.HTTPError(400, u'JSON body missing')
99 raise web.HTTPError(400, u'JSON body missing')
100 model = nbm.update_notebook_model(model, name, path)
100 model = nbm.update_notebook_model(model, name, path)
101 self._finish_model(model)
101 self._finish_model(model)
102
102
103 def _copy_notebook(self, copy_from, path, copy_to=None):
103 def _copy_notebook(self, copy_from, path, copy_to=None):
104 """Copy a notebook in path, optionally specifying the new name.
104 """Copy a notebook in path, optionally specifying the new name.
105
105
106 Only support copying within the same directory.
106 Only support copying within the same directory.
107 """
107 """
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
109 path, copy_from,
109 path, copy_from,
110 path, copy_to or '',
110 path, copy_to or '',
111 )
111 )
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
113 self.set_status(201)
113 self.set_status(201)
114 self._finish_model(model)
114 self._finish_model(model)
115
115
116 def _upload_notebook(self, model, path, name=None):
116 def _upload_notebook(self, model, path, name=None):
117 """Upload a notebook
117 """Upload a notebook
118
118
119 If name specified, create it in path/name.
119 If name specified, create it in path/name.
120 """
120 """
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
122 if name:
122 if name:
123 model['name'] = name
123 model['name'] = name
124
124
125 model = self.notebook_manager.create_notebook_model(model, path)
125 model = self.notebook_manager.create_notebook_model(model, path)
126 self.set_status(201)
126 self.set_status(201)
127 self._finish_model(model)
127 self._finish_model(model)
128
128
129 def _create_empty_notebook(self, path, name=None):
129 def _create_empty_notebook(self, path, name=None):
130 """Create an empty notebook in path
130 """Create an empty notebook in path
131
131
132 If name specified, create it in path/name.
132 If name specified, create it in path/name.
133 """
133 """
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
135 model = {}
135 model = {}
136 if name:
136 if name:
137 model['name'] = name
137 model['name'] = name
138 model = self.notebook_manager.create_notebook_model(model, path=path)
138 model = self.notebook_manager.create_notebook_model(model, path=path)
139 self.set_status(201)
139 self.set_status(201)
140 self._finish_model(model)
140 self._finish_model(model)
141
141
142 def _save_notebook(self, model, path, name):
142 def _save_notebook(self, model, path, name):
143 """Save an existing notebook."""
143 """Save an existing notebook."""
144 self.log.info(u"Saving notebook at %s/%s", path, name)
144 self.log.info(u"Saving notebook at %s/%s", path, name)
145 model = self.notebook_manager.save_notebook_model(model, name, path)
145 model = self.notebook_manager.save_notebook_model(model, name, path)
146 if model['path'] != path.strip('/') or model['name'] != name:
146 if model['path'] != path.strip('/') or model['name'] != name:
147 # a rename happened, set Location header
147 # a rename happened, set Location header
148 location = True
148 location = True
149 else:
149 else:
150 location = False
150 location = False
151 self._finish_model(model, location)
151 self._finish_model(model, location)
152
152
153 @web.authenticated
153 @web.authenticated
154 @json_errors
154 @json_errors
155 def post(self, path='', name=None):
155 def post(self, path='', name=None):
156 """Create a new notebook in the specified path.
156 """Create a new notebook in the specified path.
157
157
158 POST creates new notebooks. The server always decides on the notebook name.
158 POST creates new notebooks. The server always decides on the notebook name.
159
159
160 POST /api/notebooks/path
160 POST /api/notebooks/path
161 New untitled notebook in path. If content specified, upload a
161 New untitled notebook in path. If content specified, upload a
162 notebook, otherwise start empty.
162 notebook, otherwise start empty.
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
164 New copy of OtherNotebook in path
164 New copy of OtherNotebook in path
165 """
165 """
166
166
167 if name is not None:
167 if name is not None:
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
169
169
170 model = self.get_json_body()
170 model = self.get_json_body()
171
171
172 if model is not None:
172 if model is not None:
173 copy_from = model.get('copy_from')
173 copy_from = model.get('copy_from')
174 if copy_from:
174 if copy_from:
175 if model.get('content'):
175 if model.get('content'):
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
177 self._copy_notebook(copy_from, path)
177 self._copy_notebook(copy_from, path)
178 else:
178 else:
179 self._upload_notebook(model, path)
179 self._upload_notebook(model, path)
180 else:
180 else:
181 self._create_empty_notebook(path)
181 self._create_empty_notebook(path)
182
182
183 @web.authenticated
183 @web.authenticated
184 @json_errors
184 @json_errors
185 def put(self, path='', name=None):
185 def put(self, path='', name=None):
186 """Saves the notebook in the location specified by name and path.
186 """Saves the notebook in the location specified by name and path.
187
187
188 PUT is very similar to POST, but the requester specifies the name,
188 PUT is very similar to POST, but the requester specifies the name,
189 whereas with POST, the server picks the name.
189 whereas with POST, the server picks the name.
190
190
191 PUT /api/notebooks/path/Name.ipynb
191 PUT /api/notebooks/path/Name.ipynb
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
193 in `content` key of JSON request body. If content is not specified,
193 in `content` key of JSON request body. If content is not specified,
194 create a new empty notebook.
194 create a new empty notebook.
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
196 Copy OtherNotebook to Name
196 Copy OtherNotebook to Name
197 """
197 """
198 if name is None:
198 if name is None:
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
200
200
201 model = self.get_json_body()
201 model = self.get_json_body()
202 if model:
202 if model:
203 copy_from = model.get('copy_from')
203 copy_from = model.get('copy_from')
204 if copy_from:
204 if copy_from:
205 if model.get('content'):
205 if model.get('content'):
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
207 self._copy_notebook(copy_from, path, name)
207 self._copy_notebook(copy_from, path, name)
208 elif self.notebook_manager.notebook_exists(name, path):
208 elif self.notebook_manager.notebook_exists(name, path):
209 self._save_notebook(model, path, name)
209 self._save_notebook(model, path, name)
210 else:
210 else:
211 self._upload_notebook(model, path, name)
211 self._upload_notebook(model, path, name)
212 else:
212 else:
213 self._create_empty_notebook(path, name)
213 self._create_empty_notebook(path, name)
214
214
215 @web.authenticated
215 @web.authenticated
216 @json_errors
216 @json_errors
217 def delete(self, path='', name=None):
217 def delete(self, path='', name=None):
218 """delete the notebook in the given notebook path"""
218 """delete the notebook in the given notebook path"""
219 nbm = self.notebook_manager
219 nbm = self.notebook_manager
220 nbm.delete_notebook_model(name, path)
220 nbm.delete_notebook_model(name, path)
221 self.set_status(204)
221 self.set_status(204)
222 self.finish()
222 self.finish()
223
223
224
224
225 class NotebookCheckpointsHandler(IPythonHandler):
225 class NotebookCheckpointsHandler(IPythonHandler):
226
226
227 SUPPORTED_METHODS = ('GET', 'POST')
227 SUPPORTED_METHODS = ('GET', 'POST')
228
228
229 @web.authenticated
229 @web.authenticated
230 @json_errors
230 @json_errors
231 def get(self, path='', name=None):
231 def get(self, path='', name=None):
232 """get lists checkpoints for a notebook"""
232 """get lists checkpoints for a notebook"""
233 nbm = self.notebook_manager
233 nbm = self.notebook_manager
234 checkpoints = nbm.list_checkpoints(name, path)
234 checkpoints = nbm.list_checkpoints(name, path)
235 data = json.dumps(checkpoints, default=date_default)
235 data = json.dumps(checkpoints, default=date_default)
236 self.finish(data)
236 self.finish(data)
237
237
238 @web.authenticated
238 @web.authenticated
239 @json_errors
239 @json_errors
240 def post(self, path='', name=None):
240 def post(self, path='', name=None):
241 """post creates a new checkpoint"""
241 """post creates a new checkpoint"""
242 nbm = self.notebook_manager
242 nbm = self.notebook_manager
243 checkpoint = nbm.create_checkpoint(name, path)
243 checkpoint = nbm.create_checkpoint(name, path)
244 data = json.dumps(checkpoint, default=date_default)
244 data = json.dumps(checkpoint, default=date_default)
245 location = url_path_join(self.base_project_url, 'api/notebooks',
245 location = url_path_join(self.base_url, 'api/notebooks',
246 path, name, 'checkpoints', checkpoint['id'])
246 path, name, 'checkpoints', checkpoint['id'])
247 self.set_header('Location', url_escape(location))
247 self.set_header('Location', url_escape(location))
248 self.set_status(201)
248 self.set_status(201)
249 self.finish(data)
249 self.finish(data)
250
250
251
251
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
253
253
254 SUPPORTED_METHODS = ('POST', 'DELETE')
254 SUPPORTED_METHODS = ('POST', 'DELETE')
255
255
256 @web.authenticated
256 @web.authenticated
257 @json_errors
257 @json_errors
258 def post(self, path, name, checkpoint_id):
258 def post(self, path, name, checkpoint_id):
259 """post restores a notebook from a checkpoint"""
259 """post restores a notebook from a checkpoint"""
260 nbm = self.notebook_manager
260 nbm = self.notebook_manager
261 nbm.restore_checkpoint(checkpoint_id, name, path)
261 nbm.restore_checkpoint(checkpoint_id, name, path)
262 self.set_status(204)
262 self.set_status(204)
263 self.finish()
263 self.finish()
264
264
265 @web.authenticated
265 @web.authenticated
266 @json_errors
266 @json_errors
267 def delete(self, path, name, checkpoint_id):
267 def delete(self, path, name, checkpoint_id):
268 """delete clears a checkpoint for a given notebook"""
268 """delete clears a checkpoint for a given notebook"""
269 nbm = self.notebook_manager
269 nbm = self.notebook_manager
270 nbm.delete_checkpoint(checkpoint_id, name, path)
270 nbm.delete_checkpoint(checkpoint_id, name, path)
271 self.set_status(204)
271 self.set_status(204)
272 self.finish()
272 self.finish()
273
273
274 #-----------------------------------------------------------------------------
274 #-----------------------------------------------------------------------------
275 # URL to handler mappings
275 # URL to handler mappings
276 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
277
277
278
278
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
280
280
281 default_handlers = [
281 default_handlers = [
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
284 ModifyNotebookCheckpointsHandler),
284 ModifyNotebookCheckpointsHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
287 ]
287 ]
288
288
289
289
290
290
@@ -1,52 +1,52
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Login button
9 // Login button
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var LoginWidget = function (selector, options) {
15 var LoginWidget = function (selector, options) {
16 options = options || {};
16 options = options || {};
17 this.base_project_url = options.base_project_url || IPython.utils.get_data("baseProjectUrl");
17 this.base_url = options.base_url || IPython.utils.get_data("baseUrl");
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26 LoginWidget.prototype.style = function () {
26 LoginWidget.prototype.style = function () {
27 this.element.find("button").addClass("btn btn-small");
27 this.element.find("button").addClass("btn btn-small");
28 };
28 };
29
29
30
30
31 LoginWidget.prototype.bind_events = function () {
31 LoginWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find("button#logout").click(function () {
33 this.element.find("button#logout").click(function () {
34 window.location = IPythin.utils.url_join_encode(
34 window.location = IPythin.utils.url_join_encode(
35 that.base_project_url,
35 that.base_url,
36 "logout"
36 "logout"
37 );
37 );
38 });
38 });
39 this.element.find("button#login").click(function () {
39 this.element.find("button#login").click(function () {
40 window.location = IPythin.utils.url_join_encode(
40 window.location = IPythin.utils.url_join_encode(
41 that.base_project_url,
41 that.base_url,
42 "login"
42 "login"
43 );
43 );
44 });
44 });
45 };
45 };
46
46
47 // Set module variables
47 // Set module variables
48 IPython.LoginWidget = LoginWidget;
48 IPython.LoginWidget = LoginWidget;
49
49
50 return IPython;
50 return IPython;
51
51
52 }(IPython));
52 }(IPython));
@@ -1,122 +1,122
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12 // for the time beeing, we have to pass marked as a parameter here,
12 // for the time beeing, we have to pass marked as a parameter here,
13 // as injecting require.js make marked not to put itself in the globals,
13 // as injecting require.js make marked not to put itself in the globals,
14 // which make both this file fail at setting marked configuration, and textcell.js
14 // which make both this file fail at setting marked configuration, and textcell.js
15 // which search marked into global.
15 // which search marked into global.
16 require(['components/marked/lib/marked',
16 require(['components/marked/lib/marked',
17 'notebook/js/widgets/init'],
17 'notebook/js/widgets/init'],
18
18
19 function (marked) {
19 function (marked) {
20 "use strict";
20 "use strict";
21
21
22 window.marked = marked;
22 window.marked = marked;
23
23
24 // monkey patch CM to be able to syntax highlight cell magics
24 // monkey patch CM to be able to syntax highlight cell magics
25 // bug reported upstream,
25 // bug reported upstream,
26 // see https://github.com/marijnh/CodeMirror2/issues/670
26 // see https://github.com/marijnh/CodeMirror2/issues/670
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
28 console.log('patching CM for undefined indent');
28 console.log('patching CM for undefined indent');
29 CodeMirror.modes.null = function() {
29 CodeMirror.modes.null = function() {
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
31 };
31 };
32 }
32 }
33
33
34 CodeMirror.patchedGetMode = function(config, mode){
34 CodeMirror.patchedGetMode = function(config, mode){
35 var cmmode = CodeMirror.getMode(config, mode);
35 var cmmode = CodeMirror.getMode(config, mode);
36 if(cmmode.indent === null) {
36 if(cmmode.indent === null) {
37 console.log('patch mode "' , mode, '" on the fly');
37 console.log('patch mode "' , mode, '" on the fly');
38 cmmode.indent = function(){return 0;};
38 cmmode.indent = function(){return 0;};
39 }
39 }
40 return cmmode;
40 return cmmode;
41 };
41 };
42 // end monkey patching CodeMirror
42 // end monkey patching CodeMirror
43
43
44 IPython.mathjaxutils.init();
44 IPython.mathjaxutils.init();
45
45
46 $('#ipython-main-app').addClass('border-box-sizing');
46 $('#ipython-main-app').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
48
48
49 var opts = {
49 var opts = {
50 base_project_url : IPython.utils.get_data("baseProjectUrl"),
50 base_url : IPython.utils.get_data("baseUrl"),
51 base_kernel_url : IPython.utils.get_data("baseKernelUrl"),
51 base_kernel_url : IPython.utils.get_data("baseKernelUrl"),
52 notebook_path : IPython.utils.get_data("notebookPath"),
52 notebook_path : IPython.utils.get_data("notebookPath"),
53 notebook_name : IPython.utils.get_data('notebookName')
53 notebook_name : IPython.utils.get_data('notebookName')
54 };
54 };
55
55
56 IPython.page = new IPython.Page();
56 IPython.page = new IPython.Page();
57 IPython.layout_manager = new IPython.LayoutManager();
57 IPython.layout_manager = new IPython.LayoutManager();
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
59 IPython.quick_help = new IPython.QuickHelp();
59 IPython.quick_help = new IPython.QuickHelp();
60 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
60 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
61 IPython.notebook = new IPython.Notebook('div#notebook', opts);
61 IPython.notebook = new IPython.Notebook('div#notebook', opts);
62 IPython.keyboard_manager = new IPython.KeyboardManager();
62 IPython.keyboard_manager = new IPython.KeyboardManager();
63 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
63 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
64 IPython.menubar = new IPython.MenuBar('#menubar', opts);
64 IPython.menubar = new IPython.MenuBar('#menubar', opts);
65 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
65 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
66 IPython.tooltip = new IPython.Tooltip();
66 IPython.tooltip = new IPython.Tooltip();
67 IPython.notification_area = new IPython.NotificationArea('#notification_area');
67 IPython.notification_area = new IPython.NotificationArea('#notification_area');
68 IPython.notification_area.init_notification_widgets();
68 IPython.notification_area.init_notification_widgets();
69
69
70 IPython.layout_manager.do_resize();
70 IPython.layout_manager.do_resize();
71
71
72 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
72 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
73 '<span id="test2" style="font-weight: bold;">x</span>'+
73 '<span id="test2" style="font-weight: bold;">x</span>'+
74 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
74 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
75 var nh = $('#test1').innerHeight();
75 var nh = $('#test1').innerHeight();
76 var bh = $('#test2').innerHeight();
76 var bh = $('#test2').innerHeight();
77 var ih = $('#test3').innerHeight();
77 var ih = $('#test3').innerHeight();
78 if(nh != bh || nh != ih) {
78 if(nh != bh || nh != ih) {
79 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
79 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
80 }
80 }
81 $('#fonttest').remove();
81 $('#fonttest').remove();
82
82
83 IPython.page.show();
83 IPython.page.show();
84
84
85 IPython.layout_manager.do_resize();
85 IPython.layout_manager.do_resize();
86 var first_load = function () {
86 var first_load = function () {
87 IPython.layout_manager.do_resize();
87 IPython.layout_manager.do_resize();
88 var hash = document.location.hash;
88 var hash = document.location.hash;
89 if (hash) {
89 if (hash) {
90 document.location.hash = '';
90 document.location.hash = '';
91 document.location.hash = hash;
91 document.location.hash = hash;
92 }
92 }
93 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
93 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
94 // only do this once
94 // only do this once
95 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
95 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
96 };
96 };
97
97
98 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
98 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
99 $([IPython.events]).trigger('app_initialized.NotebookApp');
99 $([IPython.events]).trigger('app_initialized.NotebookApp');
100 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
100 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
101
101
102 if (marked) {
102 if (marked) {
103 marked.setOptions({
103 marked.setOptions({
104 gfm : true,
104 gfm : true,
105 tables: true,
105 tables: true,
106 langPrefix: "language-",
106 langPrefix: "language-",
107 highlight: function(code, lang) {
107 highlight: function(code, lang) {
108 if (!lang) {
108 if (!lang) {
109 // no language, no highlight
109 // no language, no highlight
110 return code;
110 return code;
111 }
111 }
112 var highlighted;
112 var highlighted;
113 try {
113 try {
114 highlighted = hljs.highlight(lang, code, false);
114 highlighted = hljs.highlight(lang, code, false);
115 } catch(err) {
115 } catch(err) {
116 highlighted = hljs.highlightAuto(code);
116 highlighted = hljs.highlightAuto(code);
117 }
117 }
118 return highlighted.value;
118 return highlighted.value;
119 }
119 }
120 });
120 });
121 }
121 }
122 });
122 });
@@ -1,317 +1,317
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 var utils = IPython.utils;
22 var utils = IPython.utils;
23
23
24 /**
24 /**
25 * A MenuBar Class to generate the menubar of IPython notebook
25 * A MenuBar Class to generate the menubar of IPython notebook
26 * @Class MenuBar
26 * @Class MenuBar
27 *
27 *
28 * @constructor
28 * @constructor
29 *
29 *
30 *
30 *
31 * @param selector {string} selector for the menubar element in DOM
31 * @param selector {string} selector for the menubar element in DOM
32 * @param {object} [options]
32 * @param {object} [options]
33 * @param [options.base_project_url] {String} String to use for the
33 * @param [options.base_url] {String} String to use for the
34 * base project url. Default is to inspect
34 * base project url. Default is to inspect
35 * $('body').data('baseProjectUrl');
35 * $('body').data('baseUrl');
36 * does not support change for now is set through this option
36 * does not support change for now is set through this option
37 */
37 */
38 var MenuBar = function (selector, options) {
38 var MenuBar = function (selector, options) {
39 options = options || {};
39 options = options || {};
40 this.base_project_url = options.base_project_url || IPython.utils.get_data("baseProjectUrl");
40 this.base_url = options.base_url || IPython.utils.get_data("baseUrl");
41 this.selector = selector;
41 this.selector = selector;
42 if (this.selector !== undefined) {
42 if (this.selector !== undefined) {
43 this.element = $(selector);
43 this.element = $(selector);
44 this.style();
44 this.style();
45 this.bind_events();
45 this.bind_events();
46 }
46 }
47 };
47 };
48
48
49 MenuBar.prototype.style = function () {
49 MenuBar.prototype.style = function () {
50 this.element.addClass('border-box-sizing');
50 this.element.addClass('border-box-sizing');
51 this.element.find("li").click(function (event, ui) {
51 this.element.find("li").click(function (event, ui) {
52 // The selected cell loses focus when the menu is entered, so we
52 // The selected cell loses focus when the menu is entered, so we
53 // re-select it upon selection.
53 // re-select it upon selection.
54 var i = IPython.notebook.get_selected_index();
54 var i = IPython.notebook.get_selected_index();
55 IPython.notebook.select(i);
55 IPython.notebook.select(i);
56 }
56 }
57 );
57 );
58 };
58 };
59
59
60 MenuBar.prototype._nbconvert = function (format, download) {
60 MenuBar.prototype._nbconvert = function (format, download) {
61 download = download || false;
61 download = download || false;
62 var notebook_name = IPython.notebook.get_notebook_name();
62 var notebook_name = IPython.notebook.get_notebook_name();
63 if (IPython.notebook.dirty) {
63 if (IPython.notebook.dirty) {
64 IPython.notebook.save_notebook({async : false});
64 IPython.notebook.save_notebook({async : false});
65 }
65 }
66 var url = utils.url_join_encode(
66 var url = utils.url_join_encode(
67 this.base_project_url,
67 this.base_url,
68 'nbconvert',
68 'nbconvert',
69 format,
69 format,
70 this.notebook_path,
70 this.notebook_path,
71 notebook_name + '.ipynb'
71 notebook_name + '.ipynb'
72 ) + "?download=" + download.toString();
72 ) + "?download=" + download.toString();
73
73
74 window.open(url);
74 window.open(url);
75 };
75 };
76
76
77 MenuBar.prototype.bind_events = function () {
77 MenuBar.prototype.bind_events = function () {
78 // File
78 // File
79 var that = this;
79 var that = this;
80 this.element.find('#new_notebook').click(function () {
80 this.element.find('#new_notebook').click(function () {
81 IPython.notebook.new_notebook();
81 IPython.notebook.new_notebook();
82 });
82 });
83 this.element.find('#open_notebook').click(function () {
83 this.element.find('#open_notebook').click(function () {
84 window.open(utils.url_join_encode(
84 window.open(utils.url_join_encode(
85 IPython.notebook.base_url,
85 IPython.notebook.base_url,
86 'tree',
86 'tree',
87 IPython.notebook.notebook_path
87 IPython.notebook.notebook_path
88 ));
88 ));
89 });
89 });
90 this.element.find('#copy_notebook').click(function () {
90 this.element.find('#copy_notebook').click(function () {
91 IPython.notebook.copy_notebook();
91 IPython.notebook.copy_notebook();
92 return false;
92 return false;
93 });
93 });
94 this.element.find('#download_ipynb').click(function () {
94 this.element.find('#download_ipynb').click(function () {
95 var base_url = IPython.notebook.base_url;
95 var base_url = IPython.notebook.base_url;
96 var notebook_path = IPython.notebook.notebook_path;
96 var notebook_path = IPython.notebook.notebook_path;
97 var notebook_name = IPython.notebook.notebook_name;
97 var notebook_name = IPython.notebook.notebook_name;
98 if (IPython.notebook.dirty) {
98 if (IPython.notebook.dirty) {
99 IPython.notebook.save_notebook({async : false});
99 IPython.notebook.save_notebook({async : false});
100 }
100 }
101
101
102 var url = utils.url_join_encode(
102 var url = utils.url_join_encode(
103 base_url,
103 base_url,
104 'files',
104 'files',
105 notebook_path,
105 notebook_path,
106 notebook_name + '.ipynb'
106 notebook_name + '.ipynb'
107 );
107 );
108 window.location.assign(url);
108 window.location.assign(url);
109 });
109 });
110
110
111 this.element.find('#print_preview').click(function () {
111 this.element.find('#print_preview').click(function () {
112 that._nbconvert('html', false);
112 that._nbconvert('html', false);
113 });
113 });
114
114
115 this.element.find('#download_py').click(function () {
115 this.element.find('#download_py').click(function () {
116 that._nbconvert('python', true);
116 that._nbconvert('python', true);
117 });
117 });
118
118
119 this.element.find('#download_html').click(function () {
119 this.element.find('#download_html').click(function () {
120 that._nbconvert('html', true);
120 that._nbconvert('html', true);
121 });
121 });
122
122
123 this.element.find('#download_rst').click(function () {
123 this.element.find('#download_rst').click(function () {
124 that._nbconvert('rst', true);
124 that._nbconvert('rst', true);
125 });
125 });
126
126
127 this.element.find('#rename_notebook').click(function () {
127 this.element.find('#rename_notebook').click(function () {
128 IPython.save_widget.rename_notebook();
128 IPython.save_widget.rename_notebook();
129 });
129 });
130 this.element.find('#save_checkpoint').click(function () {
130 this.element.find('#save_checkpoint').click(function () {
131 IPython.notebook.save_checkpoint();
131 IPython.notebook.save_checkpoint();
132 });
132 });
133 this.element.find('#restore_checkpoint').click(function () {
133 this.element.find('#restore_checkpoint').click(function () {
134 });
134 });
135 this.element.find('#kill_and_exit').click(function () {
135 this.element.find('#kill_and_exit').click(function () {
136 IPython.notebook.session.delete();
136 IPython.notebook.session.delete();
137 setTimeout(function(){
137 setTimeout(function(){
138 // allow closing of new tabs in Chromium, impossible in FF
138 // allow closing of new tabs in Chromium, impossible in FF
139 window.open('', '_self', '');
139 window.open('', '_self', '');
140 window.close();
140 window.close();
141 }, 500);
141 }, 500);
142 });
142 });
143 // Edit
143 // Edit
144 this.element.find('#cut_cell').click(function () {
144 this.element.find('#cut_cell').click(function () {
145 IPython.notebook.cut_cell();
145 IPython.notebook.cut_cell();
146 });
146 });
147 this.element.find('#copy_cell').click(function () {
147 this.element.find('#copy_cell').click(function () {
148 IPython.notebook.copy_cell();
148 IPython.notebook.copy_cell();
149 });
149 });
150 this.element.find('#delete_cell').click(function () {
150 this.element.find('#delete_cell').click(function () {
151 IPython.notebook.delete_cell();
151 IPython.notebook.delete_cell();
152 });
152 });
153 this.element.find('#undelete_cell').click(function () {
153 this.element.find('#undelete_cell').click(function () {
154 IPython.notebook.undelete_cell();
154 IPython.notebook.undelete_cell();
155 });
155 });
156 this.element.find('#split_cell').click(function () {
156 this.element.find('#split_cell').click(function () {
157 IPython.notebook.split_cell();
157 IPython.notebook.split_cell();
158 });
158 });
159 this.element.find('#merge_cell_above').click(function () {
159 this.element.find('#merge_cell_above').click(function () {
160 IPython.notebook.merge_cell_above();
160 IPython.notebook.merge_cell_above();
161 });
161 });
162 this.element.find('#merge_cell_below').click(function () {
162 this.element.find('#merge_cell_below').click(function () {
163 IPython.notebook.merge_cell_below();
163 IPython.notebook.merge_cell_below();
164 });
164 });
165 this.element.find('#move_cell_up').click(function () {
165 this.element.find('#move_cell_up').click(function () {
166 IPython.notebook.move_cell_up();
166 IPython.notebook.move_cell_up();
167 });
167 });
168 this.element.find('#move_cell_down').click(function () {
168 this.element.find('#move_cell_down').click(function () {
169 IPython.notebook.move_cell_down();
169 IPython.notebook.move_cell_down();
170 });
170 });
171 this.element.find('#edit_nb_metadata').click(function () {
171 this.element.find('#edit_nb_metadata').click(function () {
172 IPython.notebook.edit_metadata();
172 IPython.notebook.edit_metadata();
173 });
173 });
174
174
175 // View
175 // View
176 this.element.find('#toggle_header').click(function () {
176 this.element.find('#toggle_header').click(function () {
177 $('div#header').toggle();
177 $('div#header').toggle();
178 IPython.layout_manager.do_resize();
178 IPython.layout_manager.do_resize();
179 });
179 });
180 this.element.find('#toggle_toolbar').click(function () {
180 this.element.find('#toggle_toolbar').click(function () {
181 $('div#maintoolbar').toggle();
181 $('div#maintoolbar').toggle();
182 IPython.layout_manager.do_resize();
182 IPython.layout_manager.do_resize();
183 });
183 });
184 // Insert
184 // Insert
185 this.element.find('#insert_cell_above').click(function () {
185 this.element.find('#insert_cell_above').click(function () {
186 IPython.notebook.insert_cell_above('code');
186 IPython.notebook.insert_cell_above('code');
187 IPython.notebook.select_prev();
187 IPython.notebook.select_prev();
188 });
188 });
189 this.element.find('#insert_cell_below').click(function () {
189 this.element.find('#insert_cell_below').click(function () {
190 IPython.notebook.insert_cell_below('code');
190 IPython.notebook.insert_cell_below('code');
191 IPython.notebook.select_next();
191 IPython.notebook.select_next();
192 });
192 });
193 // Cell
193 // Cell
194 this.element.find('#run_cell').click(function () {
194 this.element.find('#run_cell').click(function () {
195 IPython.notebook.execute_cell();
195 IPython.notebook.execute_cell();
196 });
196 });
197 this.element.find('#run_cell_select_below').click(function () {
197 this.element.find('#run_cell_select_below').click(function () {
198 IPython.notebook.execute_cell_and_select_below();
198 IPython.notebook.execute_cell_and_select_below();
199 });
199 });
200 this.element.find('#run_cell_insert_below').click(function () {
200 this.element.find('#run_cell_insert_below').click(function () {
201 IPython.notebook.execute_cell_and_insert_below();
201 IPython.notebook.execute_cell_and_insert_below();
202 });
202 });
203 this.element.find('#run_all_cells').click(function () {
203 this.element.find('#run_all_cells').click(function () {
204 IPython.notebook.execute_all_cells();
204 IPython.notebook.execute_all_cells();
205 });
205 });
206 this.element.find('#run_all_cells_above').click(function () {
206 this.element.find('#run_all_cells_above').click(function () {
207 IPython.notebook.execute_cells_above();
207 IPython.notebook.execute_cells_above();
208 });
208 });
209 this.element.find('#run_all_cells_below').click(function () {
209 this.element.find('#run_all_cells_below').click(function () {
210 IPython.notebook.execute_cells_below();
210 IPython.notebook.execute_cells_below();
211 });
211 });
212 this.element.find('#to_code').click(function () {
212 this.element.find('#to_code').click(function () {
213 IPython.notebook.to_code();
213 IPython.notebook.to_code();
214 });
214 });
215 this.element.find('#to_markdown').click(function () {
215 this.element.find('#to_markdown').click(function () {
216 IPython.notebook.to_markdown();
216 IPython.notebook.to_markdown();
217 });
217 });
218 this.element.find('#to_raw').click(function () {
218 this.element.find('#to_raw').click(function () {
219 IPython.notebook.to_raw();
219 IPython.notebook.to_raw();
220 });
220 });
221 this.element.find('#to_heading1').click(function () {
221 this.element.find('#to_heading1').click(function () {
222 IPython.notebook.to_heading(undefined, 1);
222 IPython.notebook.to_heading(undefined, 1);
223 });
223 });
224 this.element.find('#to_heading2').click(function () {
224 this.element.find('#to_heading2').click(function () {
225 IPython.notebook.to_heading(undefined, 2);
225 IPython.notebook.to_heading(undefined, 2);
226 });
226 });
227 this.element.find('#to_heading3').click(function () {
227 this.element.find('#to_heading3').click(function () {
228 IPython.notebook.to_heading(undefined, 3);
228 IPython.notebook.to_heading(undefined, 3);
229 });
229 });
230 this.element.find('#to_heading4').click(function () {
230 this.element.find('#to_heading4').click(function () {
231 IPython.notebook.to_heading(undefined, 4);
231 IPython.notebook.to_heading(undefined, 4);
232 });
232 });
233 this.element.find('#to_heading5').click(function () {
233 this.element.find('#to_heading5').click(function () {
234 IPython.notebook.to_heading(undefined, 5);
234 IPython.notebook.to_heading(undefined, 5);
235 });
235 });
236 this.element.find('#to_heading6').click(function () {
236 this.element.find('#to_heading6').click(function () {
237 IPython.notebook.to_heading(undefined, 6);
237 IPython.notebook.to_heading(undefined, 6);
238 });
238 });
239
239
240 this.element.find('#toggle_current_output').click(function () {
240 this.element.find('#toggle_current_output').click(function () {
241 IPython.notebook.toggle_output();
241 IPython.notebook.toggle_output();
242 });
242 });
243 this.element.find('#toggle_current_output_scroll').click(function () {
243 this.element.find('#toggle_current_output_scroll').click(function () {
244 IPython.notebook.toggle_output_scroll();
244 IPython.notebook.toggle_output_scroll();
245 });
245 });
246 this.element.find('#clear_current_output').click(function () {
246 this.element.find('#clear_current_output').click(function () {
247 IPython.notebook.clear_output();
247 IPython.notebook.clear_output();
248 });
248 });
249
249
250 this.element.find('#toggle_all_output').click(function () {
250 this.element.find('#toggle_all_output').click(function () {
251 IPython.notebook.toggle_all_output();
251 IPython.notebook.toggle_all_output();
252 });
252 });
253 this.element.find('#toggle_all_output_scroll').click(function () {
253 this.element.find('#toggle_all_output_scroll').click(function () {
254 IPython.notebook.toggle_all_output_scroll();
254 IPython.notebook.toggle_all_output_scroll();
255 });
255 });
256 this.element.find('#clear_all_output').click(function () {
256 this.element.find('#clear_all_output').click(function () {
257 IPython.notebook.clear_all_output();
257 IPython.notebook.clear_all_output();
258 });
258 });
259
259
260 // Kernel
260 // Kernel
261 this.element.find('#int_kernel').click(function () {
261 this.element.find('#int_kernel').click(function () {
262 IPython.notebook.session.interrupt_kernel();
262 IPython.notebook.session.interrupt_kernel();
263 });
263 });
264 this.element.find('#restart_kernel').click(function () {
264 this.element.find('#restart_kernel').click(function () {
265 IPython.notebook.restart_kernel();
265 IPython.notebook.restart_kernel();
266 });
266 });
267 // Help
267 // Help
268 this.element.find('#keyboard_shortcuts').click(function () {
268 this.element.find('#keyboard_shortcuts').click(function () {
269 IPython.quick_help.show_keyboard_shortcuts();
269 IPython.quick_help.show_keyboard_shortcuts();
270 });
270 });
271
271
272 this.update_restore_checkpoint(null);
272 this.update_restore_checkpoint(null);
273
273
274 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
274 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
275 that.update_restore_checkpoint(IPython.notebook.checkpoints);
275 that.update_restore_checkpoint(IPython.notebook.checkpoints);
276 });
276 });
277
277
278 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
278 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
279 that.update_restore_checkpoint(IPython.notebook.checkpoints);
279 that.update_restore_checkpoint(IPython.notebook.checkpoints);
280 });
280 });
281 };
281 };
282
282
283 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
283 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
284 var ul = this.element.find("#restore_checkpoint").find("ul");
284 var ul = this.element.find("#restore_checkpoint").find("ul");
285 ul.empty();
285 ul.empty();
286 if (!checkpoints || checkpoints.length === 0) {
286 if (!checkpoints || checkpoints.length === 0) {
287 ul.append(
287 ul.append(
288 $("<li/>")
288 $("<li/>")
289 .addClass("disabled")
289 .addClass("disabled")
290 .append(
290 .append(
291 $("<a/>")
291 $("<a/>")
292 .text("No checkpoints")
292 .text("No checkpoints")
293 )
293 )
294 );
294 );
295 return;
295 return;
296 }
296 }
297
297
298 checkpoints.map(function (checkpoint) {
298 checkpoints.map(function (checkpoint) {
299 var d = new Date(checkpoint.last_modified);
299 var d = new Date(checkpoint.last_modified);
300 ul.append(
300 ul.append(
301 $("<li/>").append(
301 $("<li/>").append(
302 $("<a/>")
302 $("<a/>")
303 .attr("href", "#")
303 .attr("href", "#")
304 .text(d.format("mmm dd HH:MM:ss"))
304 .text(d.format("mmm dd HH:MM:ss"))
305 .click(function () {
305 .click(function () {
306 IPython.notebook.restore_checkpoint_dialog(checkpoint);
306 IPython.notebook.restore_checkpoint_dialog(checkpoint);
307 })
307 })
308 )
308 )
309 );
309 );
310 });
310 });
311 };
311 };
312
312
313 IPython.MenuBar = MenuBar;
313 IPython.MenuBar = MenuBar;
314
314
315 return IPython;
315 return IPython;
316
316
317 }(IPython));
317 }(IPython));
@@ -1,2288 +1,2288
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 this.options = options = options || {};
26 this.options = options = options || {};
27 this.base_project_url = options.base_project_url;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Create an HTML and CSS representation of the notebook.
73 * Create an HTML and CSS representation of the notebook.
74 *
74 *
75 * @method create_elements
75 * @method create_elements
76 */
76 */
77 Notebook.prototype.create_elements = function () {
77 Notebook.prototype.create_elements = function () {
78 var that = this;
78 var that = this;
79 this.element.attr('tabindex','-1');
79 this.element.attr('tabindex','-1');
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
81 // We add this end_space div to the end of the notebook div to:
81 // We add this end_space div to the end of the notebook div to:
82 // i) provide a margin between the last cell and the end of the notebook
82 // i) provide a margin between the last cell and the end of the notebook
83 // ii) to prevent the div from scrolling up when the last cell is being
83 // ii) to prevent the div from scrolling up when the last cell is being
84 // edited, but is too low on the page, which browsers will do automatically.
84 // edited, but is too low on the page, which browsers will do automatically.
85 var end_space = $('<div/>').addClass('end_space');
85 var end_space = $('<div/>').addClass('end_space');
86 end_space.dblclick(function (e) {
86 end_space.dblclick(function (e) {
87 var ncells = that.ncells();
87 var ncells = that.ncells();
88 that.insert_cell_below('code',ncells-1);
88 that.insert_cell_below('code',ncells-1);
89 });
89 });
90 this.element.append(this.container);
90 this.element.append(this.container);
91 this.container.append(end_space);
91 this.container.append(end_space);
92 };
92 };
93
93
94 /**
94 /**
95 * Bind JavaScript events: key presses and custom IPython events.
95 * Bind JavaScript events: key presses and custom IPython events.
96 *
96 *
97 * @method bind_events
97 * @method bind_events
98 */
98 */
99 Notebook.prototype.bind_events = function () {
99 Notebook.prototype.bind_events = function () {
100 var that = this;
100 var that = this;
101
101
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
103 var index = that.find_cell_index(data.cell);
103 var index = that.find_cell_index(data.cell);
104 var new_cell = that.insert_cell_below('code',index);
104 var new_cell = that.insert_cell_below('code',index);
105 new_cell.set_text(data.text);
105 new_cell.set_text(data.text);
106 that.dirty = true;
106 that.dirty = true;
107 });
107 });
108
108
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
110 that.dirty = data.value;
110 that.dirty = data.value;
111 });
111 });
112
112
113 $([IPython.events]).on('select.Cell', function (event, data) {
113 $([IPython.events]).on('select.Cell', function (event, data) {
114 var index = that.find_cell_index(data.cell);
114 var index = that.find_cell_index(data.cell);
115 that.select(index);
115 that.select(index);
116 });
116 });
117
117
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
119 var index = that.find_cell_index(data.cell);
119 var index = that.find_cell_index(data.cell);
120 that.select(index);
120 that.select(index);
121 that.edit_mode();
121 that.edit_mode();
122 });
122 });
123
123
124 $([IPython.events]).on('command_mode.Cell', function (event, data) {
124 $([IPython.events]).on('command_mode.Cell', function (event, data) {
125 that.command_mode();
125 that.command_mode();
126 });
126 });
127
127
128 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 $([IPython.events]).on('status_autorestarting.Kernel', function () {
129 IPython.dialog.modal({
129 IPython.dialog.modal({
130 title: "Kernel Restarting",
130 title: "Kernel Restarting",
131 body: "The kernel appears to have died. It will restart automatically.",
131 body: "The kernel appears to have died. It will restart automatically.",
132 buttons: {
132 buttons: {
133 OK : {
133 OK : {
134 class : "btn-primary"
134 class : "btn-primary"
135 }
135 }
136 }
136 }
137 });
137 });
138 });
138 });
139
139
140 var collapse_time = function (time) {
140 var collapse_time = function (time) {
141 var app_height = $('#ipython-main-app').height(); // content height
141 var app_height = $('#ipython-main-app').height(); // content height
142 var splitter_height = $('div#pager_splitter').outerHeight(true);
142 var splitter_height = $('div#pager_splitter').outerHeight(true);
143 var new_height = app_height - splitter_height;
143 var new_height = app_height - splitter_height;
144 that.element.animate({height : new_height + 'px'}, time);
144 that.element.animate({height : new_height + 'px'}, time);
145 };
145 };
146
146
147 this.element.bind('collapse_pager', function (event, extrap) {
147 this.element.bind('collapse_pager', function (event, extrap) {
148 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
148 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
149 collapse_time(time);
149 collapse_time(time);
150 });
150 });
151
151
152 var expand_time = function (time) {
152 var expand_time = function (time) {
153 var app_height = $('#ipython-main-app').height(); // content height
153 var app_height = $('#ipython-main-app').height(); // content height
154 var splitter_height = $('div#pager_splitter').outerHeight(true);
154 var splitter_height = $('div#pager_splitter').outerHeight(true);
155 var pager_height = $('div#pager').outerHeight(true);
155 var pager_height = $('div#pager').outerHeight(true);
156 var new_height = app_height - pager_height - splitter_height;
156 var new_height = app_height - pager_height - splitter_height;
157 that.element.animate({height : new_height + 'px'}, time);
157 that.element.animate({height : new_height + 'px'}, time);
158 };
158 };
159
159
160 this.element.bind('expand_pager', function (event, extrap) {
160 this.element.bind('expand_pager', function (event, extrap) {
161 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
161 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
162 expand_time(time);
162 expand_time(time);
163 });
163 });
164
164
165 // Firefox 22 broke $(window).on("beforeunload")
165 // Firefox 22 broke $(window).on("beforeunload")
166 // I'm not sure why or how.
166 // I'm not sure why or how.
167 window.onbeforeunload = function (e) {
167 window.onbeforeunload = function (e) {
168 // TODO: Make killing the kernel configurable.
168 // TODO: Make killing the kernel configurable.
169 var kill_kernel = false;
169 var kill_kernel = false;
170 if (kill_kernel) {
170 if (kill_kernel) {
171 that.session.kill_kernel();
171 that.session.kill_kernel();
172 }
172 }
173 // if we are autosaving, trigger an autosave on nav-away.
173 // if we are autosaving, trigger an autosave on nav-away.
174 // still warn, because if we don't the autosave may fail.
174 // still warn, because if we don't the autosave may fail.
175 if (that.dirty) {
175 if (that.dirty) {
176 if ( that.autosave_interval ) {
176 if ( that.autosave_interval ) {
177 // schedule autosave in a timeout
177 // schedule autosave in a timeout
178 // this gives you a chance to forcefully discard changes
178 // this gives you a chance to forcefully discard changes
179 // by reloading the page if you *really* want to.
179 // by reloading the page if you *really* want to.
180 // the timer doesn't start until you *dismiss* the dialog.
180 // the timer doesn't start until you *dismiss* the dialog.
181 setTimeout(function () {
181 setTimeout(function () {
182 if (that.dirty) {
182 if (that.dirty) {
183 that.save_notebook();
183 that.save_notebook();
184 }
184 }
185 }, 1000);
185 }, 1000);
186 return "Autosave in progress, latest changes may be lost.";
186 return "Autosave in progress, latest changes may be lost.";
187 } else {
187 } else {
188 return "Unsaved changes will be lost.";
188 return "Unsaved changes will be lost.";
189 }
189 }
190 }
190 }
191 // Null is the *only* return value that will make the browser not
191 // Null is the *only* return value that will make the browser not
192 // pop up the "don't leave" dialog.
192 // pop up the "don't leave" dialog.
193 return null;
193 return null;
194 };
194 };
195 };
195 };
196
196
197 /**
197 /**
198 * Set the dirty flag, and trigger the set_dirty.Notebook event
198 * Set the dirty flag, and trigger the set_dirty.Notebook event
199 *
199 *
200 * @method set_dirty
200 * @method set_dirty
201 */
201 */
202 Notebook.prototype.set_dirty = function (value) {
202 Notebook.prototype.set_dirty = function (value) {
203 if (value === undefined) {
203 if (value === undefined) {
204 value = true;
204 value = true;
205 }
205 }
206 if (this.dirty == value) {
206 if (this.dirty == value) {
207 return;
207 return;
208 }
208 }
209 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
209 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
210 };
210 };
211
211
212 /**
212 /**
213 * Scroll the top of the page to a given cell.
213 * Scroll the top of the page to a given cell.
214 *
214 *
215 * @method scroll_to_cell
215 * @method scroll_to_cell
216 * @param {Number} cell_number An index of the cell to view
216 * @param {Number} cell_number An index of the cell to view
217 * @param {Number} time Animation time in milliseconds
217 * @param {Number} time Animation time in milliseconds
218 * @return {Number} Pixel offset from the top of the container
218 * @return {Number} Pixel offset from the top of the container
219 */
219 */
220 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
220 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
221 var cells = this.get_cells();
221 var cells = this.get_cells();
222 time = time || 0;
222 time = time || 0;
223 cell_number = Math.min(cells.length-1,cell_number);
223 cell_number = Math.min(cells.length-1,cell_number);
224 cell_number = Math.max(0 ,cell_number);
224 cell_number = Math.max(0 ,cell_number);
225 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
225 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
226 this.element.animate({scrollTop:scroll_value}, time);
226 this.element.animate({scrollTop:scroll_value}, time);
227 return scroll_value;
227 return scroll_value;
228 };
228 };
229
229
230 /**
230 /**
231 * Scroll to the bottom of the page.
231 * Scroll to the bottom of the page.
232 *
232 *
233 * @method scroll_to_bottom
233 * @method scroll_to_bottom
234 */
234 */
235 Notebook.prototype.scroll_to_bottom = function () {
235 Notebook.prototype.scroll_to_bottom = function () {
236 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
236 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
237 };
237 };
238
238
239 /**
239 /**
240 * Scroll to the top of the page.
240 * Scroll to the top of the page.
241 *
241 *
242 * @method scroll_to_top
242 * @method scroll_to_top
243 */
243 */
244 Notebook.prototype.scroll_to_top = function () {
244 Notebook.prototype.scroll_to_top = function () {
245 this.element.animate({scrollTop:0}, 0);
245 this.element.animate({scrollTop:0}, 0);
246 };
246 };
247
247
248 // Edit Notebook metadata
248 // Edit Notebook metadata
249
249
250 Notebook.prototype.edit_metadata = function () {
250 Notebook.prototype.edit_metadata = function () {
251 var that = this;
251 var that = this;
252 IPython.dialog.edit_metadata(this.metadata, function (md) {
252 IPython.dialog.edit_metadata(this.metadata, function (md) {
253 that.metadata = md;
253 that.metadata = md;
254 }, 'Notebook');
254 }, 'Notebook');
255 };
255 };
256
256
257 // Cell indexing, retrieval, etc.
257 // Cell indexing, retrieval, etc.
258
258
259 /**
259 /**
260 * Get all cell elements in the notebook.
260 * Get all cell elements in the notebook.
261 *
261 *
262 * @method get_cell_elements
262 * @method get_cell_elements
263 * @return {jQuery} A selector of all cell elements
263 * @return {jQuery} A selector of all cell elements
264 */
264 */
265 Notebook.prototype.get_cell_elements = function () {
265 Notebook.prototype.get_cell_elements = function () {
266 return this.container.children("div.cell");
266 return this.container.children("div.cell");
267 };
267 };
268
268
269 /**
269 /**
270 * Get a particular cell element.
270 * Get a particular cell element.
271 *
271 *
272 * @method get_cell_element
272 * @method get_cell_element
273 * @param {Number} index An index of a cell to select
273 * @param {Number} index An index of a cell to select
274 * @return {jQuery} A selector of the given cell.
274 * @return {jQuery} A selector of the given cell.
275 */
275 */
276 Notebook.prototype.get_cell_element = function (index) {
276 Notebook.prototype.get_cell_element = function (index) {
277 var result = null;
277 var result = null;
278 var e = this.get_cell_elements().eq(index);
278 var e = this.get_cell_elements().eq(index);
279 if (e.length !== 0) {
279 if (e.length !== 0) {
280 result = e;
280 result = e;
281 }
281 }
282 return result;
282 return result;
283 };
283 };
284
284
285 /**
285 /**
286 * Try to get a particular cell by msg_id.
286 * Try to get a particular cell by msg_id.
287 *
287 *
288 * @method get_msg_cell
288 * @method get_msg_cell
289 * @param {String} msg_id A message UUID
289 * @param {String} msg_id A message UUID
290 * @return {Cell} Cell or null if no cell was found.
290 * @return {Cell} Cell or null if no cell was found.
291 */
291 */
292 Notebook.prototype.get_msg_cell = function (msg_id) {
292 Notebook.prototype.get_msg_cell = function (msg_id) {
293 return IPython.CodeCell.msg_cells[msg_id] || null;
293 return IPython.CodeCell.msg_cells[msg_id] || null;
294 };
294 };
295
295
296 /**
296 /**
297 * Count the cells in this notebook.
297 * Count the cells in this notebook.
298 *
298 *
299 * @method ncells
299 * @method ncells
300 * @return {Number} The number of cells in this notebook
300 * @return {Number} The number of cells in this notebook
301 */
301 */
302 Notebook.prototype.ncells = function () {
302 Notebook.prototype.ncells = function () {
303 return this.get_cell_elements().length;
303 return this.get_cell_elements().length;
304 };
304 };
305
305
306 /**
306 /**
307 * Get all Cell objects in this notebook.
307 * Get all Cell objects in this notebook.
308 *
308 *
309 * @method get_cells
309 * @method get_cells
310 * @return {Array} This notebook's Cell objects
310 * @return {Array} This notebook's Cell objects
311 */
311 */
312 // TODO: we are often calling cells as cells()[i], which we should optimize
312 // TODO: we are often calling cells as cells()[i], which we should optimize
313 // to cells(i) or a new method.
313 // to cells(i) or a new method.
314 Notebook.prototype.get_cells = function () {
314 Notebook.prototype.get_cells = function () {
315 return this.get_cell_elements().toArray().map(function (e) {
315 return this.get_cell_elements().toArray().map(function (e) {
316 return $(e).data("cell");
316 return $(e).data("cell");
317 });
317 });
318 };
318 };
319
319
320 /**
320 /**
321 * Get a Cell object from this notebook.
321 * Get a Cell object from this notebook.
322 *
322 *
323 * @method get_cell
323 * @method get_cell
324 * @param {Number} index An index of a cell to retrieve
324 * @param {Number} index An index of a cell to retrieve
325 * @return {Cell} A particular cell
325 * @return {Cell} A particular cell
326 */
326 */
327 Notebook.prototype.get_cell = function (index) {
327 Notebook.prototype.get_cell = function (index) {
328 var result = null;
328 var result = null;
329 var ce = this.get_cell_element(index);
329 var ce = this.get_cell_element(index);
330 if (ce !== null) {
330 if (ce !== null) {
331 result = ce.data('cell');
331 result = ce.data('cell');
332 }
332 }
333 return result;
333 return result;
334 };
334 };
335
335
336 /**
336 /**
337 * Get the cell below a given cell.
337 * Get the cell below a given cell.
338 *
338 *
339 * @method get_next_cell
339 * @method get_next_cell
340 * @param {Cell} cell The provided cell
340 * @param {Cell} cell The provided cell
341 * @return {Cell} The next cell
341 * @return {Cell} The next cell
342 */
342 */
343 Notebook.prototype.get_next_cell = function (cell) {
343 Notebook.prototype.get_next_cell = function (cell) {
344 var result = null;
344 var result = null;
345 var index = this.find_cell_index(cell);
345 var index = this.find_cell_index(cell);
346 if (this.is_valid_cell_index(index+1)) {
346 if (this.is_valid_cell_index(index+1)) {
347 result = this.get_cell(index+1);
347 result = this.get_cell(index+1);
348 }
348 }
349 return result;
349 return result;
350 };
350 };
351
351
352 /**
352 /**
353 * Get the cell above a given cell.
353 * Get the cell above a given cell.
354 *
354 *
355 * @method get_prev_cell
355 * @method get_prev_cell
356 * @param {Cell} cell The provided cell
356 * @param {Cell} cell The provided cell
357 * @return {Cell} The previous cell
357 * @return {Cell} The previous cell
358 */
358 */
359 Notebook.prototype.get_prev_cell = function (cell) {
359 Notebook.prototype.get_prev_cell = function (cell) {
360 // TODO: off-by-one
360 // TODO: off-by-one
361 // nb.get_prev_cell(nb.get_cell(1)) is null
361 // nb.get_prev_cell(nb.get_cell(1)) is null
362 var result = null;
362 var result = null;
363 var index = this.find_cell_index(cell);
363 var index = this.find_cell_index(cell);
364 if (index !== null && index > 1) {
364 if (index !== null && index > 1) {
365 result = this.get_cell(index-1);
365 result = this.get_cell(index-1);
366 }
366 }
367 return result;
367 return result;
368 };
368 };
369
369
370 /**
370 /**
371 * Get the numeric index of a given cell.
371 * Get the numeric index of a given cell.
372 *
372 *
373 * @method find_cell_index
373 * @method find_cell_index
374 * @param {Cell} cell The provided cell
374 * @param {Cell} cell The provided cell
375 * @return {Number} The cell's numeric index
375 * @return {Number} The cell's numeric index
376 */
376 */
377 Notebook.prototype.find_cell_index = function (cell) {
377 Notebook.prototype.find_cell_index = function (cell) {
378 var result = null;
378 var result = null;
379 this.get_cell_elements().filter(function (index) {
379 this.get_cell_elements().filter(function (index) {
380 if ($(this).data("cell") === cell) {
380 if ($(this).data("cell") === cell) {
381 result = index;
381 result = index;
382 }
382 }
383 });
383 });
384 return result;
384 return result;
385 };
385 };
386
386
387 /**
387 /**
388 * Get a given index , or the selected index if none is provided.
388 * Get a given index , or the selected index if none is provided.
389 *
389 *
390 * @method index_or_selected
390 * @method index_or_selected
391 * @param {Number} index A cell's index
391 * @param {Number} index A cell's index
392 * @return {Number} The given index, or selected index if none is provided.
392 * @return {Number} The given index, or selected index if none is provided.
393 */
393 */
394 Notebook.prototype.index_or_selected = function (index) {
394 Notebook.prototype.index_or_selected = function (index) {
395 var i;
395 var i;
396 if (index === undefined || index === null) {
396 if (index === undefined || index === null) {
397 i = this.get_selected_index();
397 i = this.get_selected_index();
398 if (i === null) {
398 if (i === null) {
399 i = 0;
399 i = 0;
400 }
400 }
401 } else {
401 } else {
402 i = index;
402 i = index;
403 }
403 }
404 return i;
404 return i;
405 };
405 };
406
406
407 /**
407 /**
408 * Get the currently selected cell.
408 * Get the currently selected cell.
409 * @method get_selected_cell
409 * @method get_selected_cell
410 * @return {Cell} The selected cell
410 * @return {Cell} The selected cell
411 */
411 */
412 Notebook.prototype.get_selected_cell = function () {
412 Notebook.prototype.get_selected_cell = function () {
413 var index = this.get_selected_index();
413 var index = this.get_selected_index();
414 return this.get_cell(index);
414 return this.get_cell(index);
415 };
415 };
416
416
417 /**
417 /**
418 * Check whether a cell index is valid.
418 * Check whether a cell index is valid.
419 *
419 *
420 * @method is_valid_cell_index
420 * @method is_valid_cell_index
421 * @param {Number} index A cell index
421 * @param {Number} index A cell index
422 * @return True if the index is valid, false otherwise
422 * @return True if the index is valid, false otherwise
423 */
423 */
424 Notebook.prototype.is_valid_cell_index = function (index) {
424 Notebook.prototype.is_valid_cell_index = function (index) {
425 if (index !== null && index >= 0 && index < this.ncells()) {
425 if (index !== null && index >= 0 && index < this.ncells()) {
426 return true;
426 return true;
427 } else {
427 } else {
428 return false;
428 return false;
429 }
429 }
430 };
430 };
431
431
432 /**
432 /**
433 * Get the index of the currently selected cell.
433 * Get the index of the currently selected cell.
434
434
435 * @method get_selected_index
435 * @method get_selected_index
436 * @return {Number} The selected cell's numeric index
436 * @return {Number} The selected cell's numeric index
437 */
437 */
438 Notebook.prototype.get_selected_index = function () {
438 Notebook.prototype.get_selected_index = function () {
439 var result = null;
439 var result = null;
440 this.get_cell_elements().filter(function (index) {
440 this.get_cell_elements().filter(function (index) {
441 if ($(this).data("cell").selected === true) {
441 if ($(this).data("cell").selected === true) {
442 result = index;
442 result = index;
443 }
443 }
444 });
444 });
445 return result;
445 return result;
446 };
446 };
447
447
448
448
449 // Cell selection.
449 // Cell selection.
450
450
451 /**
451 /**
452 * Programmatically select a cell.
452 * Programmatically select a cell.
453 *
453 *
454 * @method select
454 * @method select
455 * @param {Number} index A cell's index
455 * @param {Number} index A cell's index
456 * @return {Notebook} This notebook
456 * @return {Notebook} This notebook
457 */
457 */
458 Notebook.prototype.select = function (index) {
458 Notebook.prototype.select = function (index) {
459 if (this.is_valid_cell_index(index)) {
459 if (this.is_valid_cell_index(index)) {
460 var sindex = this.get_selected_index();
460 var sindex = this.get_selected_index();
461 if (sindex !== null && index !== sindex) {
461 if (sindex !== null && index !== sindex) {
462 this.command_mode();
462 this.command_mode();
463 this.get_cell(sindex).unselect();
463 this.get_cell(sindex).unselect();
464 }
464 }
465 var cell = this.get_cell(index);
465 var cell = this.get_cell(index);
466 cell.select();
466 cell.select();
467 if (cell.cell_type === 'heading') {
467 if (cell.cell_type === 'heading') {
468 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
468 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
469 {'cell_type':cell.cell_type,level:cell.level}
469 {'cell_type':cell.cell_type,level:cell.level}
470 );
470 );
471 } else {
471 } else {
472 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
472 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
473 {'cell_type':cell.cell_type}
473 {'cell_type':cell.cell_type}
474 );
474 );
475 }
475 }
476 }
476 }
477 return this;
477 return this;
478 };
478 };
479
479
480 /**
480 /**
481 * Programmatically select the next cell.
481 * Programmatically select the next cell.
482 *
482 *
483 * @method select_next
483 * @method select_next
484 * @return {Notebook} This notebook
484 * @return {Notebook} This notebook
485 */
485 */
486 Notebook.prototype.select_next = function () {
486 Notebook.prototype.select_next = function () {
487 var index = this.get_selected_index();
487 var index = this.get_selected_index();
488 this.select(index+1);
488 this.select(index+1);
489 return this;
489 return this;
490 };
490 };
491
491
492 /**
492 /**
493 * Programmatically select the previous cell.
493 * Programmatically select the previous cell.
494 *
494 *
495 * @method select_prev
495 * @method select_prev
496 * @return {Notebook} This notebook
496 * @return {Notebook} This notebook
497 */
497 */
498 Notebook.prototype.select_prev = function () {
498 Notebook.prototype.select_prev = function () {
499 var index = this.get_selected_index();
499 var index = this.get_selected_index();
500 this.select(index-1);
500 this.select(index-1);
501 return this;
501 return this;
502 };
502 };
503
503
504
504
505 // Edit/Command mode
505 // Edit/Command mode
506
506
507 Notebook.prototype.get_edit_index = function () {
507 Notebook.prototype.get_edit_index = function () {
508 var result = null;
508 var result = null;
509 this.get_cell_elements().filter(function (index) {
509 this.get_cell_elements().filter(function (index) {
510 if ($(this).data("cell").mode === 'edit') {
510 if ($(this).data("cell").mode === 'edit') {
511 result = index;
511 result = index;
512 }
512 }
513 });
513 });
514 return result;
514 return result;
515 };
515 };
516
516
517 Notebook.prototype.command_mode = function () {
517 Notebook.prototype.command_mode = function () {
518 if (this.mode !== 'command') {
518 if (this.mode !== 'command') {
519 $([IPython.events]).trigger('command_mode.Notebook');
519 $([IPython.events]).trigger('command_mode.Notebook');
520 var index = this.get_edit_index();
520 var index = this.get_edit_index();
521 var cell = this.get_cell(index);
521 var cell = this.get_cell(index);
522 if (cell) {
522 if (cell) {
523 cell.command_mode();
523 cell.command_mode();
524 }
524 }
525 this.mode = 'command';
525 this.mode = 'command';
526 IPython.keyboard_manager.command_mode();
526 IPython.keyboard_manager.command_mode();
527 }
527 }
528 };
528 };
529
529
530 Notebook.prototype.edit_mode = function () {
530 Notebook.prototype.edit_mode = function () {
531 if (this.mode !== 'edit') {
531 if (this.mode !== 'edit') {
532 $([IPython.events]).trigger('edit_mode.Notebook');
532 $([IPython.events]).trigger('edit_mode.Notebook');
533 var cell = this.get_selected_cell();
533 var cell = this.get_selected_cell();
534 if (cell === null) {return;} // No cell is selected
534 if (cell === null) {return;} // No cell is selected
535 // We need to set the mode to edit to prevent reentering this method
535 // We need to set the mode to edit to prevent reentering this method
536 // when cell.edit_mode() is called below.
536 // when cell.edit_mode() is called below.
537 this.mode = 'edit';
537 this.mode = 'edit';
538 IPython.keyboard_manager.edit_mode();
538 IPython.keyboard_manager.edit_mode();
539 cell.edit_mode();
539 cell.edit_mode();
540 }
540 }
541 };
541 };
542
542
543 Notebook.prototype.focus_cell = function () {
543 Notebook.prototype.focus_cell = function () {
544 var cell = this.get_selected_cell();
544 var cell = this.get_selected_cell();
545 if (cell === null) {return;} // No cell is selected
545 if (cell === null) {return;} // No cell is selected
546 cell.focus_cell();
546 cell.focus_cell();
547 };
547 };
548
548
549 // Cell movement
549 // Cell movement
550
550
551 /**
551 /**
552 * Move given (or selected) cell up and select it.
552 * Move given (or selected) cell up and select it.
553 *
553 *
554 * @method move_cell_up
554 * @method move_cell_up
555 * @param [index] {integer} cell index
555 * @param [index] {integer} cell index
556 * @return {Notebook} This notebook
556 * @return {Notebook} This notebook
557 **/
557 **/
558 Notebook.prototype.move_cell_up = function (index) {
558 Notebook.prototype.move_cell_up = function (index) {
559 var i = this.index_or_selected(index);
559 var i = this.index_or_selected(index);
560 if (this.is_valid_cell_index(i) && i > 0) {
560 if (this.is_valid_cell_index(i) && i > 0) {
561 var pivot = this.get_cell_element(i-1);
561 var pivot = this.get_cell_element(i-1);
562 var tomove = this.get_cell_element(i);
562 var tomove = this.get_cell_element(i);
563 if (pivot !== null && tomove !== null) {
563 if (pivot !== null && tomove !== null) {
564 tomove.detach();
564 tomove.detach();
565 pivot.before(tomove);
565 pivot.before(tomove);
566 this.select(i-1);
566 this.select(i-1);
567 var cell = this.get_selected_cell();
567 var cell = this.get_selected_cell();
568 cell.focus_cell();
568 cell.focus_cell();
569 }
569 }
570 this.set_dirty(true);
570 this.set_dirty(true);
571 }
571 }
572 return this;
572 return this;
573 };
573 };
574
574
575
575
576 /**
576 /**
577 * Move given (or selected) cell down and select it
577 * Move given (or selected) cell down and select it
578 *
578 *
579 * @method move_cell_down
579 * @method move_cell_down
580 * @param [index] {integer} cell index
580 * @param [index] {integer} cell index
581 * @return {Notebook} This notebook
581 * @return {Notebook} This notebook
582 **/
582 **/
583 Notebook.prototype.move_cell_down = function (index) {
583 Notebook.prototype.move_cell_down = function (index) {
584 var i = this.index_or_selected(index);
584 var i = this.index_or_selected(index);
585 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
585 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
586 var pivot = this.get_cell_element(i+1);
586 var pivot = this.get_cell_element(i+1);
587 var tomove = this.get_cell_element(i);
587 var tomove = this.get_cell_element(i);
588 if (pivot !== null && tomove !== null) {
588 if (pivot !== null && tomove !== null) {
589 tomove.detach();
589 tomove.detach();
590 pivot.after(tomove);
590 pivot.after(tomove);
591 this.select(i+1);
591 this.select(i+1);
592 var cell = this.get_selected_cell();
592 var cell = this.get_selected_cell();
593 cell.focus_cell();
593 cell.focus_cell();
594 }
594 }
595 }
595 }
596 this.set_dirty();
596 this.set_dirty();
597 return this;
597 return this;
598 };
598 };
599
599
600
600
601 // Insertion, deletion.
601 // Insertion, deletion.
602
602
603 /**
603 /**
604 * Delete a cell from the notebook.
604 * Delete a cell from the notebook.
605 *
605 *
606 * @method delete_cell
606 * @method delete_cell
607 * @param [index] A cell's numeric index
607 * @param [index] A cell's numeric index
608 * @return {Notebook} This notebook
608 * @return {Notebook} This notebook
609 */
609 */
610 Notebook.prototype.delete_cell = function (index) {
610 Notebook.prototype.delete_cell = function (index) {
611 var i = this.index_or_selected(index);
611 var i = this.index_or_selected(index);
612 var cell = this.get_selected_cell();
612 var cell = this.get_selected_cell();
613 this.undelete_backup = cell.toJSON();
613 this.undelete_backup = cell.toJSON();
614 $('#undelete_cell').removeClass('disabled');
614 $('#undelete_cell').removeClass('disabled');
615 if (this.is_valid_cell_index(i)) {
615 if (this.is_valid_cell_index(i)) {
616 var old_ncells = this.ncells();
616 var old_ncells = this.ncells();
617 var ce = this.get_cell_element(i);
617 var ce = this.get_cell_element(i);
618 ce.remove();
618 ce.remove();
619 if (i === 0) {
619 if (i === 0) {
620 // Always make sure we have at least one cell.
620 // Always make sure we have at least one cell.
621 if (old_ncells === 1) {
621 if (old_ncells === 1) {
622 this.insert_cell_below('code');
622 this.insert_cell_below('code');
623 }
623 }
624 this.select(0);
624 this.select(0);
625 this.undelete_index = 0;
625 this.undelete_index = 0;
626 this.undelete_below = false;
626 this.undelete_below = false;
627 } else if (i === old_ncells-1 && i !== 0) {
627 } else if (i === old_ncells-1 && i !== 0) {
628 this.select(i-1);
628 this.select(i-1);
629 this.undelete_index = i - 1;
629 this.undelete_index = i - 1;
630 this.undelete_below = true;
630 this.undelete_below = true;
631 } else {
631 } else {
632 this.select(i);
632 this.select(i);
633 this.undelete_index = i;
633 this.undelete_index = i;
634 this.undelete_below = false;
634 this.undelete_below = false;
635 }
635 }
636 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
636 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
637 this.set_dirty(true);
637 this.set_dirty(true);
638 }
638 }
639 return this;
639 return this;
640 };
640 };
641
641
642 /**
642 /**
643 * Restore the most recently deleted cell.
643 * Restore the most recently deleted cell.
644 *
644 *
645 * @method undelete
645 * @method undelete
646 */
646 */
647 Notebook.prototype.undelete_cell = function() {
647 Notebook.prototype.undelete_cell = function() {
648 if (this.undelete_backup !== null && this.undelete_index !== null) {
648 if (this.undelete_backup !== null && this.undelete_index !== null) {
649 var current_index = this.get_selected_index();
649 var current_index = this.get_selected_index();
650 if (this.undelete_index < current_index) {
650 if (this.undelete_index < current_index) {
651 current_index = current_index + 1;
651 current_index = current_index + 1;
652 }
652 }
653 if (this.undelete_index >= this.ncells()) {
653 if (this.undelete_index >= this.ncells()) {
654 this.select(this.ncells() - 1);
654 this.select(this.ncells() - 1);
655 }
655 }
656 else {
656 else {
657 this.select(this.undelete_index);
657 this.select(this.undelete_index);
658 }
658 }
659 var cell_data = this.undelete_backup;
659 var cell_data = this.undelete_backup;
660 var new_cell = null;
660 var new_cell = null;
661 if (this.undelete_below) {
661 if (this.undelete_below) {
662 new_cell = this.insert_cell_below(cell_data.cell_type);
662 new_cell = this.insert_cell_below(cell_data.cell_type);
663 } else {
663 } else {
664 new_cell = this.insert_cell_above(cell_data.cell_type);
664 new_cell = this.insert_cell_above(cell_data.cell_type);
665 }
665 }
666 new_cell.fromJSON(cell_data);
666 new_cell.fromJSON(cell_data);
667 if (this.undelete_below) {
667 if (this.undelete_below) {
668 this.select(current_index+1);
668 this.select(current_index+1);
669 } else {
669 } else {
670 this.select(current_index);
670 this.select(current_index);
671 }
671 }
672 this.undelete_backup = null;
672 this.undelete_backup = null;
673 this.undelete_index = null;
673 this.undelete_index = null;
674 }
674 }
675 $('#undelete_cell').addClass('disabled');
675 $('#undelete_cell').addClass('disabled');
676 };
676 };
677
677
678 /**
678 /**
679 * Insert a cell so that after insertion the cell is at given index.
679 * Insert a cell so that after insertion the cell is at given index.
680 *
680 *
681 * Similar to insert_above, but index parameter is mandatory
681 * Similar to insert_above, but index parameter is mandatory
682 *
682 *
683 * Index will be brought back into the accissible range [0,n]
683 * Index will be brought back into the accissible range [0,n]
684 *
684 *
685 * @method insert_cell_at_index
685 * @method insert_cell_at_index
686 * @param type {string} in ['code','markdown','heading']
686 * @param type {string} in ['code','markdown','heading']
687 * @param [index] {int} a valid index where to inser cell
687 * @param [index] {int} a valid index where to inser cell
688 *
688 *
689 * @return cell {cell|null} created cell or null
689 * @return cell {cell|null} created cell or null
690 **/
690 **/
691 Notebook.prototype.insert_cell_at_index = function(type, index){
691 Notebook.prototype.insert_cell_at_index = function(type, index){
692
692
693 var ncells = this.ncells();
693 var ncells = this.ncells();
694 index = Math.min(index,ncells);
694 index = Math.min(index,ncells);
695 index = Math.max(index,0);
695 index = Math.max(index,0);
696 var cell = null;
696 var cell = null;
697
697
698 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
698 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
699 if (type === 'code') {
699 if (type === 'code') {
700 cell = new IPython.CodeCell(this.kernel);
700 cell = new IPython.CodeCell(this.kernel);
701 cell.set_input_prompt();
701 cell.set_input_prompt();
702 } else if (type === 'markdown') {
702 } else if (type === 'markdown') {
703 cell = new IPython.MarkdownCell();
703 cell = new IPython.MarkdownCell();
704 } else if (type === 'raw') {
704 } else if (type === 'raw') {
705 cell = new IPython.RawCell();
705 cell = new IPython.RawCell();
706 } else if (type === 'heading') {
706 } else if (type === 'heading') {
707 cell = new IPython.HeadingCell();
707 cell = new IPython.HeadingCell();
708 }
708 }
709
709
710 if(this._insert_element_at_index(cell.element,index)) {
710 if(this._insert_element_at_index(cell.element,index)) {
711 cell.render();
711 cell.render();
712 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
712 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
713 cell.refresh();
713 cell.refresh();
714 // We used to select the cell after we refresh it, but there
714 // We used to select the cell after we refresh it, but there
715 // are now cases were this method is called where select is
715 // are now cases were this method is called where select is
716 // not appropriate. The selection logic should be handled by the
716 // not appropriate. The selection logic should be handled by the
717 // caller of the the top level insert_cell methods.
717 // caller of the the top level insert_cell methods.
718 this.set_dirty(true);
718 this.set_dirty(true);
719 }
719 }
720 }
720 }
721 return cell;
721 return cell;
722
722
723 };
723 };
724
724
725 /**
725 /**
726 * Insert an element at given cell index.
726 * Insert an element at given cell index.
727 *
727 *
728 * @method _insert_element_at_index
728 * @method _insert_element_at_index
729 * @param element {dom element} a cell element
729 * @param element {dom element} a cell element
730 * @param [index] {int} a valid index where to inser cell
730 * @param [index] {int} a valid index where to inser cell
731 * @private
731 * @private
732 *
732 *
733 * return true if everything whent fine.
733 * return true if everything whent fine.
734 **/
734 **/
735 Notebook.prototype._insert_element_at_index = function(element, index){
735 Notebook.prototype._insert_element_at_index = function(element, index){
736 if (element === undefined){
736 if (element === undefined){
737 return false;
737 return false;
738 }
738 }
739
739
740 var ncells = this.ncells();
740 var ncells = this.ncells();
741
741
742 if (ncells === 0) {
742 if (ncells === 0) {
743 // special case append if empty
743 // special case append if empty
744 this.element.find('div.end_space').before(element);
744 this.element.find('div.end_space').before(element);
745 } else if ( ncells === index ) {
745 } else if ( ncells === index ) {
746 // special case append it the end, but not empty
746 // special case append it the end, but not empty
747 this.get_cell_element(index-1).after(element);
747 this.get_cell_element(index-1).after(element);
748 } else if (this.is_valid_cell_index(index)) {
748 } else if (this.is_valid_cell_index(index)) {
749 // otherwise always somewhere to append to
749 // otherwise always somewhere to append to
750 this.get_cell_element(index).before(element);
750 this.get_cell_element(index).before(element);
751 } else {
751 } else {
752 return false;
752 return false;
753 }
753 }
754
754
755 if (this.undelete_index !== null && index <= this.undelete_index) {
755 if (this.undelete_index !== null && index <= this.undelete_index) {
756 this.undelete_index = this.undelete_index + 1;
756 this.undelete_index = this.undelete_index + 1;
757 this.set_dirty(true);
757 this.set_dirty(true);
758 }
758 }
759 return true;
759 return true;
760 };
760 };
761
761
762 /**
762 /**
763 * Insert a cell of given type above given index, or at top
763 * Insert a cell of given type above given index, or at top
764 * of notebook if index smaller than 0.
764 * of notebook if index smaller than 0.
765 *
765 *
766 * default index value is the one of currently selected cell
766 * default index value is the one of currently selected cell
767 *
767 *
768 * @method insert_cell_above
768 * @method insert_cell_above
769 * @param type {string} cell type
769 * @param type {string} cell type
770 * @param [index] {integer}
770 * @param [index] {integer}
771 *
771 *
772 * @return handle to created cell or null
772 * @return handle to created cell or null
773 **/
773 **/
774 Notebook.prototype.insert_cell_above = function (type, index) {
774 Notebook.prototype.insert_cell_above = function (type, index) {
775 index = this.index_or_selected(index);
775 index = this.index_or_selected(index);
776 return this.insert_cell_at_index(type, index);
776 return this.insert_cell_at_index(type, index);
777 };
777 };
778
778
779 /**
779 /**
780 * Insert a cell of given type below given index, or at bottom
780 * Insert a cell of given type below given index, or at bottom
781 * of notebook if index greater thatn number of cell
781 * of notebook if index greater thatn number of cell
782 *
782 *
783 * default index value is the one of currently selected cell
783 * default index value is the one of currently selected cell
784 *
784 *
785 * @method insert_cell_below
785 * @method insert_cell_below
786 * @param type {string} cell type
786 * @param type {string} cell type
787 * @param [index] {integer}
787 * @param [index] {integer}
788 *
788 *
789 * @return handle to created cell or null
789 * @return handle to created cell or null
790 *
790 *
791 **/
791 **/
792 Notebook.prototype.insert_cell_below = function (type, index) {
792 Notebook.prototype.insert_cell_below = function (type, index) {
793 index = this.index_or_selected(index);
793 index = this.index_or_selected(index);
794 return this.insert_cell_at_index(type, index+1);
794 return this.insert_cell_at_index(type, index+1);
795 };
795 };
796
796
797
797
798 /**
798 /**
799 * Insert cell at end of notebook
799 * Insert cell at end of notebook
800 *
800 *
801 * @method insert_cell_at_bottom
801 * @method insert_cell_at_bottom
802 * @param {String} type cell type
802 * @param {String} type cell type
803 *
803 *
804 * @return the added cell; or null
804 * @return the added cell; or null
805 **/
805 **/
806 Notebook.prototype.insert_cell_at_bottom = function (type){
806 Notebook.prototype.insert_cell_at_bottom = function (type){
807 var len = this.ncells();
807 var len = this.ncells();
808 return this.insert_cell_below(type,len-1);
808 return this.insert_cell_below(type,len-1);
809 };
809 };
810
810
811 /**
811 /**
812 * Turn a cell into a code cell.
812 * Turn a cell into a code cell.
813 *
813 *
814 * @method to_code
814 * @method to_code
815 * @param {Number} [index] A cell's index
815 * @param {Number} [index] A cell's index
816 */
816 */
817 Notebook.prototype.to_code = function (index) {
817 Notebook.prototype.to_code = function (index) {
818 var i = this.index_or_selected(index);
818 var i = this.index_or_selected(index);
819 if (this.is_valid_cell_index(i)) {
819 if (this.is_valid_cell_index(i)) {
820 var source_element = this.get_cell_element(i);
820 var source_element = this.get_cell_element(i);
821 var source_cell = source_element.data("cell");
821 var source_cell = source_element.data("cell");
822 if (!(source_cell instanceof IPython.CodeCell)) {
822 if (!(source_cell instanceof IPython.CodeCell)) {
823 var target_cell = this.insert_cell_below('code',i);
823 var target_cell = this.insert_cell_below('code',i);
824 var text = source_cell.get_text();
824 var text = source_cell.get_text();
825 if (text === source_cell.placeholder) {
825 if (text === source_cell.placeholder) {
826 text = '';
826 text = '';
827 }
827 }
828 target_cell.set_text(text);
828 target_cell.set_text(text);
829 // make this value the starting point, so that we can only undo
829 // make this value the starting point, so that we can only undo
830 // to this state, instead of a blank cell
830 // to this state, instead of a blank cell
831 target_cell.code_mirror.clearHistory();
831 target_cell.code_mirror.clearHistory();
832 source_element.remove();
832 source_element.remove();
833 this.select(i);
833 this.select(i);
834 this.set_dirty(true);
834 this.set_dirty(true);
835 }
835 }
836 }
836 }
837 };
837 };
838
838
839 /**
839 /**
840 * Turn a cell into a Markdown cell.
840 * Turn a cell into a Markdown cell.
841 *
841 *
842 * @method to_markdown
842 * @method to_markdown
843 * @param {Number} [index] A cell's index
843 * @param {Number} [index] A cell's index
844 */
844 */
845 Notebook.prototype.to_markdown = function (index) {
845 Notebook.prototype.to_markdown = function (index) {
846 var i = this.index_or_selected(index);
846 var i = this.index_or_selected(index);
847 if (this.is_valid_cell_index(i)) {
847 if (this.is_valid_cell_index(i)) {
848 var source_element = this.get_cell_element(i);
848 var source_element = this.get_cell_element(i);
849 var source_cell = source_element.data("cell");
849 var source_cell = source_element.data("cell");
850 if (!(source_cell instanceof IPython.MarkdownCell)) {
850 if (!(source_cell instanceof IPython.MarkdownCell)) {
851 var target_cell = this.insert_cell_below('markdown',i);
851 var target_cell = this.insert_cell_below('markdown',i);
852 var text = source_cell.get_text();
852 var text = source_cell.get_text();
853 if (text === source_cell.placeholder) {
853 if (text === source_cell.placeholder) {
854 text = '';
854 text = '';
855 }
855 }
856 // We must show the editor before setting its contents
856 // We must show the editor before setting its contents
857 target_cell.unrender();
857 target_cell.unrender();
858 target_cell.set_text(text);
858 target_cell.set_text(text);
859 // make this value the starting point, so that we can only undo
859 // make this value the starting point, so that we can only undo
860 // to this state, instead of a blank cell
860 // to this state, instead of a blank cell
861 target_cell.code_mirror.clearHistory();
861 target_cell.code_mirror.clearHistory();
862 source_element.remove();
862 source_element.remove();
863 this.select(i);
863 this.select(i);
864 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
864 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
865 target_cell.render();
865 target_cell.render();
866 }
866 }
867 this.set_dirty(true);
867 this.set_dirty(true);
868 }
868 }
869 }
869 }
870 };
870 };
871
871
872 /**
872 /**
873 * Turn a cell into a raw text cell.
873 * Turn a cell into a raw text cell.
874 *
874 *
875 * @method to_raw
875 * @method to_raw
876 * @param {Number} [index] A cell's index
876 * @param {Number} [index] A cell's index
877 */
877 */
878 Notebook.prototype.to_raw = function (index) {
878 Notebook.prototype.to_raw = function (index) {
879 var i = this.index_or_selected(index);
879 var i = this.index_or_selected(index);
880 if (this.is_valid_cell_index(i)) {
880 if (this.is_valid_cell_index(i)) {
881 var source_element = this.get_cell_element(i);
881 var source_element = this.get_cell_element(i);
882 var source_cell = source_element.data("cell");
882 var source_cell = source_element.data("cell");
883 var target_cell = null;
883 var target_cell = null;
884 if (!(source_cell instanceof IPython.RawCell)) {
884 if (!(source_cell instanceof IPython.RawCell)) {
885 target_cell = this.insert_cell_below('raw',i);
885 target_cell = this.insert_cell_below('raw',i);
886 var text = source_cell.get_text();
886 var text = source_cell.get_text();
887 if (text === source_cell.placeholder) {
887 if (text === source_cell.placeholder) {
888 text = '';
888 text = '';
889 }
889 }
890 // We must show the editor before setting its contents
890 // We must show the editor before setting its contents
891 target_cell.unrender();
891 target_cell.unrender();
892 target_cell.set_text(text);
892 target_cell.set_text(text);
893 // make this value the starting point, so that we can only undo
893 // make this value the starting point, so that we can only undo
894 // to this state, instead of a blank cell
894 // to this state, instead of a blank cell
895 target_cell.code_mirror.clearHistory();
895 target_cell.code_mirror.clearHistory();
896 source_element.remove();
896 source_element.remove();
897 this.select(i);
897 this.select(i);
898 this.set_dirty(true);
898 this.set_dirty(true);
899 }
899 }
900 }
900 }
901 };
901 };
902
902
903 /**
903 /**
904 * Turn a cell into a heading cell.
904 * Turn a cell into a heading cell.
905 *
905 *
906 * @method to_heading
906 * @method to_heading
907 * @param {Number} [index] A cell's index
907 * @param {Number} [index] A cell's index
908 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
908 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
909 */
909 */
910 Notebook.prototype.to_heading = function (index, level) {
910 Notebook.prototype.to_heading = function (index, level) {
911 level = level || 1;
911 level = level || 1;
912 var i = this.index_or_selected(index);
912 var i = this.index_or_selected(index);
913 if (this.is_valid_cell_index(i)) {
913 if (this.is_valid_cell_index(i)) {
914 var source_element = this.get_cell_element(i);
914 var source_element = this.get_cell_element(i);
915 var source_cell = source_element.data("cell");
915 var source_cell = source_element.data("cell");
916 var target_cell = null;
916 var target_cell = null;
917 if (source_cell instanceof IPython.HeadingCell) {
917 if (source_cell instanceof IPython.HeadingCell) {
918 source_cell.set_level(level);
918 source_cell.set_level(level);
919 } else {
919 } else {
920 target_cell = this.insert_cell_below('heading',i);
920 target_cell = this.insert_cell_below('heading',i);
921 var text = source_cell.get_text();
921 var text = source_cell.get_text();
922 if (text === source_cell.placeholder) {
922 if (text === source_cell.placeholder) {
923 text = '';
923 text = '';
924 }
924 }
925 // We must show the editor before setting its contents
925 // We must show the editor before setting its contents
926 target_cell.set_level(level);
926 target_cell.set_level(level);
927 target_cell.unrender();
927 target_cell.unrender();
928 target_cell.set_text(text);
928 target_cell.set_text(text);
929 // make this value the starting point, so that we can only undo
929 // make this value the starting point, so that we can only undo
930 // to this state, instead of a blank cell
930 // to this state, instead of a blank cell
931 target_cell.code_mirror.clearHistory();
931 target_cell.code_mirror.clearHistory();
932 source_element.remove();
932 source_element.remove();
933 this.select(i);
933 this.select(i);
934 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
934 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
935 target_cell.render();
935 target_cell.render();
936 }
936 }
937 }
937 }
938 this.set_dirty(true);
938 this.set_dirty(true);
939 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
939 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
940 {'cell_type':'heading',level:level}
940 {'cell_type':'heading',level:level}
941 );
941 );
942 }
942 }
943 };
943 };
944
944
945
945
946 // Cut/Copy/Paste
946 // Cut/Copy/Paste
947
947
948 /**
948 /**
949 * Enable UI elements for pasting cells.
949 * Enable UI elements for pasting cells.
950 *
950 *
951 * @method enable_paste
951 * @method enable_paste
952 */
952 */
953 Notebook.prototype.enable_paste = function () {
953 Notebook.prototype.enable_paste = function () {
954 var that = this;
954 var that = this;
955 if (!this.paste_enabled) {
955 if (!this.paste_enabled) {
956 $('#paste_cell_replace').removeClass('disabled')
956 $('#paste_cell_replace').removeClass('disabled')
957 .on('click', function () {that.paste_cell_replace();});
957 .on('click', function () {that.paste_cell_replace();});
958 $('#paste_cell_above').removeClass('disabled')
958 $('#paste_cell_above').removeClass('disabled')
959 .on('click', function () {that.paste_cell_above();});
959 .on('click', function () {that.paste_cell_above();});
960 $('#paste_cell_below').removeClass('disabled')
960 $('#paste_cell_below').removeClass('disabled')
961 .on('click', function () {that.paste_cell_below();});
961 .on('click', function () {that.paste_cell_below();});
962 this.paste_enabled = true;
962 this.paste_enabled = true;
963 }
963 }
964 };
964 };
965
965
966 /**
966 /**
967 * Disable UI elements for pasting cells.
967 * Disable UI elements for pasting cells.
968 *
968 *
969 * @method disable_paste
969 * @method disable_paste
970 */
970 */
971 Notebook.prototype.disable_paste = function () {
971 Notebook.prototype.disable_paste = function () {
972 if (this.paste_enabled) {
972 if (this.paste_enabled) {
973 $('#paste_cell_replace').addClass('disabled').off('click');
973 $('#paste_cell_replace').addClass('disabled').off('click');
974 $('#paste_cell_above').addClass('disabled').off('click');
974 $('#paste_cell_above').addClass('disabled').off('click');
975 $('#paste_cell_below').addClass('disabled').off('click');
975 $('#paste_cell_below').addClass('disabled').off('click');
976 this.paste_enabled = false;
976 this.paste_enabled = false;
977 }
977 }
978 };
978 };
979
979
980 /**
980 /**
981 * Cut a cell.
981 * Cut a cell.
982 *
982 *
983 * @method cut_cell
983 * @method cut_cell
984 */
984 */
985 Notebook.prototype.cut_cell = function () {
985 Notebook.prototype.cut_cell = function () {
986 this.copy_cell();
986 this.copy_cell();
987 this.delete_cell();
987 this.delete_cell();
988 };
988 };
989
989
990 /**
990 /**
991 * Copy a cell.
991 * Copy a cell.
992 *
992 *
993 * @method copy_cell
993 * @method copy_cell
994 */
994 */
995 Notebook.prototype.copy_cell = function () {
995 Notebook.prototype.copy_cell = function () {
996 var cell = this.get_selected_cell();
996 var cell = this.get_selected_cell();
997 this.clipboard = cell.toJSON();
997 this.clipboard = cell.toJSON();
998 this.enable_paste();
998 this.enable_paste();
999 };
999 };
1000
1000
1001 /**
1001 /**
1002 * Replace the selected cell with a cell in the clipboard.
1002 * Replace the selected cell with a cell in the clipboard.
1003 *
1003 *
1004 * @method paste_cell_replace
1004 * @method paste_cell_replace
1005 */
1005 */
1006 Notebook.prototype.paste_cell_replace = function () {
1006 Notebook.prototype.paste_cell_replace = function () {
1007 if (this.clipboard !== null && this.paste_enabled) {
1007 if (this.clipboard !== null && this.paste_enabled) {
1008 var cell_data = this.clipboard;
1008 var cell_data = this.clipboard;
1009 var new_cell = this.insert_cell_above(cell_data.cell_type);
1009 var new_cell = this.insert_cell_above(cell_data.cell_type);
1010 new_cell.fromJSON(cell_data);
1010 new_cell.fromJSON(cell_data);
1011 var old_cell = this.get_next_cell(new_cell);
1011 var old_cell = this.get_next_cell(new_cell);
1012 this.delete_cell(this.find_cell_index(old_cell));
1012 this.delete_cell(this.find_cell_index(old_cell));
1013 this.select(this.find_cell_index(new_cell));
1013 this.select(this.find_cell_index(new_cell));
1014 }
1014 }
1015 };
1015 };
1016
1016
1017 /**
1017 /**
1018 * Paste a cell from the clipboard above the selected cell.
1018 * Paste a cell from the clipboard above the selected cell.
1019 *
1019 *
1020 * @method paste_cell_above
1020 * @method paste_cell_above
1021 */
1021 */
1022 Notebook.prototype.paste_cell_above = function () {
1022 Notebook.prototype.paste_cell_above = function () {
1023 if (this.clipboard !== null && this.paste_enabled) {
1023 if (this.clipboard !== null && this.paste_enabled) {
1024 var cell_data = this.clipboard;
1024 var cell_data = this.clipboard;
1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1026 new_cell.fromJSON(cell_data);
1026 new_cell.fromJSON(cell_data);
1027 new_cell.focus_cell();
1027 new_cell.focus_cell();
1028 }
1028 }
1029 };
1029 };
1030
1030
1031 /**
1031 /**
1032 * Paste a cell from the clipboard below the selected cell.
1032 * Paste a cell from the clipboard below the selected cell.
1033 *
1033 *
1034 * @method paste_cell_below
1034 * @method paste_cell_below
1035 */
1035 */
1036 Notebook.prototype.paste_cell_below = function () {
1036 Notebook.prototype.paste_cell_below = function () {
1037 if (this.clipboard !== null && this.paste_enabled) {
1037 if (this.clipboard !== null && this.paste_enabled) {
1038 var cell_data = this.clipboard;
1038 var cell_data = this.clipboard;
1039 var new_cell = this.insert_cell_below(cell_data.cell_type);
1039 var new_cell = this.insert_cell_below(cell_data.cell_type);
1040 new_cell.fromJSON(cell_data);
1040 new_cell.fromJSON(cell_data);
1041 new_cell.focus_cell();
1041 new_cell.focus_cell();
1042 }
1042 }
1043 };
1043 };
1044
1044
1045 // Split/merge
1045 // Split/merge
1046
1046
1047 /**
1047 /**
1048 * Split the selected cell into two, at the cursor.
1048 * Split the selected cell into two, at the cursor.
1049 *
1049 *
1050 * @method split_cell
1050 * @method split_cell
1051 */
1051 */
1052 Notebook.prototype.split_cell = function () {
1052 Notebook.prototype.split_cell = function () {
1053 var mdc = IPython.MarkdownCell;
1053 var mdc = IPython.MarkdownCell;
1054 var rc = IPython.RawCell;
1054 var rc = IPython.RawCell;
1055 var cell = this.get_selected_cell();
1055 var cell = this.get_selected_cell();
1056 if (cell.is_splittable()) {
1056 if (cell.is_splittable()) {
1057 var texta = cell.get_pre_cursor();
1057 var texta = cell.get_pre_cursor();
1058 var textb = cell.get_post_cursor();
1058 var textb = cell.get_post_cursor();
1059 if (cell instanceof IPython.CodeCell) {
1059 if (cell instanceof IPython.CodeCell) {
1060 // In this case the operations keep the notebook in its existing mode
1060 // In this case the operations keep the notebook in its existing mode
1061 // so we don't need to do any post-op mode changes.
1061 // so we don't need to do any post-op mode changes.
1062 cell.set_text(textb);
1062 cell.set_text(textb);
1063 var new_cell = this.insert_cell_above('code');
1063 var new_cell = this.insert_cell_above('code');
1064 new_cell.set_text(texta);
1064 new_cell.set_text(texta);
1065 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1065 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1066 // We know cell is !rendered so we can use set_text.
1066 // We know cell is !rendered so we can use set_text.
1067 cell.set_text(textb);
1067 cell.set_text(textb);
1068 var new_cell = this.insert_cell_above(cell.cell_type);
1068 var new_cell = this.insert_cell_above(cell.cell_type);
1069 // Unrender the new cell so we can call set_text.
1069 // Unrender the new cell so we can call set_text.
1070 new_cell.unrender();
1070 new_cell.unrender();
1071 new_cell.set_text(texta);
1071 new_cell.set_text(texta);
1072 }
1072 }
1073 }
1073 }
1074 };
1074 };
1075
1075
1076 /**
1076 /**
1077 * Combine the selected cell into the cell above it.
1077 * Combine the selected cell into the cell above it.
1078 *
1078 *
1079 * @method merge_cell_above
1079 * @method merge_cell_above
1080 */
1080 */
1081 Notebook.prototype.merge_cell_above = function () {
1081 Notebook.prototype.merge_cell_above = function () {
1082 var mdc = IPython.MarkdownCell;
1082 var mdc = IPython.MarkdownCell;
1083 var rc = IPython.RawCell;
1083 var rc = IPython.RawCell;
1084 var index = this.get_selected_index();
1084 var index = this.get_selected_index();
1085 var cell = this.get_cell(index);
1085 var cell = this.get_cell(index);
1086 var render = cell.rendered;
1086 var render = cell.rendered;
1087 if (!cell.is_mergeable()) {
1087 if (!cell.is_mergeable()) {
1088 return;
1088 return;
1089 }
1089 }
1090 if (index > 0) {
1090 if (index > 0) {
1091 var upper_cell = this.get_cell(index-1);
1091 var upper_cell = this.get_cell(index-1);
1092 if (!upper_cell.is_mergeable()) {
1092 if (!upper_cell.is_mergeable()) {
1093 return;
1093 return;
1094 }
1094 }
1095 var upper_text = upper_cell.get_text();
1095 var upper_text = upper_cell.get_text();
1096 var text = cell.get_text();
1096 var text = cell.get_text();
1097 if (cell instanceof IPython.CodeCell) {
1097 if (cell instanceof IPython.CodeCell) {
1098 cell.set_text(upper_text+'\n'+text);
1098 cell.set_text(upper_text+'\n'+text);
1099 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1099 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1100 cell.unrender(); // Must unrender before we set_text.
1100 cell.unrender(); // Must unrender before we set_text.
1101 cell.set_text(upper_text+'\n\n'+text);
1101 cell.set_text(upper_text+'\n\n'+text);
1102 if (render) {
1102 if (render) {
1103 // The rendered state of the final cell should match
1103 // The rendered state of the final cell should match
1104 // that of the original selected cell;
1104 // that of the original selected cell;
1105 cell.render();
1105 cell.render();
1106 }
1106 }
1107 }
1107 }
1108 this.delete_cell(index-1);
1108 this.delete_cell(index-1);
1109 this.select(this.find_cell_index(cell));
1109 this.select(this.find_cell_index(cell));
1110 }
1110 }
1111 };
1111 };
1112
1112
1113 /**
1113 /**
1114 * Combine the selected cell into the cell below it.
1114 * Combine the selected cell into the cell below it.
1115 *
1115 *
1116 * @method merge_cell_below
1116 * @method merge_cell_below
1117 */
1117 */
1118 Notebook.prototype.merge_cell_below = function () {
1118 Notebook.prototype.merge_cell_below = function () {
1119 var mdc = IPython.MarkdownCell;
1119 var mdc = IPython.MarkdownCell;
1120 var rc = IPython.RawCell;
1120 var rc = IPython.RawCell;
1121 var index = this.get_selected_index();
1121 var index = this.get_selected_index();
1122 var cell = this.get_cell(index);
1122 var cell = this.get_cell(index);
1123 var render = cell.rendered;
1123 var render = cell.rendered;
1124 if (!cell.is_mergeable()) {
1124 if (!cell.is_mergeable()) {
1125 return;
1125 return;
1126 }
1126 }
1127 if (index < this.ncells()-1) {
1127 if (index < this.ncells()-1) {
1128 var lower_cell = this.get_cell(index+1);
1128 var lower_cell = this.get_cell(index+1);
1129 if (!lower_cell.is_mergeable()) {
1129 if (!lower_cell.is_mergeable()) {
1130 return;
1130 return;
1131 }
1131 }
1132 var lower_text = lower_cell.get_text();
1132 var lower_text = lower_cell.get_text();
1133 var text = cell.get_text();
1133 var text = cell.get_text();
1134 if (cell instanceof IPython.CodeCell) {
1134 if (cell instanceof IPython.CodeCell) {
1135 cell.set_text(text+'\n'+lower_text);
1135 cell.set_text(text+'\n'+lower_text);
1136 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1136 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1137 cell.unrender(); // Must unrender before we set_text.
1137 cell.unrender(); // Must unrender before we set_text.
1138 cell.set_text(text+'\n\n'+lower_text);
1138 cell.set_text(text+'\n\n'+lower_text);
1139 if (render) {
1139 if (render) {
1140 // The rendered state of the final cell should match
1140 // The rendered state of the final cell should match
1141 // that of the original selected cell;
1141 // that of the original selected cell;
1142 cell.render();
1142 cell.render();
1143 }
1143 }
1144 }
1144 }
1145 this.delete_cell(index+1);
1145 this.delete_cell(index+1);
1146 this.select(this.find_cell_index(cell));
1146 this.select(this.find_cell_index(cell));
1147 }
1147 }
1148 };
1148 };
1149
1149
1150
1150
1151 // Cell collapsing and output clearing
1151 // Cell collapsing and output clearing
1152
1152
1153 /**
1153 /**
1154 * Hide a cell's output.
1154 * Hide a cell's output.
1155 *
1155 *
1156 * @method collapse_output
1156 * @method collapse_output
1157 * @param {Number} index A cell's numeric index
1157 * @param {Number} index A cell's numeric index
1158 */
1158 */
1159 Notebook.prototype.collapse_output = function (index) {
1159 Notebook.prototype.collapse_output = function (index) {
1160 var i = this.index_or_selected(index);
1160 var i = this.index_or_selected(index);
1161 var cell = this.get_cell(i);
1161 var cell = this.get_cell(i);
1162 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1162 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1163 cell.collapse_output();
1163 cell.collapse_output();
1164 this.set_dirty(true);
1164 this.set_dirty(true);
1165 }
1165 }
1166 };
1166 };
1167
1167
1168 /**
1168 /**
1169 * Hide each code cell's output area.
1169 * Hide each code cell's output area.
1170 *
1170 *
1171 * @method collapse_all_output
1171 * @method collapse_all_output
1172 */
1172 */
1173 Notebook.prototype.collapse_all_output = function () {
1173 Notebook.prototype.collapse_all_output = function () {
1174 $.map(this.get_cells(), function (cell, i) {
1174 $.map(this.get_cells(), function (cell, i) {
1175 if (cell instanceof IPython.CodeCell) {
1175 if (cell instanceof IPython.CodeCell) {
1176 cell.collapse_output();
1176 cell.collapse_output();
1177 }
1177 }
1178 });
1178 });
1179 // this should not be set if the `collapse` key is removed from nbformat
1179 // this should not be set if the `collapse` key is removed from nbformat
1180 this.set_dirty(true);
1180 this.set_dirty(true);
1181 };
1181 };
1182
1182
1183 /**
1183 /**
1184 * Show a cell's output.
1184 * Show a cell's output.
1185 *
1185 *
1186 * @method expand_output
1186 * @method expand_output
1187 * @param {Number} index A cell's numeric index
1187 * @param {Number} index A cell's numeric index
1188 */
1188 */
1189 Notebook.prototype.expand_output = function (index) {
1189 Notebook.prototype.expand_output = function (index) {
1190 var i = this.index_or_selected(index);
1190 var i = this.index_or_selected(index);
1191 var cell = this.get_cell(i);
1191 var cell = this.get_cell(i);
1192 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1192 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1193 cell.expand_output();
1193 cell.expand_output();
1194 this.set_dirty(true);
1194 this.set_dirty(true);
1195 }
1195 }
1196 };
1196 };
1197
1197
1198 /**
1198 /**
1199 * Expand each code cell's output area, and remove scrollbars.
1199 * Expand each code cell's output area, and remove scrollbars.
1200 *
1200 *
1201 * @method expand_all_output
1201 * @method expand_all_output
1202 */
1202 */
1203 Notebook.prototype.expand_all_output = function () {
1203 Notebook.prototype.expand_all_output = function () {
1204 $.map(this.get_cells(), function (cell, i) {
1204 $.map(this.get_cells(), function (cell, i) {
1205 if (cell instanceof IPython.CodeCell) {
1205 if (cell instanceof IPython.CodeCell) {
1206 cell.expand_output();
1206 cell.expand_output();
1207 }
1207 }
1208 });
1208 });
1209 // this should not be set if the `collapse` key is removed from nbformat
1209 // this should not be set if the `collapse` key is removed from nbformat
1210 this.set_dirty(true);
1210 this.set_dirty(true);
1211 };
1211 };
1212
1212
1213 /**
1213 /**
1214 * Clear the selected CodeCell's output area.
1214 * Clear the selected CodeCell's output area.
1215 *
1215 *
1216 * @method clear_output
1216 * @method clear_output
1217 * @param {Number} index A cell's numeric index
1217 * @param {Number} index A cell's numeric index
1218 */
1218 */
1219 Notebook.prototype.clear_output = function (index) {
1219 Notebook.prototype.clear_output = function (index) {
1220 var i = this.index_or_selected(index);
1220 var i = this.index_or_selected(index);
1221 var cell = this.get_cell(i);
1221 var cell = this.get_cell(i);
1222 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1222 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1223 cell.clear_output();
1223 cell.clear_output();
1224 this.set_dirty(true);
1224 this.set_dirty(true);
1225 }
1225 }
1226 };
1226 };
1227
1227
1228 /**
1228 /**
1229 * Clear each code cell's output area.
1229 * Clear each code cell's output area.
1230 *
1230 *
1231 * @method clear_all_output
1231 * @method clear_all_output
1232 */
1232 */
1233 Notebook.prototype.clear_all_output = function () {
1233 Notebook.prototype.clear_all_output = function () {
1234 $.map(this.get_cells(), function (cell, i) {
1234 $.map(this.get_cells(), function (cell, i) {
1235 if (cell instanceof IPython.CodeCell) {
1235 if (cell instanceof IPython.CodeCell) {
1236 cell.clear_output();
1236 cell.clear_output();
1237 }
1237 }
1238 });
1238 });
1239 this.set_dirty(true);
1239 this.set_dirty(true);
1240 };
1240 };
1241
1241
1242 /**
1242 /**
1243 * Scroll the selected CodeCell's output area.
1243 * Scroll the selected CodeCell's output area.
1244 *
1244 *
1245 * @method scroll_output
1245 * @method scroll_output
1246 * @param {Number} index A cell's numeric index
1246 * @param {Number} index A cell's numeric index
1247 */
1247 */
1248 Notebook.prototype.scroll_output = function (index) {
1248 Notebook.prototype.scroll_output = function (index) {
1249 var i = this.index_or_selected(index);
1249 var i = this.index_or_selected(index);
1250 var cell = this.get_cell(i);
1250 var cell = this.get_cell(i);
1251 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1251 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1252 cell.scroll_output();
1252 cell.scroll_output();
1253 this.set_dirty(true);
1253 this.set_dirty(true);
1254 }
1254 }
1255 };
1255 };
1256
1256
1257 /**
1257 /**
1258 * Expand each code cell's output area, and add a scrollbar for long output.
1258 * Expand each code cell's output area, and add a scrollbar for long output.
1259 *
1259 *
1260 * @method scroll_all_output
1260 * @method scroll_all_output
1261 */
1261 */
1262 Notebook.prototype.scroll_all_output = function () {
1262 Notebook.prototype.scroll_all_output = function () {
1263 $.map(this.get_cells(), function (cell, i) {
1263 $.map(this.get_cells(), function (cell, i) {
1264 if (cell instanceof IPython.CodeCell) {
1264 if (cell instanceof IPython.CodeCell) {
1265 cell.scroll_output();
1265 cell.scroll_output();
1266 }
1266 }
1267 });
1267 });
1268 // this should not be set if the `collapse` key is removed from nbformat
1268 // this should not be set if the `collapse` key is removed from nbformat
1269 this.set_dirty(true);
1269 this.set_dirty(true);
1270 };
1270 };
1271
1271
1272 /** Toggle whether a cell's output is collapsed or expanded.
1272 /** Toggle whether a cell's output is collapsed or expanded.
1273 *
1273 *
1274 * @method toggle_output
1274 * @method toggle_output
1275 * @param {Number} index A cell's numeric index
1275 * @param {Number} index A cell's numeric index
1276 */
1276 */
1277 Notebook.prototype.toggle_output = function (index) {
1277 Notebook.prototype.toggle_output = function (index) {
1278 var i = this.index_or_selected(index);
1278 var i = this.index_or_selected(index);
1279 var cell = this.get_cell(i);
1279 var cell = this.get_cell(i);
1280 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1280 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1281 cell.toggle_output();
1281 cell.toggle_output();
1282 this.set_dirty(true);
1282 this.set_dirty(true);
1283 }
1283 }
1284 };
1284 };
1285
1285
1286 /**
1286 /**
1287 * Hide/show the output of all cells.
1287 * Hide/show the output of all cells.
1288 *
1288 *
1289 * @method toggle_all_output
1289 * @method toggle_all_output
1290 */
1290 */
1291 Notebook.prototype.toggle_all_output = function () {
1291 Notebook.prototype.toggle_all_output = function () {
1292 $.map(this.get_cells(), function (cell, i) {
1292 $.map(this.get_cells(), function (cell, i) {
1293 if (cell instanceof IPython.CodeCell) {
1293 if (cell instanceof IPython.CodeCell) {
1294 cell.toggle_output();
1294 cell.toggle_output();
1295 }
1295 }
1296 });
1296 });
1297 // this should not be set if the `collapse` key is removed from nbformat
1297 // this should not be set if the `collapse` key is removed from nbformat
1298 this.set_dirty(true);
1298 this.set_dirty(true);
1299 };
1299 };
1300
1300
1301 /**
1301 /**
1302 * Toggle a scrollbar for long cell outputs.
1302 * Toggle a scrollbar for long cell outputs.
1303 *
1303 *
1304 * @method toggle_output_scroll
1304 * @method toggle_output_scroll
1305 * @param {Number} index A cell's numeric index
1305 * @param {Number} index A cell's numeric index
1306 */
1306 */
1307 Notebook.prototype.toggle_output_scroll = function (index) {
1307 Notebook.prototype.toggle_output_scroll = function (index) {
1308 var i = this.index_or_selected(index);
1308 var i = this.index_or_selected(index);
1309 var cell = this.get_cell(i);
1309 var cell = this.get_cell(i);
1310 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1310 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1311 cell.toggle_output_scroll();
1311 cell.toggle_output_scroll();
1312 this.set_dirty(true);
1312 this.set_dirty(true);
1313 }
1313 }
1314 };
1314 };
1315
1315
1316 /**
1316 /**
1317 * Toggle the scrolling of long output on all cells.
1317 * Toggle the scrolling of long output on all cells.
1318 *
1318 *
1319 * @method toggle_all_output_scrolling
1319 * @method toggle_all_output_scrolling
1320 */
1320 */
1321 Notebook.prototype.toggle_all_output_scroll = function () {
1321 Notebook.prototype.toggle_all_output_scroll = function () {
1322 $.map(this.get_cells(), function (cell, i) {
1322 $.map(this.get_cells(), function (cell, i) {
1323 if (cell instanceof IPython.CodeCell) {
1323 if (cell instanceof IPython.CodeCell) {
1324 cell.toggle_output_scroll();
1324 cell.toggle_output_scroll();
1325 }
1325 }
1326 });
1326 });
1327 // this should not be set if the `collapse` key is removed from nbformat
1327 // this should not be set if the `collapse` key is removed from nbformat
1328 this.set_dirty(true);
1328 this.set_dirty(true);
1329 };
1329 };
1330
1330
1331 // Other cell functions: line numbers, ...
1331 // Other cell functions: line numbers, ...
1332
1332
1333 /**
1333 /**
1334 * Toggle line numbers in the selected cell's input area.
1334 * Toggle line numbers in the selected cell's input area.
1335 *
1335 *
1336 * @method cell_toggle_line_numbers
1336 * @method cell_toggle_line_numbers
1337 */
1337 */
1338 Notebook.prototype.cell_toggle_line_numbers = function() {
1338 Notebook.prototype.cell_toggle_line_numbers = function() {
1339 this.get_selected_cell().toggle_line_numbers();
1339 this.get_selected_cell().toggle_line_numbers();
1340 };
1340 };
1341
1341
1342 // Session related things
1342 // Session related things
1343
1343
1344 /**
1344 /**
1345 * Start a new session and set it on each code cell.
1345 * Start a new session and set it on each code cell.
1346 *
1346 *
1347 * @method start_session
1347 * @method start_session
1348 */
1348 */
1349 Notebook.prototype.start_session = function () {
1349 Notebook.prototype.start_session = function () {
1350 this.session = new IPython.Session(this, this.options);
1350 this.session = new IPython.Session(this, this.options);
1351 this.session.start($.proxy(this._session_started, this));
1351 this.session.start($.proxy(this._session_started, this));
1352 };
1352 };
1353
1353
1354
1354
1355 /**
1355 /**
1356 * Once a session is started, link the code cells to the kernel and pass the
1356 * Once a session is started, link the code cells to the kernel and pass the
1357 * comm manager to the widget manager
1357 * comm manager to the widget manager
1358 *
1358 *
1359 */
1359 */
1360 Notebook.prototype._session_started = function(){
1360 Notebook.prototype._session_started = function(){
1361 this.kernel = this.session.kernel;
1361 this.kernel = this.session.kernel;
1362 var ncells = this.ncells();
1362 var ncells = this.ncells();
1363 for (var i=0; i<ncells; i++) {
1363 for (var i=0; i<ncells; i++) {
1364 var cell = this.get_cell(i);
1364 var cell = this.get_cell(i);
1365 if (cell instanceof IPython.CodeCell) {
1365 if (cell instanceof IPython.CodeCell) {
1366 cell.set_kernel(this.session.kernel);
1366 cell.set_kernel(this.session.kernel);
1367 }
1367 }
1368 }
1368 }
1369 };
1369 };
1370
1370
1371 /**
1371 /**
1372 * Prompt the user to restart the IPython kernel.
1372 * Prompt the user to restart the IPython kernel.
1373 *
1373 *
1374 * @method restart_kernel
1374 * @method restart_kernel
1375 */
1375 */
1376 Notebook.prototype.restart_kernel = function () {
1376 Notebook.prototype.restart_kernel = function () {
1377 var that = this;
1377 var that = this;
1378 IPython.dialog.modal({
1378 IPython.dialog.modal({
1379 title : "Restart kernel or continue running?",
1379 title : "Restart kernel or continue running?",
1380 body : $("<p/>").text(
1380 body : $("<p/>").text(
1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1382 ),
1382 ),
1383 buttons : {
1383 buttons : {
1384 "Continue running" : {},
1384 "Continue running" : {},
1385 "Restart" : {
1385 "Restart" : {
1386 "class" : "btn-danger",
1386 "class" : "btn-danger",
1387 "click" : function() {
1387 "click" : function() {
1388 that.session.restart_kernel();
1388 that.session.restart_kernel();
1389 }
1389 }
1390 }
1390 }
1391 }
1391 }
1392 });
1392 });
1393 };
1393 };
1394
1394
1395 /**
1395 /**
1396 * Execute or render cell outputs and go into command mode.
1396 * Execute or render cell outputs and go into command mode.
1397 *
1397 *
1398 * @method execute_cell
1398 * @method execute_cell
1399 */
1399 */
1400 Notebook.prototype.execute_cell = function () {
1400 Notebook.prototype.execute_cell = function () {
1401 // mode = shift, ctrl, alt
1401 // mode = shift, ctrl, alt
1402 var cell = this.get_selected_cell();
1402 var cell = this.get_selected_cell();
1403 var cell_index = this.find_cell_index(cell);
1403 var cell_index = this.find_cell_index(cell);
1404
1404
1405 cell.execute();
1405 cell.execute();
1406 this.command_mode();
1406 this.command_mode();
1407 cell.focus_cell();
1407 cell.focus_cell();
1408 this.set_dirty(true);
1408 this.set_dirty(true);
1409 };
1409 };
1410
1410
1411 /**
1411 /**
1412 * Execute or render cell outputs and insert a new cell below.
1412 * Execute or render cell outputs and insert a new cell below.
1413 *
1413 *
1414 * @method execute_cell_and_insert_below
1414 * @method execute_cell_and_insert_below
1415 */
1415 */
1416 Notebook.prototype.execute_cell_and_insert_below = function () {
1416 Notebook.prototype.execute_cell_and_insert_below = function () {
1417 var cell = this.get_selected_cell();
1417 var cell = this.get_selected_cell();
1418 var cell_index = this.find_cell_index(cell);
1418 var cell_index = this.find_cell_index(cell);
1419
1419
1420 cell.execute();
1420 cell.execute();
1421
1421
1422 // If we are at the end always insert a new cell and return
1422 // If we are at the end always insert a new cell and return
1423 if (cell_index === (this.ncells()-1)) {
1423 if (cell_index === (this.ncells()-1)) {
1424 this.insert_cell_below('code');
1424 this.insert_cell_below('code');
1425 this.select(cell_index+1);
1425 this.select(cell_index+1);
1426 this.edit_mode();
1426 this.edit_mode();
1427 this.scroll_to_bottom();
1427 this.scroll_to_bottom();
1428 this.set_dirty(true);
1428 this.set_dirty(true);
1429 return;
1429 return;
1430 }
1430 }
1431
1431
1432 this.insert_cell_below('code');
1432 this.insert_cell_below('code');
1433 this.select(cell_index+1);
1433 this.select(cell_index+1);
1434 this.edit_mode();
1434 this.edit_mode();
1435 this.set_dirty(true);
1435 this.set_dirty(true);
1436 };
1436 };
1437
1437
1438 /**
1438 /**
1439 * Execute or render cell outputs and select the next cell.
1439 * Execute or render cell outputs and select the next cell.
1440 *
1440 *
1441 * @method execute_cell_and_select_below
1441 * @method execute_cell_and_select_below
1442 */
1442 */
1443 Notebook.prototype.execute_cell_and_select_below = function () {
1443 Notebook.prototype.execute_cell_and_select_below = function () {
1444
1444
1445 var cell = this.get_selected_cell();
1445 var cell = this.get_selected_cell();
1446 var cell_index = this.find_cell_index(cell);
1446 var cell_index = this.find_cell_index(cell);
1447
1447
1448 cell.execute();
1448 cell.execute();
1449
1449
1450 // If we are at the end always insert a new cell and return
1450 // If we are at the end always insert a new cell and return
1451 if (cell_index === (this.ncells()-1)) {
1451 if (cell_index === (this.ncells()-1)) {
1452 this.insert_cell_below('code');
1452 this.insert_cell_below('code');
1453 this.select(cell_index+1);
1453 this.select(cell_index+1);
1454 this.edit_mode();
1454 this.edit_mode();
1455 this.scroll_to_bottom();
1455 this.scroll_to_bottom();
1456 this.set_dirty(true);
1456 this.set_dirty(true);
1457 return;
1457 return;
1458 }
1458 }
1459
1459
1460 this.select(cell_index+1);
1460 this.select(cell_index+1);
1461 this.get_cell(cell_index+1).focus_cell();
1461 this.get_cell(cell_index+1).focus_cell();
1462 this.set_dirty(true);
1462 this.set_dirty(true);
1463 };
1463 };
1464
1464
1465 /**
1465 /**
1466 * Execute all cells below the selected cell.
1466 * Execute all cells below the selected cell.
1467 *
1467 *
1468 * @method execute_cells_below
1468 * @method execute_cells_below
1469 */
1469 */
1470 Notebook.prototype.execute_cells_below = function () {
1470 Notebook.prototype.execute_cells_below = function () {
1471 this.execute_cell_range(this.get_selected_index(), this.ncells());
1471 this.execute_cell_range(this.get_selected_index(), this.ncells());
1472 this.scroll_to_bottom();
1472 this.scroll_to_bottom();
1473 };
1473 };
1474
1474
1475 /**
1475 /**
1476 * Execute all cells above the selected cell.
1476 * Execute all cells above the selected cell.
1477 *
1477 *
1478 * @method execute_cells_above
1478 * @method execute_cells_above
1479 */
1479 */
1480 Notebook.prototype.execute_cells_above = function () {
1480 Notebook.prototype.execute_cells_above = function () {
1481 this.execute_cell_range(0, this.get_selected_index());
1481 this.execute_cell_range(0, this.get_selected_index());
1482 };
1482 };
1483
1483
1484 /**
1484 /**
1485 * Execute all cells.
1485 * Execute all cells.
1486 *
1486 *
1487 * @method execute_all_cells
1487 * @method execute_all_cells
1488 */
1488 */
1489 Notebook.prototype.execute_all_cells = function () {
1489 Notebook.prototype.execute_all_cells = function () {
1490 this.execute_cell_range(0, this.ncells());
1490 this.execute_cell_range(0, this.ncells());
1491 this.scroll_to_bottom();
1491 this.scroll_to_bottom();
1492 };
1492 };
1493
1493
1494 /**
1494 /**
1495 * Execute a contiguous range of cells.
1495 * Execute a contiguous range of cells.
1496 *
1496 *
1497 * @method execute_cell_range
1497 * @method execute_cell_range
1498 * @param {Number} start Index of the first cell to execute (inclusive)
1498 * @param {Number} start Index of the first cell to execute (inclusive)
1499 * @param {Number} end Index of the last cell to execute (exclusive)
1499 * @param {Number} end Index of the last cell to execute (exclusive)
1500 */
1500 */
1501 Notebook.prototype.execute_cell_range = function (start, end) {
1501 Notebook.prototype.execute_cell_range = function (start, end) {
1502 for (var i=start; i<end; i++) {
1502 for (var i=start; i<end; i++) {
1503 this.select(i);
1503 this.select(i);
1504 this.execute_cell();
1504 this.execute_cell();
1505 }
1505 }
1506 };
1506 };
1507
1507
1508 // Persistance and loading
1508 // Persistance and loading
1509
1509
1510 /**
1510 /**
1511 * Getter method for this notebook's name.
1511 * Getter method for this notebook's name.
1512 *
1512 *
1513 * @method get_notebook_name
1513 * @method get_notebook_name
1514 * @return {String} This notebook's name (excluding file extension)
1514 * @return {String} This notebook's name (excluding file extension)
1515 */
1515 */
1516 Notebook.prototype.get_notebook_name = function () {
1516 Notebook.prototype.get_notebook_name = function () {
1517 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1517 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1518 return nbname;
1518 return nbname;
1519 };
1519 };
1520
1520
1521 /**
1521 /**
1522 * Setter method for this notebook's name.
1522 * Setter method for this notebook's name.
1523 *
1523 *
1524 * @method set_notebook_name
1524 * @method set_notebook_name
1525 * @param {String} name A new name for this notebook
1525 * @param {String} name A new name for this notebook
1526 */
1526 */
1527 Notebook.prototype.set_notebook_name = function (name) {
1527 Notebook.prototype.set_notebook_name = function (name) {
1528 this.notebook_name = name;
1528 this.notebook_name = name;
1529 };
1529 };
1530
1530
1531 /**
1531 /**
1532 * Check that a notebook's name is valid.
1532 * Check that a notebook's name is valid.
1533 *
1533 *
1534 * @method test_notebook_name
1534 * @method test_notebook_name
1535 * @param {String} nbname A name for this notebook
1535 * @param {String} nbname A name for this notebook
1536 * @return {Boolean} True if the name is valid, false if invalid
1536 * @return {Boolean} True if the name is valid, false if invalid
1537 */
1537 */
1538 Notebook.prototype.test_notebook_name = function (nbname) {
1538 Notebook.prototype.test_notebook_name = function (nbname) {
1539 nbname = nbname || '';
1539 nbname = nbname || '';
1540 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1540 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1541 return true;
1541 return true;
1542 } else {
1542 } else {
1543 return false;
1543 return false;
1544 }
1544 }
1545 };
1545 };
1546
1546
1547 /**
1547 /**
1548 * Load a notebook from JSON (.ipynb).
1548 * Load a notebook from JSON (.ipynb).
1549 *
1549 *
1550 * This currently handles one worksheet: others are deleted.
1550 * This currently handles one worksheet: others are deleted.
1551 *
1551 *
1552 * @method fromJSON
1552 * @method fromJSON
1553 * @param {Object} data JSON representation of a notebook
1553 * @param {Object} data JSON representation of a notebook
1554 */
1554 */
1555 Notebook.prototype.fromJSON = function (data) {
1555 Notebook.prototype.fromJSON = function (data) {
1556 var content = data.content;
1556 var content = data.content;
1557 var ncells = this.ncells();
1557 var ncells = this.ncells();
1558 var i;
1558 var i;
1559 for (i=0; i<ncells; i++) {
1559 for (i=0; i<ncells; i++) {
1560 // Always delete cell 0 as they get renumbered as they are deleted.
1560 // Always delete cell 0 as they get renumbered as they are deleted.
1561 this.delete_cell(0);
1561 this.delete_cell(0);
1562 }
1562 }
1563 // Save the metadata and name.
1563 // Save the metadata and name.
1564 this.metadata = content.metadata;
1564 this.metadata = content.metadata;
1565 this.notebook_name = data.name;
1565 this.notebook_name = data.name;
1566 // Only handle 1 worksheet for now.
1566 // Only handle 1 worksheet for now.
1567 var worksheet = content.worksheets[0];
1567 var worksheet = content.worksheets[0];
1568 if (worksheet !== undefined) {
1568 if (worksheet !== undefined) {
1569 if (worksheet.metadata) {
1569 if (worksheet.metadata) {
1570 this.worksheet_metadata = worksheet.metadata;
1570 this.worksheet_metadata = worksheet.metadata;
1571 }
1571 }
1572 var new_cells = worksheet.cells;
1572 var new_cells = worksheet.cells;
1573 ncells = new_cells.length;
1573 ncells = new_cells.length;
1574 var cell_data = null;
1574 var cell_data = null;
1575 var new_cell = null;
1575 var new_cell = null;
1576 for (i=0; i<ncells; i++) {
1576 for (i=0; i<ncells; i++) {
1577 cell_data = new_cells[i];
1577 cell_data = new_cells[i];
1578 // VERSIONHACK: plaintext -> raw
1578 // VERSIONHACK: plaintext -> raw
1579 // handle never-released plaintext name for raw cells
1579 // handle never-released plaintext name for raw cells
1580 if (cell_data.cell_type === 'plaintext'){
1580 if (cell_data.cell_type === 'plaintext'){
1581 cell_data.cell_type = 'raw';
1581 cell_data.cell_type = 'raw';
1582 }
1582 }
1583
1583
1584 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1584 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1585 new_cell.fromJSON(cell_data);
1585 new_cell.fromJSON(cell_data);
1586 }
1586 }
1587 }
1587 }
1588 if (content.worksheets.length > 1) {
1588 if (content.worksheets.length > 1) {
1589 IPython.dialog.modal({
1589 IPython.dialog.modal({
1590 title : "Multiple worksheets",
1590 title : "Multiple worksheets",
1591 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1591 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1592 "but this version of IPython can only handle the first. " +
1592 "but this version of IPython can only handle the first. " +
1593 "If you save this notebook, worksheets after the first will be lost.",
1593 "If you save this notebook, worksheets after the first will be lost.",
1594 buttons : {
1594 buttons : {
1595 OK : {
1595 OK : {
1596 class : "btn-danger"
1596 class : "btn-danger"
1597 }
1597 }
1598 }
1598 }
1599 });
1599 });
1600 }
1600 }
1601 };
1601 };
1602
1602
1603 /**
1603 /**
1604 * Dump this notebook into a JSON-friendly object.
1604 * Dump this notebook into a JSON-friendly object.
1605 *
1605 *
1606 * @method toJSON
1606 * @method toJSON
1607 * @return {Object} A JSON-friendly representation of this notebook.
1607 * @return {Object} A JSON-friendly representation of this notebook.
1608 */
1608 */
1609 Notebook.prototype.toJSON = function () {
1609 Notebook.prototype.toJSON = function () {
1610 var cells = this.get_cells();
1610 var cells = this.get_cells();
1611 var ncells = cells.length;
1611 var ncells = cells.length;
1612 var cell_array = new Array(ncells);
1612 var cell_array = new Array(ncells);
1613 for (var i=0; i<ncells; i++) {
1613 for (var i=0; i<ncells; i++) {
1614 cell_array[i] = cells[i].toJSON();
1614 cell_array[i] = cells[i].toJSON();
1615 }
1615 }
1616 var data = {
1616 var data = {
1617 // Only handle 1 worksheet for now.
1617 // Only handle 1 worksheet for now.
1618 worksheets : [{
1618 worksheets : [{
1619 cells: cell_array,
1619 cells: cell_array,
1620 metadata: this.worksheet_metadata
1620 metadata: this.worksheet_metadata
1621 }],
1621 }],
1622 metadata : this.metadata
1622 metadata : this.metadata
1623 };
1623 };
1624 return data;
1624 return data;
1625 };
1625 };
1626
1626
1627 /**
1627 /**
1628 * Start an autosave timer, for periodically saving the notebook.
1628 * Start an autosave timer, for periodically saving the notebook.
1629 *
1629 *
1630 * @method set_autosave_interval
1630 * @method set_autosave_interval
1631 * @param {Integer} interval the autosave interval in milliseconds
1631 * @param {Integer} interval the autosave interval in milliseconds
1632 */
1632 */
1633 Notebook.prototype.set_autosave_interval = function (interval) {
1633 Notebook.prototype.set_autosave_interval = function (interval) {
1634 var that = this;
1634 var that = this;
1635 // clear previous interval, so we don't get simultaneous timers
1635 // clear previous interval, so we don't get simultaneous timers
1636 if (this.autosave_timer) {
1636 if (this.autosave_timer) {
1637 clearInterval(this.autosave_timer);
1637 clearInterval(this.autosave_timer);
1638 }
1638 }
1639
1639
1640 this.autosave_interval = this.minimum_autosave_interval = interval;
1640 this.autosave_interval = this.minimum_autosave_interval = interval;
1641 if (interval) {
1641 if (interval) {
1642 this.autosave_timer = setInterval(function() {
1642 this.autosave_timer = setInterval(function() {
1643 if (that.dirty) {
1643 if (that.dirty) {
1644 that.save_notebook();
1644 that.save_notebook();
1645 }
1645 }
1646 }, interval);
1646 }, interval);
1647 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1647 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1648 } else {
1648 } else {
1649 this.autosave_timer = null;
1649 this.autosave_timer = null;
1650 $([IPython.events]).trigger("autosave_disabled.Notebook");
1650 $([IPython.events]).trigger("autosave_disabled.Notebook");
1651 }
1651 }
1652 };
1652 };
1653
1653
1654 /**
1654 /**
1655 * Save this notebook on the server.
1655 * Save this notebook on the server.
1656 *
1656 *
1657 * @method save_notebook
1657 * @method save_notebook
1658 */
1658 */
1659 Notebook.prototype.save_notebook = function (extra_settings) {
1659 Notebook.prototype.save_notebook = function (extra_settings) {
1660 // Create a JSON model to be sent to the server.
1660 // Create a JSON model to be sent to the server.
1661 var model = {};
1661 var model = {};
1662 model.name = this.notebook_name;
1662 model.name = this.notebook_name;
1663 model.path = this.notebook_path;
1663 model.path = this.notebook_path;
1664 model.content = this.toJSON();
1664 model.content = this.toJSON();
1665 model.content.nbformat = this.nbformat;
1665 model.content.nbformat = this.nbformat;
1666 model.content.nbformat_minor = this.nbformat_minor;
1666 model.content.nbformat_minor = this.nbformat_minor;
1667 // time the ajax call for autosave tuning purposes.
1667 // time the ajax call for autosave tuning purposes.
1668 var start = new Date().getTime();
1668 var start = new Date().getTime();
1669 // We do the call with settings so we can set cache to false.
1669 // We do the call with settings so we can set cache to false.
1670 var settings = {
1670 var settings = {
1671 processData : false,
1671 processData : false,
1672 cache : false,
1672 cache : false,
1673 type : "PUT",
1673 type : "PUT",
1674 data : JSON.stringify(model),
1674 data : JSON.stringify(model),
1675 headers : {'Content-Type': 'application/json'},
1675 headers : {'Content-Type': 'application/json'},
1676 success : $.proxy(this.save_notebook_success, this, start),
1676 success : $.proxy(this.save_notebook_success, this, start),
1677 error : $.proxy(this.save_notebook_error, this)
1677 error : $.proxy(this.save_notebook_error, this)
1678 };
1678 };
1679 if (extra_settings) {
1679 if (extra_settings) {
1680 for (var key in extra_settings) {
1680 for (var key in extra_settings) {
1681 settings[key] = extra_settings[key];
1681 settings[key] = extra_settings[key];
1682 }
1682 }
1683 }
1683 }
1684 $([IPython.events]).trigger('notebook_saving.Notebook');
1684 $([IPython.events]).trigger('notebook_saving.Notebook');
1685 var url = utils.url_join_encode(
1685 var url = utils.url_join_encode(
1686 this.base_project_url,
1686 this.base_url,
1687 'api/notebooks',
1687 'api/notebooks',
1688 this.notebook_path,
1688 this.notebook_path,
1689 this.notebook_name
1689 this.notebook_name
1690 );
1690 );
1691 $.ajax(url, settings);
1691 $.ajax(url, settings);
1692 };
1692 };
1693
1693
1694 /**
1694 /**
1695 * Success callback for saving a notebook.
1695 * Success callback for saving a notebook.
1696 *
1696 *
1697 * @method save_notebook_success
1697 * @method save_notebook_success
1698 * @param {Integer} start the time when the save request started
1698 * @param {Integer} start the time when the save request started
1699 * @param {Object} data JSON representation of a notebook
1699 * @param {Object} data JSON representation of a notebook
1700 * @param {String} status Description of response status
1700 * @param {String} status Description of response status
1701 * @param {jqXHR} xhr jQuery Ajax object
1701 * @param {jqXHR} xhr jQuery Ajax object
1702 */
1702 */
1703 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1703 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1704 this.set_dirty(false);
1704 this.set_dirty(false);
1705 $([IPython.events]).trigger('notebook_saved.Notebook');
1705 $([IPython.events]).trigger('notebook_saved.Notebook');
1706 this._update_autosave_interval(start);
1706 this._update_autosave_interval(start);
1707 if (this._checkpoint_after_save) {
1707 if (this._checkpoint_after_save) {
1708 this.create_checkpoint();
1708 this.create_checkpoint();
1709 this._checkpoint_after_save = false;
1709 this._checkpoint_after_save = false;
1710 }
1710 }
1711 };
1711 };
1712
1712
1713 /**
1713 /**
1714 * update the autosave interval based on how long the last save took
1714 * update the autosave interval based on how long the last save took
1715 *
1715 *
1716 * @method _update_autosave_interval
1716 * @method _update_autosave_interval
1717 * @param {Integer} timestamp when the save request started
1717 * @param {Integer} timestamp when the save request started
1718 */
1718 */
1719 Notebook.prototype._update_autosave_interval = function (start) {
1719 Notebook.prototype._update_autosave_interval = function (start) {
1720 var duration = (new Date().getTime() - start);
1720 var duration = (new Date().getTime() - start);
1721 if (this.autosave_interval) {
1721 if (this.autosave_interval) {
1722 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1722 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1723 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1723 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1724 // round to 10 seconds, otherwise we will be setting a new interval too often
1724 // round to 10 seconds, otherwise we will be setting a new interval too often
1725 interval = 10000 * Math.round(interval / 10000);
1725 interval = 10000 * Math.round(interval / 10000);
1726 // set new interval, if it's changed
1726 // set new interval, if it's changed
1727 if (interval != this.autosave_interval) {
1727 if (interval != this.autosave_interval) {
1728 this.set_autosave_interval(interval);
1728 this.set_autosave_interval(interval);
1729 }
1729 }
1730 }
1730 }
1731 };
1731 };
1732
1732
1733 /**
1733 /**
1734 * Failure callback for saving a notebook.
1734 * Failure callback for saving a notebook.
1735 *
1735 *
1736 * @method save_notebook_error
1736 * @method save_notebook_error
1737 * @param {jqXHR} xhr jQuery Ajax object
1737 * @param {jqXHR} xhr jQuery Ajax object
1738 * @param {String} status Description of response status
1738 * @param {String} status Description of response status
1739 * @param {String} error HTTP error message
1739 * @param {String} error HTTP error message
1740 */
1740 */
1741 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1741 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1742 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1742 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1743 };
1743 };
1744
1744
1745 Notebook.prototype.new_notebook = function(){
1745 Notebook.prototype.new_notebook = function(){
1746 var path = this.notebook_path;
1746 var path = this.notebook_path;
1747 var base_project_url = this.base_project_url;
1747 var base_url = this.base_url;
1748 var settings = {
1748 var settings = {
1749 processData : false,
1749 processData : false,
1750 cache : false,
1750 cache : false,
1751 type : "POST",
1751 type : "POST",
1752 dataType : "json",
1752 dataType : "json",
1753 async : false,
1753 async : false,
1754 success : function (data, status, xhr){
1754 success : function (data, status, xhr){
1755 var notebook_name = data.name;
1755 var notebook_name = data.name;
1756 window.open(
1756 window.open(
1757 utils.url_join_encode(
1757 utils.url_join_encode(
1758 base_project_url,
1758 base_url,
1759 'notebooks',
1759 'notebooks',
1760 path,
1760 path,
1761 notebook_name
1761 notebook_name
1762 ),
1762 ),
1763 '_blank'
1763 '_blank'
1764 );
1764 );
1765 }
1765 }
1766 };
1766 };
1767 var url = utils.url_join_encode(
1767 var url = utils.url_join_encode(
1768 base_project_url,
1768 base_url,
1769 'api/notebooks',
1769 'api/notebooks',
1770 path
1770 path
1771 );
1771 );
1772 $.ajax(url,settings);
1772 $.ajax(url,settings);
1773 };
1773 };
1774
1774
1775
1775
1776 Notebook.prototype.copy_notebook = function(){
1776 Notebook.prototype.copy_notebook = function(){
1777 var path = this.notebook_path;
1777 var path = this.notebook_path;
1778 var base_project_url = this.base_project_url;
1778 var base_url = this.base_url;
1779 var settings = {
1779 var settings = {
1780 processData : false,
1780 processData : false,
1781 cache : false,
1781 cache : false,
1782 type : "POST",
1782 type : "POST",
1783 dataType : "json",
1783 dataType : "json",
1784 data : JSON.stringify({copy_from : this.notebook_name}),
1784 data : JSON.stringify({copy_from : this.notebook_name}),
1785 async : false,
1785 async : false,
1786 success : function (data, status, xhr) {
1786 success : function (data, status, xhr) {
1787 window.open(utils.url_join_encode(
1787 window.open(utils.url_join_encode(
1788 base_project_url,
1788 base_url,
1789 'notebooks',
1789 'notebooks',
1790 data.path,
1790 data.path,
1791 data.name
1791 data.name
1792 ), '_blank');
1792 ), '_blank');
1793 }
1793 }
1794 };
1794 };
1795 var url = utils.url_join_encode(
1795 var url = utils.url_join_encode(
1796 base_project_url,
1796 base_url,
1797 'api/notebooks',
1797 'api/notebooks',
1798 path
1798 path
1799 );
1799 );
1800 $.ajax(url,settings);
1800 $.ajax(url,settings);
1801 };
1801 };
1802
1802
1803 Notebook.prototype.rename = function (nbname) {
1803 Notebook.prototype.rename = function (nbname) {
1804 var that = this;
1804 var that = this;
1805 if (!nbname.match(/\.ipynb$/)) {
1805 if (!nbname.match(/\.ipynb$/)) {
1806 nbname = nbname + ".ipynb";
1806 nbname = nbname + ".ipynb";
1807 }
1807 }
1808 var data = {name: nbname};
1808 var data = {name: nbname};
1809 var settings = {
1809 var settings = {
1810 processData : false,
1810 processData : false,
1811 cache : false,
1811 cache : false,
1812 type : "PATCH",
1812 type : "PATCH",
1813 data : JSON.stringify(data),
1813 data : JSON.stringify(data),
1814 dataType: "json",
1814 dataType: "json",
1815 headers : {'Content-Type': 'application/json'},
1815 headers : {'Content-Type': 'application/json'},
1816 success : $.proxy(that.rename_success, this),
1816 success : $.proxy(that.rename_success, this),
1817 error : $.proxy(that.rename_error, this)
1817 error : $.proxy(that.rename_error, this)
1818 };
1818 };
1819 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1819 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1820 var url = utils.url_join_encode(
1820 var url = utils.url_join_encode(
1821 this.base_project_url,
1821 this.base_url,
1822 'api/notebooks',
1822 'api/notebooks',
1823 this.notebook_path,
1823 this.notebook_path,
1824 this.notebook_name
1824 this.notebook_name
1825 );
1825 );
1826 $.ajax(url, settings);
1826 $.ajax(url, settings);
1827 };
1827 };
1828
1828
1829 Notebook.prototype.delete = function () {
1829 Notebook.prototype.delete = function () {
1830 var that = this;
1830 var that = this;
1831 var settings = {
1831 var settings = {
1832 processData : false,
1832 processData : false,
1833 cache : false,
1833 cache : false,
1834 type : "DELETE",
1834 type : "DELETE",
1835 dataType: "json",
1835 dataType: "json",
1836 };
1836 };
1837 var url = utils.url_join_encode(
1837 var url = utils.url_join_encode(
1838 this.base_project_url,
1838 this.base_url,
1839 'api/notebooks',
1839 'api/notebooks',
1840 this.notebook_path,
1840 this.notebook_path,
1841 this.notebook_name
1841 this.notebook_name
1842 );
1842 );
1843 $.ajax(url, settings);
1843 $.ajax(url, settings);
1844 };
1844 };
1845
1845
1846
1846
1847 Notebook.prototype.rename_success = function (json, status, xhr) {
1847 Notebook.prototype.rename_success = function (json, status, xhr) {
1848 var name = this.notebook_name = json.name;
1848 var name = this.notebook_name = json.name;
1849 var path = json.path;
1849 var path = json.path;
1850 this.session.rename_notebook(name, path);
1850 this.session.rename_notebook(name, path);
1851 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1851 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1852 };
1852 };
1853
1853
1854 Notebook.prototype.rename_error = function (xhr, status, error) {
1854 Notebook.prototype.rename_error = function (xhr, status, error) {
1855 var that = this;
1855 var that = this;
1856 var dialog = $('<div/>').append(
1856 var dialog = $('<div/>').append(
1857 $("<p/>").addClass("rename-message")
1857 $("<p/>").addClass("rename-message")
1858 .text('This notebook name already exists.')
1858 .text('This notebook name already exists.')
1859 );
1859 );
1860 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1860 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1861 IPython.dialog.modal({
1861 IPython.dialog.modal({
1862 title: "Notebook Rename Error!",
1862 title: "Notebook Rename Error!",
1863 body: dialog,
1863 body: dialog,
1864 buttons : {
1864 buttons : {
1865 "Cancel": {},
1865 "Cancel": {},
1866 "OK": {
1866 "OK": {
1867 class: "btn-primary",
1867 class: "btn-primary",
1868 click: function () {
1868 click: function () {
1869 IPython.save_widget.rename_notebook();
1869 IPython.save_widget.rename_notebook();
1870 }}
1870 }}
1871 },
1871 },
1872 open : function (event, ui) {
1872 open : function (event, ui) {
1873 var that = $(this);
1873 var that = $(this);
1874 // Upon ENTER, click the OK button.
1874 // Upon ENTER, click the OK button.
1875 that.find('input[type="text"]').keydown(function (event, ui) {
1875 that.find('input[type="text"]').keydown(function (event, ui) {
1876 if (event.which === utils.keycodes.ENTER) {
1876 if (event.which === utils.keycodes.ENTER) {
1877 that.find('.btn-primary').first().click();
1877 that.find('.btn-primary').first().click();
1878 }
1878 }
1879 });
1879 });
1880 that.find('input[type="text"]').focus();
1880 that.find('input[type="text"]').focus();
1881 }
1881 }
1882 });
1882 });
1883 };
1883 };
1884
1884
1885 /**
1885 /**
1886 * Request a notebook's data from the server.
1886 * Request a notebook's data from the server.
1887 *
1887 *
1888 * @method load_notebook
1888 * @method load_notebook
1889 * @param {String} notebook_name and path A notebook to load
1889 * @param {String} notebook_name and path A notebook to load
1890 */
1890 */
1891 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1891 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1892 var that = this;
1892 var that = this;
1893 this.notebook_name = notebook_name;
1893 this.notebook_name = notebook_name;
1894 this.notebook_path = notebook_path;
1894 this.notebook_path = notebook_path;
1895 // We do the call with settings so we can set cache to false.
1895 // We do the call with settings so we can set cache to false.
1896 var settings = {
1896 var settings = {
1897 processData : false,
1897 processData : false,
1898 cache : false,
1898 cache : false,
1899 type : "GET",
1899 type : "GET",
1900 dataType : "json",
1900 dataType : "json",
1901 success : $.proxy(this.load_notebook_success,this),
1901 success : $.proxy(this.load_notebook_success,this),
1902 error : $.proxy(this.load_notebook_error,this),
1902 error : $.proxy(this.load_notebook_error,this),
1903 };
1903 };
1904 $([IPython.events]).trigger('notebook_loading.Notebook');
1904 $([IPython.events]).trigger('notebook_loading.Notebook');
1905 var url = utils.url_join_encode(
1905 var url = utils.url_join_encode(
1906 this.base_project_url,
1906 this.base_url,
1907 'api/notebooks',
1907 'api/notebooks',
1908 this.notebook_path,
1908 this.notebook_path,
1909 this.notebook_name
1909 this.notebook_name
1910 );
1910 );
1911 $.ajax(url, settings);
1911 $.ajax(url, settings);
1912 };
1912 };
1913
1913
1914 /**
1914 /**
1915 * Success callback for loading a notebook from the server.
1915 * Success callback for loading a notebook from the server.
1916 *
1916 *
1917 * Load notebook data from the JSON response.
1917 * Load notebook data from the JSON response.
1918 *
1918 *
1919 * @method load_notebook_success
1919 * @method load_notebook_success
1920 * @param {Object} data JSON representation of a notebook
1920 * @param {Object} data JSON representation of a notebook
1921 * @param {String} status Description of response status
1921 * @param {String} status Description of response status
1922 * @param {jqXHR} xhr jQuery Ajax object
1922 * @param {jqXHR} xhr jQuery Ajax object
1923 */
1923 */
1924 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1924 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1925 this.fromJSON(data);
1925 this.fromJSON(data);
1926 if (this.ncells() === 0) {
1926 if (this.ncells() === 0) {
1927 this.insert_cell_below('code');
1927 this.insert_cell_below('code');
1928 this.select(0);
1928 this.select(0);
1929 this.edit_mode();
1929 this.edit_mode();
1930 } else {
1930 } else {
1931 this.select(0);
1931 this.select(0);
1932 this.command_mode();
1932 this.command_mode();
1933 }
1933 }
1934 this.set_dirty(false);
1934 this.set_dirty(false);
1935 this.scroll_to_top();
1935 this.scroll_to_top();
1936 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1936 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1937 var msg = "This notebook has been converted from an older " +
1937 var msg = "This notebook has been converted from an older " +
1938 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1938 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1939 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1939 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1940 "newer notebook format will be used and older versions of IPython " +
1940 "newer notebook format will be used and older versions of IPython " +
1941 "may not be able to read it. To keep the older version, close the " +
1941 "may not be able to read it. To keep the older version, close the " +
1942 "notebook without saving it.";
1942 "notebook without saving it.";
1943 IPython.dialog.modal({
1943 IPython.dialog.modal({
1944 title : "Notebook converted",
1944 title : "Notebook converted",
1945 body : msg,
1945 body : msg,
1946 buttons : {
1946 buttons : {
1947 OK : {
1947 OK : {
1948 class : "btn-primary"
1948 class : "btn-primary"
1949 }
1949 }
1950 }
1950 }
1951 });
1951 });
1952 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1952 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1953 var that = this;
1953 var that = this;
1954 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1954 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1955 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1955 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1956 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1956 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1957 this_vs + ". You can still work with this notebook, but some features " +
1957 this_vs + ". You can still work with this notebook, but some features " +
1958 "introduced in later notebook versions may not be available.";
1958 "introduced in later notebook versions may not be available.";
1959
1959
1960 IPython.dialog.modal({
1960 IPython.dialog.modal({
1961 title : "Newer Notebook",
1961 title : "Newer Notebook",
1962 body : msg,
1962 body : msg,
1963 buttons : {
1963 buttons : {
1964 OK : {
1964 OK : {
1965 class : "btn-danger"
1965 class : "btn-danger"
1966 }
1966 }
1967 }
1967 }
1968 });
1968 });
1969
1969
1970 }
1970 }
1971
1971
1972 // Create the session after the notebook is completely loaded to prevent
1972 // Create the session after the notebook is completely loaded to prevent
1973 // code execution upon loading, which is a security risk.
1973 // code execution upon loading, which is a security risk.
1974 if (this.session === null) {
1974 if (this.session === null) {
1975 this.start_session();
1975 this.start_session();
1976 }
1976 }
1977 // load our checkpoint list
1977 // load our checkpoint list
1978 this.list_checkpoints();
1978 this.list_checkpoints();
1979
1979
1980 // load toolbar state
1980 // load toolbar state
1981 if (this.metadata.celltoolbar) {
1981 if (this.metadata.celltoolbar) {
1982 IPython.CellToolbar.global_show();
1982 IPython.CellToolbar.global_show();
1983 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1983 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1984 }
1984 }
1985
1985
1986 $([IPython.events]).trigger('notebook_loaded.Notebook');
1986 $([IPython.events]).trigger('notebook_loaded.Notebook');
1987 };
1987 };
1988
1988
1989 /**
1989 /**
1990 * Failure callback for loading a notebook from the server.
1990 * Failure callback for loading a notebook from the server.
1991 *
1991 *
1992 * @method load_notebook_error
1992 * @method load_notebook_error
1993 * @param {jqXHR} xhr jQuery Ajax object
1993 * @param {jqXHR} xhr jQuery Ajax object
1994 * @param {String} status Description of response status
1994 * @param {String} status Description of response status
1995 * @param {String} error HTTP error message
1995 * @param {String} error HTTP error message
1996 */
1996 */
1997 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1997 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1998 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1998 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1999 var msg;
1999 var msg;
2000 if (xhr.status === 400) {
2000 if (xhr.status === 400) {
2001 msg = error;
2001 msg = error;
2002 } else if (xhr.status === 500) {
2002 } else if (xhr.status === 500) {
2003 msg = "An unknown error occurred while loading this notebook. " +
2003 msg = "An unknown error occurred while loading this notebook. " +
2004 "This version can load notebook formats " +
2004 "This version can load notebook formats " +
2005 "v" + this.nbformat + " or earlier.";
2005 "v" + this.nbformat + " or earlier.";
2006 }
2006 }
2007 IPython.dialog.modal({
2007 IPython.dialog.modal({
2008 title: "Error loading notebook",
2008 title: "Error loading notebook",
2009 body : msg,
2009 body : msg,
2010 buttons : {
2010 buttons : {
2011 "OK": {}
2011 "OK": {}
2012 }
2012 }
2013 });
2013 });
2014 };
2014 };
2015
2015
2016 /********************* checkpoint-related *********************/
2016 /********************* checkpoint-related *********************/
2017
2017
2018 /**
2018 /**
2019 * Save the notebook then immediately create a checkpoint.
2019 * Save the notebook then immediately create a checkpoint.
2020 *
2020 *
2021 * @method save_checkpoint
2021 * @method save_checkpoint
2022 */
2022 */
2023 Notebook.prototype.save_checkpoint = function () {
2023 Notebook.prototype.save_checkpoint = function () {
2024 this._checkpoint_after_save = true;
2024 this._checkpoint_after_save = true;
2025 this.save_notebook();
2025 this.save_notebook();
2026 };
2026 };
2027
2027
2028 /**
2028 /**
2029 * Add a checkpoint for this notebook.
2029 * Add a checkpoint for this notebook.
2030 * for use as a callback from checkpoint creation.
2030 * for use as a callback from checkpoint creation.
2031 *
2031 *
2032 * @method add_checkpoint
2032 * @method add_checkpoint
2033 */
2033 */
2034 Notebook.prototype.add_checkpoint = function (checkpoint) {
2034 Notebook.prototype.add_checkpoint = function (checkpoint) {
2035 var found = false;
2035 var found = false;
2036 for (var i = 0; i < this.checkpoints.length; i++) {
2036 for (var i = 0; i < this.checkpoints.length; i++) {
2037 var existing = this.checkpoints[i];
2037 var existing = this.checkpoints[i];
2038 if (existing.id == checkpoint.id) {
2038 if (existing.id == checkpoint.id) {
2039 found = true;
2039 found = true;
2040 this.checkpoints[i] = checkpoint;
2040 this.checkpoints[i] = checkpoint;
2041 break;
2041 break;
2042 }
2042 }
2043 }
2043 }
2044 if (!found) {
2044 if (!found) {
2045 this.checkpoints.push(checkpoint);
2045 this.checkpoints.push(checkpoint);
2046 }
2046 }
2047 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2047 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2048 };
2048 };
2049
2049
2050 /**
2050 /**
2051 * List checkpoints for this notebook.
2051 * List checkpoints for this notebook.
2052 *
2052 *
2053 * @method list_checkpoints
2053 * @method list_checkpoints
2054 */
2054 */
2055 Notebook.prototype.list_checkpoints = function () {
2055 Notebook.prototype.list_checkpoints = function () {
2056 var url = utils.url_join_encode(
2056 var url = utils.url_join_encode(
2057 this.base_project_url,
2057 this.base_url,
2058 'api/notebooks',
2058 'api/notebooks',
2059 this.notebook_path,
2059 this.notebook_path,
2060 this.notebook_name,
2060 this.notebook_name,
2061 'checkpoints'
2061 'checkpoints'
2062 );
2062 );
2063 $.get(url).done(
2063 $.get(url).done(
2064 $.proxy(this.list_checkpoints_success, this)
2064 $.proxy(this.list_checkpoints_success, this)
2065 ).fail(
2065 ).fail(
2066 $.proxy(this.list_checkpoints_error, this)
2066 $.proxy(this.list_checkpoints_error, this)
2067 );
2067 );
2068 };
2068 };
2069
2069
2070 /**
2070 /**
2071 * Success callback for listing checkpoints.
2071 * Success callback for listing checkpoints.
2072 *
2072 *
2073 * @method list_checkpoint_success
2073 * @method list_checkpoint_success
2074 * @param {Object} data JSON representation of a checkpoint
2074 * @param {Object} data JSON representation of a checkpoint
2075 * @param {String} status Description of response status
2075 * @param {String} status Description of response status
2076 * @param {jqXHR} xhr jQuery Ajax object
2076 * @param {jqXHR} xhr jQuery Ajax object
2077 */
2077 */
2078 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2078 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2079 data = $.parseJSON(data);
2079 data = $.parseJSON(data);
2080 this.checkpoints = data;
2080 this.checkpoints = data;
2081 if (data.length) {
2081 if (data.length) {
2082 this.last_checkpoint = data[data.length - 1];
2082 this.last_checkpoint = data[data.length - 1];
2083 } else {
2083 } else {
2084 this.last_checkpoint = null;
2084 this.last_checkpoint = null;
2085 }
2085 }
2086 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2086 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2087 };
2087 };
2088
2088
2089 /**
2089 /**
2090 * Failure callback for listing a checkpoint.
2090 * Failure callback for listing a checkpoint.
2091 *
2091 *
2092 * @method list_checkpoint_error
2092 * @method list_checkpoint_error
2093 * @param {jqXHR} xhr jQuery Ajax object
2093 * @param {jqXHR} xhr jQuery Ajax object
2094 * @param {String} status Description of response status
2094 * @param {String} status Description of response status
2095 * @param {String} error_msg HTTP error message
2095 * @param {String} error_msg HTTP error message
2096 */
2096 */
2097 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2097 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2098 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2098 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2099 };
2099 };
2100
2100
2101 /**
2101 /**
2102 * Create a checkpoint of this notebook on the server from the most recent save.
2102 * Create a checkpoint of this notebook on the server from the most recent save.
2103 *
2103 *
2104 * @method create_checkpoint
2104 * @method create_checkpoint
2105 */
2105 */
2106 Notebook.prototype.create_checkpoint = function () {
2106 Notebook.prototype.create_checkpoint = function () {
2107 var url = utils.url_join_encode(
2107 var url = utils.url_join_encode(
2108 this.base_project_url,
2108 this.base_url,
2109 'api/notebooks',
2109 'api/notebooks',
2110 this.notebook_path,
2110 this.notebook_path,
2111 this.notebook_name,
2111 this.notebook_name,
2112 'checkpoints'
2112 'checkpoints'
2113 );
2113 );
2114 $.post(url).done(
2114 $.post(url).done(
2115 $.proxy(this.create_checkpoint_success, this)
2115 $.proxy(this.create_checkpoint_success, this)
2116 ).fail(
2116 ).fail(
2117 $.proxy(this.create_checkpoint_error, this)
2117 $.proxy(this.create_checkpoint_error, this)
2118 );
2118 );
2119 };
2119 };
2120
2120
2121 /**
2121 /**
2122 * Success callback for creating a checkpoint.
2122 * Success callback for creating a checkpoint.
2123 *
2123 *
2124 * @method create_checkpoint_success
2124 * @method create_checkpoint_success
2125 * @param {Object} data JSON representation of a checkpoint
2125 * @param {Object} data JSON representation of a checkpoint
2126 * @param {String} status Description of response status
2126 * @param {String} status Description of response status
2127 * @param {jqXHR} xhr jQuery Ajax object
2127 * @param {jqXHR} xhr jQuery Ajax object
2128 */
2128 */
2129 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2129 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2130 data = $.parseJSON(data);
2130 data = $.parseJSON(data);
2131 this.add_checkpoint(data);
2131 this.add_checkpoint(data);
2132 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2132 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2133 };
2133 };
2134
2134
2135 /**
2135 /**
2136 * Failure callback for creating a checkpoint.
2136 * Failure callback for creating a checkpoint.
2137 *
2137 *
2138 * @method create_checkpoint_error
2138 * @method create_checkpoint_error
2139 * @param {jqXHR} xhr jQuery Ajax object
2139 * @param {jqXHR} xhr jQuery Ajax object
2140 * @param {String} status Description of response status
2140 * @param {String} status Description of response status
2141 * @param {String} error_msg HTTP error message
2141 * @param {String} error_msg HTTP error message
2142 */
2142 */
2143 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2143 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2144 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2144 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2145 };
2145 };
2146
2146
2147 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2147 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2148 var that = this;
2148 var that = this;
2149 checkpoint = checkpoint || this.last_checkpoint;
2149 checkpoint = checkpoint || this.last_checkpoint;
2150 if ( ! checkpoint ) {
2150 if ( ! checkpoint ) {
2151 console.log("restore dialog, but no checkpoint to restore to!");
2151 console.log("restore dialog, but no checkpoint to restore to!");
2152 return;
2152 return;
2153 }
2153 }
2154 var body = $('<div/>').append(
2154 var body = $('<div/>').append(
2155 $('<p/>').addClass("p-space").text(
2155 $('<p/>').addClass("p-space").text(
2156 "Are you sure you want to revert the notebook to " +
2156 "Are you sure you want to revert the notebook to " +
2157 "the latest checkpoint?"
2157 "the latest checkpoint?"
2158 ).append(
2158 ).append(
2159 $("<strong/>").text(
2159 $("<strong/>").text(
2160 " This cannot be undone."
2160 " This cannot be undone."
2161 )
2161 )
2162 )
2162 )
2163 ).append(
2163 ).append(
2164 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2164 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2165 ).append(
2165 ).append(
2166 $('<p/>').addClass("p-space").text(
2166 $('<p/>').addClass("p-space").text(
2167 Date(checkpoint.last_modified)
2167 Date(checkpoint.last_modified)
2168 ).css("text-align", "center")
2168 ).css("text-align", "center")
2169 );
2169 );
2170
2170
2171 IPython.dialog.modal({
2171 IPython.dialog.modal({
2172 title : "Revert notebook to checkpoint",
2172 title : "Revert notebook to checkpoint",
2173 body : body,
2173 body : body,
2174 buttons : {
2174 buttons : {
2175 Revert : {
2175 Revert : {
2176 class : "btn-danger",
2176 class : "btn-danger",
2177 click : function () {
2177 click : function () {
2178 that.restore_checkpoint(checkpoint.id);
2178 that.restore_checkpoint(checkpoint.id);
2179 }
2179 }
2180 },
2180 },
2181 Cancel : {}
2181 Cancel : {}
2182 }
2182 }
2183 });
2183 });
2184 };
2184 };
2185
2185
2186 /**
2186 /**
2187 * Restore the notebook to a checkpoint state.
2187 * Restore the notebook to a checkpoint state.
2188 *
2188 *
2189 * @method restore_checkpoint
2189 * @method restore_checkpoint
2190 * @param {String} checkpoint ID
2190 * @param {String} checkpoint ID
2191 */
2191 */
2192 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2192 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2193 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2193 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2194 var url = utils.url_join_encode(
2194 var url = utils.url_join_encode(
2195 this.base_project_url,
2195 this.base_url,
2196 'api/notebooks',
2196 'api/notebooks',
2197 this.notebook_path,
2197 this.notebook_path,
2198 this.notebook_name,
2198 this.notebook_name,
2199 'checkpoints',
2199 'checkpoints',
2200 checkpoint
2200 checkpoint
2201 );
2201 );
2202 $.post(url).done(
2202 $.post(url).done(
2203 $.proxy(this.restore_checkpoint_success, this)
2203 $.proxy(this.restore_checkpoint_success, this)
2204 ).fail(
2204 ).fail(
2205 $.proxy(this.restore_checkpoint_error, this)
2205 $.proxy(this.restore_checkpoint_error, this)
2206 );
2206 );
2207 };
2207 };
2208
2208
2209 /**
2209 /**
2210 * Success callback for restoring a notebook to a checkpoint.
2210 * Success callback for restoring a notebook to a checkpoint.
2211 *
2211 *
2212 * @method restore_checkpoint_success
2212 * @method restore_checkpoint_success
2213 * @param {Object} data (ignored, should be empty)
2213 * @param {Object} data (ignored, should be empty)
2214 * @param {String} status Description of response status
2214 * @param {String} status Description of response status
2215 * @param {jqXHR} xhr jQuery Ajax object
2215 * @param {jqXHR} xhr jQuery Ajax object
2216 */
2216 */
2217 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2217 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2218 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2218 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2219 this.load_notebook(this.notebook_name, this.notebook_path);
2219 this.load_notebook(this.notebook_name, this.notebook_path);
2220 };
2220 };
2221
2221
2222 /**
2222 /**
2223 * Failure callback for restoring a notebook to a checkpoint.
2223 * Failure callback for restoring a notebook to a checkpoint.
2224 *
2224 *
2225 * @method restore_checkpoint_error
2225 * @method restore_checkpoint_error
2226 * @param {jqXHR} xhr jQuery Ajax object
2226 * @param {jqXHR} xhr jQuery Ajax object
2227 * @param {String} status Description of response status
2227 * @param {String} status Description of response status
2228 * @param {String} error_msg HTTP error message
2228 * @param {String} error_msg HTTP error message
2229 */
2229 */
2230 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2230 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2231 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2231 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2232 };
2232 };
2233
2233
2234 /**
2234 /**
2235 * Delete a notebook checkpoint.
2235 * Delete a notebook checkpoint.
2236 *
2236 *
2237 * @method delete_checkpoint
2237 * @method delete_checkpoint
2238 * @param {String} checkpoint ID
2238 * @param {String} checkpoint ID
2239 */
2239 */
2240 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2240 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2241 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2241 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2242 var url = utils.url_join_encode(
2242 var url = utils.url_join_encode(
2243 this.base_project_url,
2243 this.base_url,
2244 'api/notebooks',
2244 'api/notebooks',
2245 this.notebook_path,
2245 this.notebook_path,
2246 this.notebook_name,
2246 this.notebook_name,
2247 'checkpoints',
2247 'checkpoints',
2248 checkpoint
2248 checkpoint
2249 );
2249 );
2250 $.ajax(url, {
2250 $.ajax(url, {
2251 type: 'DELETE',
2251 type: 'DELETE',
2252 success: $.proxy(this.delete_checkpoint_success, this),
2252 success: $.proxy(this.delete_checkpoint_success, this),
2253 error: $.proxy(this.delete_notebook_error,this)
2253 error: $.proxy(this.delete_notebook_error,this)
2254 });
2254 });
2255 };
2255 };
2256
2256
2257 /**
2257 /**
2258 * Success callback for deleting a notebook checkpoint
2258 * Success callback for deleting a notebook checkpoint
2259 *
2259 *
2260 * @method delete_checkpoint_success
2260 * @method delete_checkpoint_success
2261 * @param {Object} data (ignored, should be empty)
2261 * @param {Object} data (ignored, should be empty)
2262 * @param {String} status Description of response status
2262 * @param {String} status Description of response status
2263 * @param {jqXHR} xhr jQuery Ajax object
2263 * @param {jqXHR} xhr jQuery Ajax object
2264 */
2264 */
2265 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2265 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2266 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2266 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2267 this.load_notebook(this.notebook_name, this.notebook_path);
2267 this.load_notebook(this.notebook_name, this.notebook_path);
2268 };
2268 };
2269
2269
2270 /**
2270 /**
2271 * Failure callback for deleting a notebook checkpoint.
2271 * Failure callback for deleting a notebook checkpoint.
2272 *
2272 *
2273 * @method delete_checkpoint_error
2273 * @method delete_checkpoint_error
2274 * @param {jqXHR} xhr jQuery Ajax object
2274 * @param {jqXHR} xhr jQuery Ajax object
2275 * @param {String} status Description of response status
2275 * @param {String} status Description of response status
2276 * @param {String} error_msg HTTP error message
2276 * @param {String} error_msg HTTP error message
2277 */
2277 */
2278 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2278 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2279 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2279 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2280 };
2280 };
2281
2281
2282
2282
2283 IPython.Notebook = Notebook;
2283 IPython.Notebook = Notebook;
2284
2284
2285
2285
2286 return IPython;
2286 return IPython;
2287
2287
2288 }(IPython));
2288 }(IPython));
@@ -1,119 +1,119
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var Session = function(notebook, options){
17 var Session = function(notebook, options){
18 this.kernel = null;
18 this.kernel = null;
19 this.id = null;
19 this.id = null;
20 this.notebook = notebook;
20 this.notebook = notebook;
21 this.name = notebook.notebook_name;
21 this.name = notebook.notebook_name;
22 this.path = notebook.notebook_path;
22 this.path = notebook.notebook_path;
23 this.base_project_url = notebook.base_project_url;
23 this.base_url = notebook.base_url;
24 this.base_kernel_url = options.base_kernel_url || utils.get_data("baseKernelUrl");
24 this.base_kernel_url = options.base_kernel_url || utils.get_data("baseKernelUrl");
25 };
25 };
26
26
27 Session.prototype.start = function(callback) {
27 Session.prototype.start = function(callback) {
28 var that = this;
28 var that = this;
29 var model = {
29 var model = {
30 notebook : {
30 notebook : {
31 name : this.name,
31 name : this.name,
32 path : this.path
32 path : this.path
33 }
33 }
34 };
34 };
35 var settings = {
35 var settings = {
36 processData : false,
36 processData : false,
37 cache : false,
37 cache : false,
38 type : "POST",
38 type : "POST",
39 data: JSON.stringify(model),
39 data: JSON.stringify(model),
40 dataType : "json",
40 dataType : "json",
41 success : function (data, status, xhr) {
41 success : function (data, status, xhr) {
42 that._handle_start_success(data);
42 that._handle_start_success(data);
43 if (callback) {
43 if (callback) {
44 callback(data, status, xhr);
44 callback(data, status, xhr);
45 }
45 }
46 },
46 },
47 };
47 };
48 var url = utils.url_join_encode(this.base_project_url, 'api/sessions');
48 var url = utils.url_join_encode(this.base_url, 'api/sessions');
49 $.ajax(url, settings);
49 $.ajax(url, settings);
50 };
50 };
51
51
52 Session.prototype.rename_notebook = function (name, path) {
52 Session.prototype.rename_notebook = function (name, path) {
53 this.name = name;
53 this.name = name;
54 this.path = path;
54 this.path = path;
55 var model = {
55 var model = {
56 notebook : {
56 notebook : {
57 name : this.name,
57 name : this.name,
58 path : this.path
58 path : this.path
59 }
59 }
60 };
60 };
61 var settings = {
61 var settings = {
62 processData : false,
62 processData : false,
63 cache : false,
63 cache : false,
64 type : "PATCH",
64 type : "PATCH",
65 data: JSON.stringify(model),
65 data: JSON.stringify(model),
66 dataType : "json",
66 dataType : "json",
67 };
67 };
68 var url = utils.url_join_encode(this.base_project_url, 'api/sessions', this.id);
68 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
69 $.ajax(url, settings);
69 $.ajax(url, settings);
70 };
70 };
71
71
72 Session.prototype.delete = function() {
72 Session.prototype.delete = function() {
73 var settings = {
73 var settings = {
74 processData : false,
74 processData : false,
75 cache : false,
75 cache : false,
76 type : "DELETE",
76 type : "DELETE",
77 dataType : "json",
77 dataType : "json",
78 };
78 };
79 this.kernel.running = false;
79 this.kernel.running = false;
80 var url = utils.url_join_encode(this.base_project_url, 'api/sessions', this.id);
80 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
81 $.ajax(url, settings);
81 $.ajax(url, settings);
82 };
82 };
83
83
84 // Kernel related things
84 // Kernel related things
85 /**
85 /**
86 * Create the Kernel object associated with this Session.
86 * Create the Kernel object associated with this Session.
87 *
87 *
88 * @method _handle_start_success
88 * @method _handle_start_success
89 */
89 */
90 Session.prototype._handle_start_success = function (data, status, xhr) {
90 Session.prototype._handle_start_success = function (data, status, xhr) {
91 this.id = data.id;
91 this.id = data.id;
92 var base_url = utils.url_join_encode(this.base_kernel_url, "api/kernels");
92 var base_url = utils.url_join_encode(this.base_kernel_url, "api/kernels");
93 this.kernel = new IPython.Kernel(base_url);
93 this.kernel = new IPython.Kernel(base_url);
94 this.kernel._kernel_started(data.kernel);
94 this.kernel._kernel_started(data.kernel);
95 };
95 };
96
96
97 /**
97 /**
98 * Prompt the user to restart the IPython kernel.
98 * Prompt the user to restart the IPython kernel.
99 *
99 *
100 * @method restart_kernel
100 * @method restart_kernel
101 */
101 */
102 Session.prototype.restart_kernel = function () {
102 Session.prototype.restart_kernel = function () {
103 this.kernel.restart();
103 this.kernel.restart();
104 };
104 };
105
105
106 Session.prototype.interrupt_kernel = function() {
106 Session.prototype.interrupt_kernel = function() {
107 this.kernel.interrupt();
107 this.kernel.interrupt();
108 };
108 };
109
109
110
110
111 Session.prototype.kill_kernel = function() {
111 Session.prototype.kill_kernel = function() {
112 this.kernel.kill();
112 this.kernel.kill();
113 };
113 };
114
114
115 IPython.Session = Session;
115 IPython.Session = Session;
116
116
117 return IPython;
117 return IPython;
118
118
119 }(IPython));
119 }(IPython));
@@ -1,196 +1,196
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var ClusterList = function (selector, options) {
17 var ClusterList = function (selector, options) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 options = options || {};
24 options = options || {};
25 this.options = options;
25 this.options = options;
26 this.base_project_url = options.base_project_url || utils.get_data("baseProjectUrl");
26 this.base_url = options.base_url || utils.get_data("baseUrl");
27 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
27 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
28 };
28 };
29
29
30 ClusterList.prototype.style = function () {
30 ClusterList.prototype.style = function () {
31 $('#cluster_list').addClass('list_container');
31 $('#cluster_list').addClass('list_container');
32 $('#cluster_toolbar').addClass('list_toolbar');
32 $('#cluster_toolbar').addClass('list_toolbar');
33 $('#cluster_list_info').addClass('toolbar_info');
33 $('#cluster_list_info').addClass('toolbar_info');
34 $('#cluster_buttons').addClass('toolbar_buttons');
34 $('#cluster_buttons').addClass('toolbar_buttons');
35 };
35 };
36
36
37
37
38 ClusterList.prototype.bind_events = function () {
38 ClusterList.prototype.bind_events = function () {
39 var that = this;
39 var that = this;
40 $('#refresh_cluster_list').click(function () {
40 $('#refresh_cluster_list').click(function () {
41 that.load_list();
41 that.load_list();
42 });
42 });
43 };
43 };
44
44
45
45
46 ClusterList.prototype.load_list = function () {
46 ClusterList.prototype.load_list = function () {
47 var settings = {
47 var settings = {
48 processData : false,
48 processData : false,
49 cache : false,
49 cache : false,
50 type : "GET",
50 type : "GET",
51 dataType : "json",
51 dataType : "json",
52 success : $.proxy(this.load_list_success, this)
52 success : $.proxy(this.load_list_success, this)
53 };
53 };
54 var url = utils.url_join_encode(this.base_project_url, 'clusters');
54 var url = utils.url_join_encode(this.base_url, 'clusters');
55 $.ajax(url, settings);
55 $.ajax(url, settings);
56 };
56 };
57
57
58
58
59 ClusterList.prototype.clear_list = function () {
59 ClusterList.prototype.clear_list = function () {
60 this.element.children('.list_item').remove();
60 this.element.children('.list_item').remove();
61 };
61 };
62
62
63 ClusterList.prototype.load_list_success = function (data, status, xhr) {
63 ClusterList.prototype.load_list_success = function (data, status, xhr) {
64 this.clear_list();
64 this.clear_list();
65 var len = data.length;
65 var len = data.length;
66 for (var i=0; i<len; i++) {
66 for (var i=0; i<len; i++) {
67 var element = $('<div/>');
67 var element = $('<div/>');
68 var item = new ClusterItem(element, this.options);
68 var item = new ClusterItem(element, this.options);
69 item.update_state(data[i]);
69 item.update_state(data[i]);
70 element.data('item', item);
70 element.data('item', item);
71 this.element.append(element);
71 this.element.append(element);
72 }
72 }
73 };
73 };
74
74
75
75
76 var ClusterItem = function (element, options) {
76 var ClusterItem = function (element, options) {
77 this.element = $(element);
77 this.element = $(element);
78 this.base_project_url = options.base_project_url || utils.get_data("baseProjectUrl");
78 this.base_url = options.base_url || utils.get_data("baseUrl");
79 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
79 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
80 this.data = null;
80 this.data = null;
81 this.style();
81 this.style();
82 };
82 };
83
83
84 ClusterItem.prototype.style = function () {
84 ClusterItem.prototype.style = function () {
85 this.element.addClass('list_item').addClass("row-fluid");
85 this.element.addClass('list_item').addClass("row-fluid");
86 };
86 };
87
87
88 ClusterItem.prototype.update_state = function (data) {
88 ClusterItem.prototype.update_state = function (data) {
89 this.data = data;
89 this.data = data;
90 if (data.status === 'running') {
90 if (data.status === 'running') {
91 this.state_running();
91 this.state_running();
92 } else if (data.status === 'stopped') {
92 } else if (data.status === 'stopped') {
93 this.state_stopped();
93 this.state_stopped();
94 }
94 }
95 };
95 };
96
96
97
97
98 ClusterItem.prototype.state_stopped = function () {
98 ClusterItem.prototype.state_stopped = function () {
99 var that = this;
99 var that = this;
100 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
100 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
101 var status_col = $('<div/>').addClass('status_col span3').text('stopped');
101 var status_col = $('<div/>').addClass('status_col span3').text('stopped');
102 var engines_col = $('<div/>').addClass('engine_col span3');
102 var engines_col = $('<div/>').addClass('engine_col span3');
103 var input = $('<input/>').attr('type','number')
103 var input = $('<input/>').attr('type','number')
104 .attr('min',1)
104 .attr('min',1)
105 .attr('size',3)
105 .attr('size',3)
106 .addClass('engine_num_input');
106 .addClass('engine_num_input');
107 engines_col.append(input);
107 engines_col.append(input);
108 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
108 var start_button = $('<button/>').addClass("btn btn-mini").text("Start");
109 var action_col = $('<div/>').addClass('action_col span2').append(
109 var action_col = $('<div/>').addClass('action_col span2').append(
110 $("<span/>").addClass("item_buttons btn-group").append(
110 $("<span/>").addClass("item_buttons btn-group").append(
111 start_button
111 start_button
112 )
112 )
113 );
113 );
114 this.element.empty()
114 this.element.empty()
115 .append(profile_col)
115 .append(profile_col)
116 .append(status_col)
116 .append(status_col)
117 .append(engines_col)
117 .append(engines_col)
118 .append(action_col);
118 .append(action_col);
119 start_button.click(function (e) {
119 start_button.click(function (e) {
120 var n = that.element.find('.engine_num_input').val();
120 var n = that.element.find('.engine_num_input').val();
121 if (!/^\d+$/.test(n) && n.length>0) {
121 if (!/^\d+$/.test(n) && n.length>0) {
122 status_col.text('invalid engine #');
122 status_col.text('invalid engine #');
123 } else {
123 } else {
124 var settings = {
124 var settings = {
125 cache : false,
125 cache : false,
126 data : {n:n},
126 data : {n:n},
127 type : "POST",
127 type : "POST",
128 dataType : "json",
128 dataType : "json",
129 success : function (data, status, xhr) {
129 success : function (data, status, xhr) {
130 that.update_state(data);
130 that.update_state(data);
131 },
131 },
132 error : function (data, status, xhr) {
132 error : function (data, status, xhr) {
133 status_col.text("error starting cluster");
133 status_col.text("error starting cluster");
134 }
134 }
135 };
135 };
136 status_col.text('starting');
136 status_col.text('starting');
137 var url = utils.url_join_encode(
137 var url = utils.url_join_encode(
138 that.base_project_url,
138 that.base_url,
139 'clusters',
139 'clusters',
140 that.data.profile,
140 that.data.profile,
141 'start'
141 'start'
142 );
142 );
143 $.ajax(url, settings);
143 $.ajax(url, settings);
144 }
144 }
145 });
145 });
146 };
146 };
147
147
148
148
149 ClusterItem.prototype.state_running = function () {
149 ClusterItem.prototype.state_running = function () {
150 var that = this;
150 var that = this;
151 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
151 var profile_col = $('<div/>').addClass('profile_col span4').text(this.data.profile);
152 var status_col = $('<div/>').addClass('status_col span3').text('running');
152 var status_col = $('<div/>').addClass('status_col span3').text('running');
153 var engines_col = $('<div/>').addClass('engines_col span3').text(this.data.n);
153 var engines_col = $('<div/>').addClass('engines_col span3').text(this.data.n);
154 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
154 var stop_button = $('<button/>').addClass("btn btn-mini").text("Stop");
155 var action_col = $('<div/>').addClass('action_col span2').append(
155 var action_col = $('<div/>').addClass('action_col span2').append(
156 $("<span/>").addClass("item_buttons btn-group").append(
156 $("<span/>").addClass("item_buttons btn-group").append(
157 stop_button
157 stop_button
158 )
158 )
159 );
159 );
160 this.element.empty()
160 this.element.empty()
161 .append(profile_col)
161 .append(profile_col)
162 .append(status_col)
162 .append(status_col)
163 .append(engines_col)
163 .append(engines_col)
164 .append(action_col);
164 .append(action_col);
165 stop_button.click(function (e) {
165 stop_button.click(function (e) {
166 var settings = {
166 var settings = {
167 cache : false,
167 cache : false,
168 type : "POST",
168 type : "POST",
169 dataType : "json",
169 dataType : "json",
170 success : function (data, status, xhr) {
170 success : function (data, status, xhr) {
171 that.update_state(data);
171 that.update_state(data);
172 },
172 },
173 error : function (data, status, xhr) {
173 error : function (data, status, xhr) {
174 console.log('error',data);
174 console.log('error',data);
175 status_col.text("error stopping cluster");
175 status_col.text("error stopping cluster");
176 }
176 }
177 };
177 };
178 status_col.text('stopping');
178 status_col.text('stopping');
179 var url = utils.url_join_encode(
179 var url = utils.url_join_encode(
180 that.base_project_url,
180 that.base_url,
181 'clusters',
181 'clusters',
182 that.data.profile,
182 that.data.profile,
183 'stop'
183 'stop'
184 );
184 );
185 $.ajax(url, settings);
185 $.ajax(url, settings);
186 });
186 });
187 };
187 };
188
188
189
189
190 IPython.ClusterList = ClusterList;
190 IPython.ClusterList = ClusterList;
191 IPython.ClusterItem = ClusterItem;
191 IPython.ClusterItem = ClusterItem;
192
192
193 return IPython;
193 return IPython;
194
194
195 }(IPython));
195 }(IPython));
196
196
@@ -1,89 +1,89
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12
12
13 $(document).ready(function () {
13 $(document).ready(function () {
14
14
15 IPython.page = new IPython.Page();
15 IPython.page = new IPython.Page();
16
16
17 $('#new_notebook').button().click(function (e) {
17 $('#new_notebook').button().click(function (e) {
18 IPython.notebook_list.new_notebook()
18 IPython.notebook_list.new_notebook()
19 });
19 });
20
20
21 var opts = {
21 var opts = {
22 base_project_url : IPython.utils.get_data("baseProjectUrl"),
22 base_url : IPython.utils.get_data("baseUrl"),
23 notebook_path : IPython.utils.get_data("notebookPath"),
23 notebook_path : IPython.utils.get_data("notebookPath"),
24 };
24 };
25 IPython.notebook_list = new IPython.NotebookList('#notebook_list', opts);
25 IPython.notebook_list = new IPython.NotebookList('#notebook_list', opts);
26 IPython.cluster_list = new IPython.ClusterList('#cluster_list', opts);
26 IPython.cluster_list = new IPython.ClusterList('#cluster_list', opts);
27 IPython.login_widget = new IPython.LoginWidget('#login_widget', opts);
27 IPython.login_widget = new IPython.LoginWidget('#login_widget', opts);
28
28
29 var interval_id=0;
29 var interval_id=0;
30 // auto refresh every xx secondes, no need to be fast,
30 // auto refresh every xx secondes, no need to be fast,
31 // update is done at least when page get focus
31 // update is done at least when page get focus
32 var time_refresh = 60; // in sec
32 var time_refresh = 60; // in sec
33
33
34 var enable_autorefresh = function(){
34 var enable_autorefresh = function(){
35 //refresh immediately , then start interval
35 //refresh immediately , then start interval
36 if($('.upload_button').length == 0)
36 if($('.upload_button').length == 0)
37 {
37 {
38 IPython.notebook_list.load_sessions();
38 IPython.notebook_list.load_sessions();
39 IPython.cluster_list.load_list();
39 IPython.cluster_list.load_list();
40 }
40 }
41 if (!interval_id){
41 if (!interval_id){
42 interval_id = setInterval(function(){
42 interval_id = setInterval(function(){
43 if($('.upload_button').length == 0)
43 if($('.upload_button').length == 0)
44 {
44 {
45 IPython.notebook_list.load_sessions();
45 IPython.notebook_list.load_sessions();
46 IPython.cluster_list.load_list();
46 IPython.cluster_list.load_list();
47 }
47 }
48 }, time_refresh*1000);
48 }, time_refresh*1000);
49 }
49 }
50 }
50 }
51
51
52 var disable_autorefresh = function(){
52 var disable_autorefresh = function(){
53 clearInterval(interval_id);
53 clearInterval(interval_id);
54 interval_id = 0;
54 interval_id = 0;
55 }
55 }
56
56
57 // stop autorefresh when page lose focus
57 // stop autorefresh when page lose focus
58 $(window).blur(function() {
58 $(window).blur(function() {
59 disable_autorefresh();
59 disable_autorefresh();
60 })
60 })
61
61
62 //re-enable when page get focus back
62 //re-enable when page get focus back
63 $(window).focus(function() {
63 $(window).focus(function() {
64 enable_autorefresh();
64 enable_autorefresh();
65 });
65 });
66
66
67 // finally start it, it will refresh immediately
67 // finally start it, it will refresh immediately
68 enable_autorefresh();
68 enable_autorefresh();
69
69
70 IPython.page.show();
70 IPython.page.show();
71
71
72 // bound the upload method to the on change of the file select list
72 // bound the upload method to the on change of the file select list
73 $("#alternate_upload").change(function (event){
73 $("#alternate_upload").change(function (event){
74 IPython.notebook_list.handelFilesUpload(event,'form');
74 IPython.notebook_list.handelFilesUpload(event,'form');
75 });
75 });
76
76
77 // set hash on tab click
77 // set hash on tab click
78 $("#tabs").find("a").click(function() {
78 $("#tabs").find("a").click(function() {
79 window.location.hash = $(this).attr("href");
79 window.location.hash = $(this).attr("href");
80 })
80 })
81
81
82 // load tab if url hash
82 // load tab if url hash
83 if (window.location.hash) {
83 if (window.location.hash) {
84 $("#tabs").find("a[href=" + window.location.hash + "]").click();
84 $("#tabs").find("a[href=" + window.location.hash + "]").click();
85 }
85 }
86
86
87
87
88 });
88 });
89
89
@@ -1,431 +1,431
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var NotebookList = function (selector, options) {
17 var NotebookList = function (selector, options) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 this.notebooks_list = [];
24 this.notebooks_list = [];
25 this.sessions = {};
25 this.sessions = {};
26 this.base_project_url = options.base_project_url || utils.get_data("baseProjectUrl");
26 this.base_url = options.base_url || utils.get_data("baseUrl");
27 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
27 this.notebook_path = options.notebook_path || utils.get_data("notebookPath");
28 };
28 };
29
29
30 NotebookList.prototype.style = function () {
30 NotebookList.prototype.style = function () {
31 $('#notebook_toolbar').addClass('list_toolbar');
31 $('#notebook_toolbar').addClass('list_toolbar');
32 $('#drag_info').addClass('toolbar_info');
32 $('#drag_info').addClass('toolbar_info');
33 $('#notebook_buttons').addClass('toolbar_buttons');
33 $('#notebook_buttons').addClass('toolbar_buttons');
34 $('#notebook_list_header').addClass('list_header');
34 $('#notebook_list_header').addClass('list_header');
35 this.element.addClass("list_container");
35 this.element.addClass("list_container");
36 };
36 };
37
37
38
38
39 NotebookList.prototype.bind_events = function () {
39 NotebookList.prototype.bind_events = function () {
40 var that = this;
40 var that = this;
41 $('#refresh_notebook_list').click(function () {
41 $('#refresh_notebook_list').click(function () {
42 that.load_list();
42 that.load_list();
43 });
43 });
44 this.element.bind('dragover', function () {
44 this.element.bind('dragover', function () {
45 return false;
45 return false;
46 });
46 });
47 this.element.bind('drop', function(event){
47 this.element.bind('drop', function(event){
48 that.handelFilesUpload(event,'drop');
48 that.handelFilesUpload(event,'drop');
49 return false;
49 return false;
50 });
50 });
51 };
51 };
52
52
53 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
53 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
54 var that = this;
54 var that = this;
55 var files;
55 var files;
56 if(dropOrForm =='drop'){
56 if(dropOrForm =='drop'){
57 files = event.originalEvent.dataTransfer.files;
57 files = event.originalEvent.dataTransfer.files;
58 } else
58 } else
59 {
59 {
60 files = event.originalEvent.target.files;
60 files = event.originalEvent.target.files;
61 }
61 }
62 for (var i = 0; i < files.length; i++) {
62 for (var i = 0; i < files.length; i++) {
63 var f = files[i];
63 var f = files[i];
64 var reader = new FileReader();
64 var reader = new FileReader();
65 reader.readAsText(f);
65 reader.readAsText(f);
66 var name_and_ext = utils.splitext(f.name);
66 var name_and_ext = utils.splitext(f.name);
67 var file_ext = name_and_ext[1];
67 var file_ext = name_and_ext[1];
68 if (file_ext === '.ipynb') {
68 if (file_ext === '.ipynb') {
69 var item = that.new_notebook_item(0);
69 var item = that.new_notebook_item(0);
70 that.add_name_input(f.name, item);
70 that.add_name_input(f.name, item);
71 // Store the notebook item in the reader so we can use it later
71 // Store the notebook item in the reader so we can use it later
72 // to know which item it belongs to.
72 // to know which item it belongs to.
73 $(reader).data('item', item);
73 $(reader).data('item', item);
74 reader.onload = function (event) {
74 reader.onload = function (event) {
75 var nbitem = $(event.target).data('item');
75 var nbitem = $(event.target).data('item');
76 that.add_notebook_data(event.target.result, nbitem);
76 that.add_notebook_data(event.target.result, nbitem);
77 that.add_upload_button(nbitem);
77 that.add_upload_button(nbitem);
78 };
78 };
79 } else {
79 } else {
80 var dialog = 'Uploaded notebooks must be .ipynb files';
80 var dialog = 'Uploaded notebooks must be .ipynb files';
81 IPython.dialog.modal({
81 IPython.dialog.modal({
82 title : 'Invalid file type',
82 title : 'Invalid file type',
83 body : dialog,
83 body : dialog,
84 buttons : {'OK' : {'class' : 'btn-primary'}}
84 buttons : {'OK' : {'class' : 'btn-primary'}}
85 });
85 });
86 }
86 }
87 }
87 }
88 // Replace the file input form wth a clone of itself. This is required to
88 // Replace the file input form wth a clone of itself. This is required to
89 // reset the form. Otherwise, if you upload a file, delete it and try to
89 // reset the form. Otherwise, if you upload a file, delete it and try to
90 // upload it again, the changed event won't fire.
90 // upload it again, the changed event won't fire.
91 var form = $('input.fileinput');
91 var form = $('input.fileinput');
92 form.replaceWith(form.clone(true));
92 form.replaceWith(form.clone(true));
93 return false;
93 return false;
94 };
94 };
95
95
96 NotebookList.prototype.clear_list = function () {
96 NotebookList.prototype.clear_list = function () {
97 this.element.children('.list_item').remove();
97 this.element.children('.list_item').remove();
98 };
98 };
99
99
100 NotebookList.prototype.load_sessions = function(){
100 NotebookList.prototype.load_sessions = function(){
101 var that = this;
101 var that = this;
102 var settings = {
102 var settings = {
103 processData : false,
103 processData : false,
104 cache : false,
104 cache : false,
105 type : "GET",
105 type : "GET",
106 dataType : "json",
106 dataType : "json",
107 success : $.proxy(that.sessions_loaded, this)
107 success : $.proxy(that.sessions_loaded, this)
108 };
108 };
109 var url = utils.url_join_encode(this.base_project_url, 'api/sessions');
109 var url = utils.url_join_encode(this.base_url, 'api/sessions');
110 $.ajax(url,settings);
110 $.ajax(url,settings);
111 };
111 };
112
112
113
113
114 NotebookList.prototype.sessions_loaded = function(data){
114 NotebookList.prototype.sessions_loaded = function(data){
115 this.sessions = {};
115 this.sessions = {};
116 var len = data.length;
116 var len = data.length;
117 if (len > 0) {
117 if (len > 0) {
118 for (var i=0; i<len; i++) {
118 for (var i=0; i<len; i++) {
119 var nb_path;
119 var nb_path;
120 if (!data[i].notebook.path) {
120 if (!data[i].notebook.path) {
121 nb_path = data[i].notebook.name;
121 nb_path = data[i].notebook.name;
122 }
122 }
123 else {
123 else {
124 nb_path = utils.url_path_join(
124 nb_path = utils.url_path_join(
125 data[i].notebook.path,
125 data[i].notebook.path,
126 data[i].notebook.name
126 data[i].notebook.name
127 );
127 );
128 }
128 }
129 this.sessions[nb_path] = data[i].id;
129 this.sessions[nb_path] = data[i].id;
130 }
130 }
131 }
131 }
132 this.load_list();
132 this.load_list();
133 };
133 };
134
134
135 NotebookList.prototype.load_list = function () {
135 NotebookList.prototype.load_list = function () {
136 var that = this;
136 var that = this;
137 var settings = {
137 var settings = {
138 processData : false,
138 processData : false,
139 cache : false,
139 cache : false,
140 type : "GET",
140 type : "GET",
141 dataType : "json",
141 dataType : "json",
142 success : $.proxy(this.list_loaded, this),
142 success : $.proxy(this.list_loaded, this),
143 error : $.proxy( function(){
143 error : $.proxy( function(){
144 that.list_loaded([], null, null, {msg:"Error connecting to server."});
144 that.list_loaded([], null, null, {msg:"Error connecting to server."});
145 },this)
145 },this)
146 };
146 };
147
147
148 var url = utils.url_join_encode(
148 var url = utils.url_join_encode(
149 this.base_project_url,
149 this.base_url,
150 'api',
150 'api',
151 'notebooks',
151 'notebooks',
152 this.notebook_path
152 this.notebook_path
153 );
153 );
154 $.ajax(url, settings);
154 $.ajax(url, settings);
155 };
155 };
156
156
157
157
158 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
158 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
159 var message = 'Notebook list empty.';
159 var message = 'Notebook list empty.';
160 if (param !== undefined && param.msg) {
160 if (param !== undefined && param.msg) {
161 message = param.msg;
161 message = param.msg;
162 }
162 }
163 var item = null;
163 var item = null;
164 var len = data.length;
164 var len = data.length;
165 this.clear_list();
165 this.clear_list();
166 if (len === 0) {
166 if (len === 0) {
167 item = this.new_notebook_item(0);
167 item = this.new_notebook_item(0);
168 var span12 = item.children().first();
168 var span12 = item.children().first();
169 span12.empty();
169 span12.empty();
170 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
170 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
171 }
171 }
172 var path = this.notebook_path;
172 var path = this.notebook_path;
173 var offset = 0;
173 var offset = 0;
174 if (path !== '') {
174 if (path !== '') {
175 item = this.new_notebook_item(0);
175 item = this.new_notebook_item(0);
176 this.add_dir(path, '..', item);
176 this.add_dir(path, '..', item);
177 offset = 1;
177 offset = 1;
178 }
178 }
179 for (var i=0; i<len; i++) {
179 for (var i=0; i<len; i++) {
180 if (data[i].type === 'directory') {
180 if (data[i].type === 'directory') {
181 var name = data[i].name;
181 var name = data[i].name;
182 item = this.new_notebook_item(i+offset);
182 item = this.new_notebook_item(i+offset);
183 this.add_dir(path, name, item);
183 this.add_dir(path, name, item);
184 } else {
184 } else {
185 var name = data[i].name;
185 var name = data[i].name;
186 item = this.new_notebook_item(i+offset);
186 item = this.new_notebook_item(i+offset);
187 this.add_link(path, name, item);
187 this.add_link(path, name, item);
188 name = utils.url_path_join(path, name);
188 name = utils.url_path_join(path, name);
189 if(this.sessions[name] === undefined){
189 if(this.sessions[name] === undefined){
190 this.add_delete_button(item);
190 this.add_delete_button(item);
191 } else {
191 } else {
192 this.add_shutdown_button(item,this.sessions[name]);
192 this.add_shutdown_button(item,this.sessions[name]);
193 }
193 }
194 }
194 }
195 }
195 }
196 };
196 };
197
197
198
198
199 NotebookList.prototype.new_notebook_item = function (index) {
199 NotebookList.prototype.new_notebook_item = function (index) {
200 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
200 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
201 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
201 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
202 // item.css('border-top-style','none');
202 // item.css('border-top-style','none');
203 item.append($("<div/>").addClass("span12").append(
203 item.append($("<div/>").addClass("span12").append(
204 $('<i/>').addClass('item_icon')
204 $('<i/>').addClass('item_icon')
205 ).append(
205 ).append(
206 $("<a/>").addClass("item_link").append(
206 $("<a/>").addClass("item_link").append(
207 $("<span/>").addClass("item_name")
207 $("<span/>").addClass("item_name")
208 )
208 )
209 ).append(
209 ).append(
210 $('<div/>').addClass("item_buttons btn-group pull-right")
210 $('<div/>').addClass("item_buttons btn-group pull-right")
211 ));
211 ));
212
212
213 if (index === -1) {
213 if (index === -1) {
214 this.element.append(item);
214 this.element.append(item);
215 } else {
215 } else {
216 this.element.children().eq(index).after(item);
216 this.element.children().eq(index).after(item);
217 }
217 }
218 return item;
218 return item;
219 };
219 };
220
220
221
221
222 NotebookList.prototype.add_dir = function (path, name, item) {
222 NotebookList.prototype.add_dir = function (path, name, item) {
223 item.data('name', name);
223 item.data('name', name);
224 item.data('path', path);
224 item.data('path', path);
225 item.find(".item_name").text(name);
225 item.find(".item_name").text(name);
226 item.find(".item_icon").addClass('icon-folder-open');
226 item.find(".item_icon").addClass('icon-folder-open');
227 item.find("a.item_link")
227 item.find("a.item_link")
228 .attr('href',
228 .attr('href',
229 utils.url_join_encode(
229 utils.url_join_encode(
230 this.base_project_url,
230 this.base_url,
231 "tree",
231 "tree",
232 path,
232 path,
233 name
233 name
234 )
234 )
235 );
235 );
236 };
236 };
237
237
238
238
239 NotebookList.prototype.add_link = function (path, nbname, item) {
239 NotebookList.prototype.add_link = function (path, nbname, item) {
240 item.data('nbname', nbname);
240 item.data('nbname', nbname);
241 item.data('path', path);
241 item.data('path', path);
242 item.find(".item_name").text(nbname);
242 item.find(".item_name").text(nbname);
243 item.find(".item_icon").addClass('icon-book');
243 item.find(".item_icon").addClass('icon-book');
244 item.find("a.item_link")
244 item.find("a.item_link")
245 .attr('href',
245 .attr('href',
246 utils.url_join_encode(
246 utils.url_join_encode(
247 this.base_project_url,
247 this.base_url,
248 "notebooks",
248 "notebooks",
249 path,
249 path,
250 nbname
250 nbname
251 )
251 )
252 ).attr('target','_blank');
252 ).attr('target','_blank');
253 };
253 };
254
254
255
255
256 NotebookList.prototype.add_name_input = function (nbname, item) {
256 NotebookList.prototype.add_name_input = function (nbname, item) {
257 item.data('nbname', nbname);
257 item.data('nbname', nbname);
258 item.find(".item_icon").addClass('icon-book');
258 item.find(".item_icon").addClass('icon-book');
259 item.find(".item_name").empty().append(
259 item.find(".item_name").empty().append(
260 $('<input/>')
260 $('<input/>')
261 .addClass("nbname_input")
261 .addClass("nbname_input")
262 .attr('value', utils.splitext(nbname)[0])
262 .attr('value', utils.splitext(nbname)[0])
263 .attr('size', '30')
263 .attr('size', '30')
264 .attr('type', 'text')
264 .attr('type', 'text')
265 );
265 );
266 };
266 };
267
267
268
268
269 NotebookList.prototype.add_notebook_data = function (data, item) {
269 NotebookList.prototype.add_notebook_data = function (data, item) {
270 item.data('nbdata', data);
270 item.data('nbdata', data);
271 };
271 };
272
272
273
273
274 NotebookList.prototype.add_shutdown_button = function (item, session) {
274 NotebookList.prototype.add_shutdown_button = function (item, session) {
275 var that = this;
275 var that = this;
276 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
276 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
277 click(function (e) {
277 click(function (e) {
278 var settings = {
278 var settings = {
279 processData : false,
279 processData : false,
280 cache : false,
280 cache : false,
281 type : "DELETE",
281 type : "DELETE",
282 dataType : "json",
282 dataType : "json",
283 success : function () {
283 success : function () {
284 that.load_sessions();
284 that.load_sessions();
285 }
285 }
286 };
286 };
287 var url = utils.url_join_encode(
287 var url = utils.url_join_encode(
288 that.base_project_url,
288 that.base_url,
289 'api/sessions',
289 'api/sessions',
290 session
290 session
291 );
291 );
292 $.ajax(url, settings);
292 $.ajax(url, settings);
293 return false;
293 return false;
294 });
294 });
295 // var new_buttons = item.find('a'); // shutdown_button;
295 // var new_buttons = item.find('a'); // shutdown_button;
296 item.find(".item_buttons").text("").append(shutdown_button);
296 item.find(".item_buttons").text("").append(shutdown_button);
297 };
297 };
298
298
299 NotebookList.prototype.add_delete_button = function (item) {
299 NotebookList.prototype.add_delete_button = function (item) {
300 var new_buttons = $('<span/>').addClass("btn-group pull-right");
300 var new_buttons = $('<span/>').addClass("btn-group pull-right");
301 var notebooklist = this;
301 var notebooklist = this;
302 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
302 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
303 click(function (e) {
303 click(function (e) {
304 // $(this) is the button that was clicked.
304 // $(this) is the button that was clicked.
305 var that = $(this);
305 var that = $(this);
306 // We use the nbname and notebook_id from the parent notebook_item element's
306 // We use the nbname and notebook_id from the parent notebook_item element's
307 // data because the outer scopes values change as we iterate through the loop.
307 // data because the outer scopes values change as we iterate through the loop.
308 var parent_item = that.parents('div.list_item');
308 var parent_item = that.parents('div.list_item');
309 var nbname = parent_item.data('nbname');
309 var nbname = parent_item.data('nbname');
310 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
310 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
311 IPython.dialog.modal({
311 IPython.dialog.modal({
312 title : "Delete notebook",
312 title : "Delete notebook",
313 body : message,
313 body : message,
314 buttons : {
314 buttons : {
315 Delete : {
315 Delete : {
316 class: "btn-danger",
316 class: "btn-danger",
317 click: function() {
317 click: function() {
318 var settings = {
318 var settings = {
319 processData : false,
319 processData : false,
320 cache : false,
320 cache : false,
321 type : "DELETE",
321 type : "DELETE",
322 dataType : "json",
322 dataType : "json",
323 success : function (data, status, xhr) {
323 success : function (data, status, xhr) {
324 parent_item.remove();
324 parent_item.remove();
325 }
325 }
326 };
326 };
327 var url = utils.url_join_encode(
327 var url = utils.url_join_encode(
328 notebooklist.base_project_url,
328 notebooklist.base_url,
329 'api/notebooks',
329 'api/notebooks',
330 notebooklist.notebook_path,
330 notebooklist.notebook_path,
331 nbname
331 nbname
332 );
332 );
333 $.ajax(url, settings);
333 $.ajax(url, settings);
334 }
334 }
335 },
335 },
336 Cancel : {}
336 Cancel : {}
337 }
337 }
338 });
338 });
339 return false;
339 return false;
340 });
340 });
341 item.find(".item_buttons").text("").append(delete_button);
341 item.find(".item_buttons").text("").append(delete_button);
342 };
342 };
343
343
344
344
345 NotebookList.prototype.add_upload_button = function (item) {
345 NotebookList.prototype.add_upload_button = function (item) {
346 var that = this;
346 var that = this;
347 var upload_button = $('<button/>').text("Upload")
347 var upload_button = $('<button/>').text("Upload")
348 .addClass('btn btn-primary btn-mini upload_button')
348 .addClass('btn btn-primary btn-mini upload_button')
349 .click(function (e) {
349 .click(function (e) {
350 var nbname = item.find('.item_name > input').val();
350 var nbname = item.find('.item_name > input').val();
351 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
351 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
352 nbname = nbname + ".ipynb";
352 nbname = nbname + ".ipynb";
353 }
353 }
354 var path = that.notebook_path;
354 var path = that.notebook_path;
355 var nbdata = item.data('nbdata');
355 var nbdata = item.data('nbdata');
356 var content_type = 'application/json';
356 var content_type = 'application/json';
357 var model = {
357 var model = {
358 content : JSON.parse(nbdata),
358 content : JSON.parse(nbdata),
359 };
359 };
360 var settings = {
360 var settings = {
361 processData : false,
361 processData : false,
362 cache : false,
362 cache : false,
363 type : 'PUT',
363 type : 'PUT',
364 dataType : 'json',
364 dataType : 'json',
365 data : JSON.stringify(model),
365 data : JSON.stringify(model),
366 headers : {'Content-Type': content_type},
366 headers : {'Content-Type': content_type},
367 success : function (data, status, xhr) {
367 success : function (data, status, xhr) {
368 that.add_link(path, nbname, item);
368 that.add_link(path, nbname, item);
369 that.add_delete_button(item);
369 that.add_delete_button(item);
370 },
370 },
371 error : function (data, status, xhr) {
371 error : function (data, status, xhr) {
372 console.log(data, status);
372 console.log(data, status);
373 }
373 }
374 };
374 };
375
375
376 var url = utils.url_join_encode(
376 var url = utils.url_join_encode(
377 that.base_project_url,
377 that.base_url,
378 'api/notebooks',
378 'api/notebooks',
379 that.notebook_path,
379 that.notebook_path,
380 nbname
380 nbname
381 );
381 );
382 $.ajax(url, settings);
382 $.ajax(url, settings);
383 return false;
383 return false;
384 });
384 });
385 var cancel_button = $('<button/>').text("Cancel")
385 var cancel_button = $('<button/>').text("Cancel")
386 .addClass("btn btn-mini")
386 .addClass("btn btn-mini")
387 .click(function (e) {
387 .click(function (e) {
388 console.log('cancel click');
388 console.log('cancel click');
389 item.remove();
389 item.remove();
390 return false;
390 return false;
391 });
391 });
392 item.find(".item_buttons").empty()
392 item.find(".item_buttons").empty()
393 .append(upload_button)
393 .append(upload_button)
394 .append(cancel_button);
394 .append(cancel_button);
395 };
395 };
396
396
397
397
398 NotebookList.prototype.new_notebook = function(){
398 NotebookList.prototype.new_notebook = function(){
399 var path = this.notebook_path;
399 var path = this.notebook_path;
400 var base_project_url = this.base_project_url;
400 var base_url = this.base_url;
401 var settings = {
401 var settings = {
402 processData : false,
402 processData : false,
403 cache : false,
403 cache : false,
404 type : "POST",
404 type : "POST",
405 dataType : "json",
405 dataType : "json",
406 async : false,
406 async : false,
407 success : function (data, status, xhr) {
407 success : function (data, status, xhr) {
408 var notebook_name = data.name;
408 var notebook_name = data.name;
409 window.open(
409 window.open(
410 utils.url_join_encode(
410 utils.url_join_encode(
411 base_project_url,
411 base_url,
412 'notebooks',
412 'notebooks',
413 path,
413 path,
414 notebook_name),
414 notebook_name),
415 '_blank'
415 '_blank'
416 );
416 );
417 }
417 }
418 };
418 };
419 var url = utils.url_join_encode(
419 var url = utils.url_join_encode(
420 base_project_url,
420 base_url,
421 'api/notebooks',
421 'api/notebooks',
422 path
422 path
423 );
423 );
424 $.ajax(url, settings);
424 $.ajax(url, settings);
425 };
425 };
426
426
427 IPython.NotebookList = NotebookList;
427 IPython.NotebookList = NotebookList;
428
428
429 return IPython;
429 return IPython;
430
430
431 }(IPython));
431 }(IPython));
@@ -1,52 +1,52
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3
3
4 {% block stylesheet %}
4 {% block stylesheet %}
5 {{super()}}
5 {{super()}}
6 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
6 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block login_widget %}
9 {% block login_widget %}
10 {% endblock %}
10 {% endblock %}
11
11
12 {% block site %}
12 {% block site %}
13
13
14 <div id="ipython-main-app" class="container">
14 <div id="ipython-main-app" class="container">
15
15
16 {% if login_available %}
16 {% if login_available %}
17 <div class="row">
17 <div class="row">
18 <div class="navbar span8 offset2">
18 <div class="navbar span8 offset2">
19 <div class="navbar-inner">
19 <div class="navbar-inner">
20 <div class="container">
20 <div class="container">
21 <div class="center-nav">
21 <div class="center-nav">
22 <p class="navbar-text nav">Password:</p>
22 <p class="navbar-text nav">Password:</p>
23 <form action="{{base_project_url}}login?next={{next}}" method="post" class="navbar-form pull-left">
23 <form action="{{base_url}}login?next={{next}}" method="post" class="navbar-form pull-left">
24 <input type="password" name="password" id="password_input">
24 <input type="password" name="password" id="password_input">
25 <button type="submit" id="login_submit">Log in</button>
25 <button type="submit" id="login_submit">Log in</button>
26 </form>
26 </form>
27 </div>
27 </div>
28 </div>
28 </div>
29 </div>
29 </div>
30 </div>
30 </div>
31 </div>
31 </div>
32 {% endif %}
32 {% endif %}
33 {% if message %}
33 {% if message %}
34 <div class="row">
34 <div class="row">
35 {% for key in message %}
35 {% for key in message %}
36 <div class="message {{key}}">
36 <div class="message {{key}}">
37 {{message[key]}}
37 {{message[key]}}
38 </div>
38 </div>
39 {% endfor %}
39 {% endfor %}
40 </div>
40 </div>
41 {% endif %}
41 {% endif %}
42
42
43 <div/>
43 <div/>
44
44
45 {% endblock %}
45 {% endblock %}
46
46
47
47
48 {% block script %}
48 {% block script %}
49
49
50 <script src="{{static_url("auth/js/loginmain.js") }}" type="text/javascript" charset="utf-8"></script>
50 <script src="{{static_url("auth/js/loginmain.js") }}" type="text/javascript" charset="utf-8"></script>
51
51
52 {% endblock %}
52 {% endblock %}
@@ -1,38 +1,38
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4 {{super()}}
4 {{super()}}
5 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
5 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
6 {% endblock %}
6 {% endblock %}
7
7
8 {% block login_widget %}
8 {% block login_widget %}
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block site %}
11 {% block site %}
12
12
13 <div id="ipython-main-app" class="container">
13 <div id="ipython-main-app" class="container">
14
14
15 {% if message %}
15 {% if message %}
16 {% for key in message %}
16 {% for key in message %}
17 <div class="message {{key}}">
17 <div class="message {{key}}">
18 {{message[key]}}
18 {{message[key]}}
19 </div>
19 </div>
20 {% endfor %}
20 {% endfor %}
21 {% endif %}
21 {% endif %}
22
22
23 {% if not login_available %}
23 {% if not login_available %}
24 Proceed to the <a href="{{base_project_url}}">dashboard</a>.
24 Proceed to the <a href="{{base_url}}">dashboard</a>.
25 {% else %}
25 {% else %}
26 Proceed to the <a href="{{base_project_url}}login">login page</a>.
26 Proceed to the <a href="{{base_url}}login">login page</a>.
27 {% endif %}
27 {% endif %}
28
28
29
29
30 <div/>
30 <div/>
31
31
32 {% endblock %}
32 {% endblock %}
33
33
34 {% block script %}
34 {% block script %}
35
35
36 <script src="{{static_url("auth/js/logoutmain.js") }}" type="text/javascript" charset="utf-8"></script>
36 <script src="{{static_url("auth/js/logoutmain.js") }}" type="text/javascript" charset="utf-8"></script>
37
37
38 {% endblock %}
38 {% endblock %}
@@ -1,352 +1,352
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project="{{project}}"
24 data-project="{{project}}"
25 data-base-project-url="{{base_project_url}}"
25 data-base-url="{{base_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
27 data-notebook-name="{{notebook_name}}"
27 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
28 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
29 class="notebook_app"
30
30
31 {% endblock %}
31 {% endblock %}
32
32
33
33
34 {% block header %}
34 {% block header %}
35
35
36 <span id="save_widget" class="nav pull-left">
36 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
37 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
38 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
39 <span id="autosave_status"></span>
40 </span>
40 </span>
41
41
42 {% endblock %}
42 {% endblock %}
43
43
44
44
45 {% block site %}
45 {% block site %}
46
46
47 <div id="menubar-container" class="container">
47 <div id="menubar-container" class="container">
48 <div id="menubar">
48 <div id="menubar">
49 <div class="navbar">
49 <div class="navbar">
50 <div class="navbar-inner">
50 <div class="navbar-inner">
51 <div class="container">
51 <div class="container">
52 <ul id="menus" class="nav">
52 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul id="file_menu" class="dropdown-menu">
54 <ul id="file_menu" class="dropdown-menu">
55 <li id="new_notebook"
55 <li id="new_notebook"
56 title="Make a new notebook (Opens a new window)">
56 title="Make a new notebook (Opens a new window)">
57 <a href="#">New</a></li>
57 <a href="#">New</a></li>
58 <li id="open_notebook"
58 <li id="open_notebook"
59 title="Opens a new window with the Dashboard view">
59 title="Opens a new window with the Dashboard view">
60 <a href="#">Open...</a></li>
60 <a href="#">Open...</a></li>
61 <!-- <hr/> -->
61 <!-- <hr/> -->
62 <li class="divider"></li>
62 <li class="divider"></li>
63 <li id="copy_notebook"
63 <li id="copy_notebook"
64 title="Open a copy of this notebook's contents and start a new kernel">
64 title="Open a copy of this notebook's contents and start a new kernel">
65 <a href="#">Make a Copy...</a></li>
65 <a href="#">Make a Copy...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 <!-- <hr/> -->
68 <!-- <hr/> -->
69 <li class="divider"></li>
69 <li class="divider"></li>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 <ul class="dropdown-menu">
71 <ul class="dropdown-menu">
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <li class="divider"></li>
79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
82 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
87 </ul>
87 </ul>
88 </li>
88 </li>
89 <li class="divider"></li>
89 <li class="divider"></li>
90
90
91 <li id="kill_and_exit"
91 <li id="kill_and_exit"
92 title="Shutdown this notebook's kernel, and close this window">
92 title="Shutdown this notebook's kernel, and close this window">
93 <a href="#" >Close and halt</a></li>
93 <a href="#" >Close and halt</a></li>
94 </ul>
94 </ul>
95 </li>
95 </li>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
97 <ul id="edit_menu" class="dropdown-menu">
97 <ul id="edit_menu" class="dropdown-menu">
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
105 <li class="divider"></li>
105 <li class="divider"></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
109 <li class="divider"></li>
109 <li class="divider"></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
112 <li class="divider"></li>
112 <li class="divider"></li>
113 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
113 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
114 </ul>
114 </ul>
115 </li>
115 </li>
116 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
116 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
117 <ul id="view_menu" class="dropdown-menu">
117 <ul id="view_menu" class="dropdown-menu">
118 <li id="toggle_header"
118 <li id="toggle_header"
119 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
119 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
120 <a href="#">Toggle Header</a></li>
120 <a href="#">Toggle Header</a></li>
121 <li id="toggle_toolbar"
121 <li id="toggle_toolbar"
122 title="Show/Hide the action icons (below menu bar)">
122 title="Show/Hide the action icons (below menu bar)">
123 <a href="#">Toggle Toolbar</a></li>
123 <a href="#">Toggle Toolbar</a></li>
124 </ul>
124 </ul>
125 </li>
125 </li>
126 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
126 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
127 <ul id="insert_menu" class="dropdown-menu">
127 <ul id="insert_menu" class="dropdown-menu">
128 <li id="insert_cell_above"
128 <li id="insert_cell_above"
129 title="Insert an empty Code cell above the currently active cell">
129 title="Insert an empty Code cell above the currently active cell">
130 <a href="#">Insert Cell Above</a></li>
130 <a href="#">Insert Cell Above</a></li>
131 <li id="insert_cell_below"
131 <li id="insert_cell_below"
132 title="Insert an empty Code cell below the currently active cell">
132 title="Insert an empty Code cell below the currently active cell">
133 <a href="#">Insert Cell Below</a></li>
133 <a href="#">Insert Cell Below</a></li>
134 </ul>
134 </ul>
135 </li>
135 </li>
136 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
136 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
137 <ul id="cell_menu" class="dropdown-menu">
137 <ul id="cell_menu" class="dropdown-menu">
138 <li id="run_cell" title="Run this cell, and move cursor to the next one">
138 <li id="run_cell" title="Run this cell, and move cursor to the next one">
139 <a href="#">Run</a></li>
139 <a href="#">Run</a></li>
140 <li id="run_cell_select_below" title="Run this cell, select below">
140 <li id="run_cell_select_below" title="Run this cell, select below">
141 <a href="#">Run and Select Below</a></li>
141 <a href="#">Run and Select Below</a></li>
142 <li id="run_cell_insert_below" title="Run this cell, insert below">
142 <li id="run_cell_insert_below" title="Run this cell, insert below">
143 <a href="#">Run and Insert Below</a></li>
143 <a href="#">Run and Insert Below</a></li>
144 <li id="run_all_cells" title="Run all cells in the notebook">
144 <li id="run_all_cells" title="Run all cells in the notebook">
145 <a href="#">Run All</a></li>
145 <a href="#">Run All</a></li>
146 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
146 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
147 <a href="#">Run All Above</a></li>
147 <a href="#">Run All Above</a></li>
148 <li id="run_all_cells_below" title="Run this cell and all cells below it">
148 <li id="run_all_cells_below" title="Run this cell and all cells below it">
149 <a href="#">Run All Below</a></li>
149 <a href="#">Run All Below</a></li>
150 <li class="divider"></li>
150 <li class="divider"></li>
151 <li id="change_cell_type" class="dropdown-submenu"
151 <li id="change_cell_type" class="dropdown-submenu"
152 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
152 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
153 <a href="#">Cell Type</a>
153 <a href="#">Cell Type</a>
154 <ul class="dropdown-menu">
154 <ul class="dropdown-menu">
155 <li id="to_code"
155 <li id="to_code"
156 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
156 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
157 <a href="#">Code</a></li>
157 <a href="#">Code</a></li>
158 <li id="to_markdown"
158 <li id="to_markdown"
159 title="Contents will be rendered as HTML and serve as explanatory text">
159 title="Contents will be rendered as HTML and serve as explanatory text">
160 <a href="#">Markdown</a></li>
160 <a href="#">Markdown</a></li>
161 <li id="to_raw"
161 <li id="to_raw"
162 title="Contents will pass through nbconvert unmodified">
162 title="Contents will pass through nbconvert unmodified">
163 <a href="#">Raw NBConvert</a></li>
163 <a href="#">Raw NBConvert</a></li>
164 <li id="to_heading1"><a href="#">Heading 1</a></li>
164 <li id="to_heading1"><a href="#">Heading 1</a></li>
165 <li id="to_heading2"><a href="#">Heading 2</a></li>
165 <li id="to_heading2"><a href="#">Heading 2</a></li>
166 <li id="to_heading3"><a href="#">Heading 3</a></li>
166 <li id="to_heading3"><a href="#">Heading 3</a></li>
167 <li id="to_heading4"><a href="#">Heading 4</a></li>
167 <li id="to_heading4"><a href="#">Heading 4</a></li>
168 <li id="to_heading5"><a href="#">Heading 5</a></li>
168 <li id="to_heading5"><a href="#">Heading 5</a></li>
169 <li id="to_heading6"><a href="#">Heading 6</a></li>
169 <li id="to_heading6"><a href="#">Heading 6</a></li>
170 </ul>
170 </ul>
171 </li>
171 </li>
172 <li class="divider"></li>
172 <li class="divider"></li>
173 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
173 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
174 <ul class="dropdown-menu">
174 <ul class="dropdown-menu">
175 <li id="toggle_current_output"
175 <li id="toggle_current_output"
176 title="Hide/Show the output of the current cell">
176 title="Hide/Show the output of the current cell">
177 <a href="#">Toggle</a>
177 <a href="#">Toggle</a>
178 </li>
178 </li>
179 <li id="toggle_current_output_scroll"
179 <li id="toggle_current_output_scroll"
180 title="Scroll the output of the current cell">
180 title="Scroll the output of the current cell">
181 <a href="#">Toggle Scrolling</a>
181 <a href="#">Toggle Scrolling</a>
182 </li>
182 </li>
183 <li id="clear_current_output"
183 <li id="clear_current_output"
184 title="Clear the output of the current cell">
184 title="Clear the output of the current cell">
185 <a href="#">Clear</a>
185 <a href="#">Clear</a>
186 </li>
186 </li>
187 </ul>
187 </ul>
188 </li>
188 </li>
189 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
189 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
190 <ul class="dropdown-menu">
190 <ul class="dropdown-menu">
191 <li id="toggle_all_output"
191 <li id="toggle_all_output"
192 title="Hide/Show the output of all cells">
192 title="Hide/Show the output of all cells">
193 <a href="#">Toggle</a>
193 <a href="#">Toggle</a>
194 </li>
194 </li>
195 <li id="toggle_all_output_scroll"
195 <li id="toggle_all_output_scroll"
196 title="Scroll the output of all cells">
196 title="Scroll the output of all cells">
197 <a href="#">Toggle Scrolling</a>
197 <a href="#">Toggle Scrolling</a>
198 </li>
198 </li>
199 <li id="clear_all_output"
199 <li id="clear_all_output"
200 title="Clear the output of all cells">
200 title="Clear the output of all cells">
201 <a href="#">Clear</a>
201 <a href="#">Clear</a>
202 </li>
202 </li>
203 </ul>
203 </ul>
204 </li>
204 </li>
205 </ul>
205 </ul>
206 </li>
206 </li>
207 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
207 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
208 <ul id="kernel_menu" class="dropdown-menu">
208 <ul id="kernel_menu" class="dropdown-menu">
209 <li id="int_kernel"
209 <li id="int_kernel"
210 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
210 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
211 <a href="#">Interrupt</a></li>
211 <a href="#">Interrupt</a></li>
212 <li id="restart_kernel"
212 <li id="restart_kernel"
213 title="Restart the Kernel">
213 title="Restart the Kernel">
214 <a href="#">Restart</a></li>
214 <a href="#">Restart</a></li>
215 </ul>
215 </ul>
216 </li>
216 </li>
217 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
217 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
218 <ul id="help_menu" class="dropdown-menu">
218 <ul id="help_menu" class="dropdown-menu">
219 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
219 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
220 <li class="divider"></li>
220 <li class="divider"></li>
221 {% set
221 {% set
222 sections = (
222 sections = (
223 (
223 (
224 ("http://ipython.org/documentation.html","IPython Help",True),
224 ("http://ipython.org/documentation.html","IPython Help",True),
225 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
225 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
226 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
226 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
227 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
227 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
228 ),(
228 ),(
229 ("http://docs.python.org","Python",True),
229 ("http://docs.python.org","Python",True),
230 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
230 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
231 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
231 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
232 ("http://matplotlib.org/contents.html","Matplotlib",True),
232 ("http://matplotlib.org/contents.html","Matplotlib",True),
233 ("http://docs.sympy.org/dev/index.html","SymPy",True),
233 ("http://docs.sympy.org/dev/index.html","SymPy",True),
234 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
234 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
235 )
235 )
236 )
236 )
237 %}
237 %}
238
238
239 {% for helplinks in sections %}
239 {% for helplinks in sections %}
240 {% for link in helplinks %}
240 {% for link in helplinks %}
241 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
241 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
242 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
242 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
243 {{link[1]}}
243 {{link[1]}}
244 </a></li>
244 </a></li>
245 {% endfor %}
245 {% endfor %}
246 {% if not loop.last %}
246 {% if not loop.last %}
247 <li class="divider"></li>
247 <li class="divider"></li>
248 {% endif %}
248 {% endif %}
249 {% endfor %}
249 {% endfor %}
250 </li>
250 </li>
251 </ul>
251 </ul>
252 </li>
252 </li>
253 </ul>
253 </ul>
254 <div id="kernel_indicator" class="indicator_area pull-right">
254 <div id="kernel_indicator" class="indicator_area pull-right">
255 <i id="kernel_indicator_icon"></i>
255 <i id="kernel_indicator_icon"></i>
256 </div>
256 </div>
257 <div id="modal_indicator" class="indicator_area pull-right">
257 <div id="modal_indicator" class="indicator_area pull-right">
258 <i id="modal_indicator_icon"></i>
258 <i id="modal_indicator_icon"></i>
259 </div>
259 </div>
260 <div id="notification_area"></div>
260 <div id="notification_area"></div>
261 </div>
261 </div>
262 </div>
262 </div>
263 </div>
263 </div>
264 </div>
264 </div>
265 <div id="maintoolbar" class="navbar">
265 <div id="maintoolbar" class="navbar">
266 <div class="toolbar-inner navbar-inner navbar-nobg">
266 <div class="toolbar-inner navbar-inner navbar-nobg">
267 <div id="maintoolbar-container" class="container"></div>
267 <div id="maintoolbar-container" class="container"></div>
268 </div>
268 </div>
269 </div>
269 </div>
270 </div>
270 </div>
271
271
272 <div id="ipython-main-app">
272 <div id="ipython-main-app">
273
273
274 <div id="notebook_panel">
274 <div id="notebook_panel">
275 <div id="notebook"></div>
275 <div id="notebook"></div>
276 <div id="pager_splitter"></div>
276 <div id="pager_splitter"></div>
277 <div id="pager">
277 <div id="pager">
278 <div id='pager_button_area'>
278 <div id='pager_button_area'>
279 </div>
279 </div>
280 <div id="pager-container" class="container"></div>
280 <div id="pager-container" class="container"></div>
281 </div>
281 </div>
282 </div>
282 </div>
283
283
284 </div>
284 </div>
285 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
285 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
286
286
287
287
288 {% endblock %}
288 {% endblock %}
289
289
290
290
291 {% block script %}
291 {% block script %}
292
292
293 {{super()}}
293 {{super()}}
294
294
295 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
295 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
296 <script type="text/javascript">
296 <script type="text/javascript">
297 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
297 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
298 </script>
298 </script>
299 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
313
313
314 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
315
315
316 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
317
317
318 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
318 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
345
345
346 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
346 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
347
347
348 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
348 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
351
351
352 {% endblock %}
352 {% endblock %}
@@ -1,96 +1,96
1
1
2
2
3 <!DOCTYPE HTML>
3 <!DOCTYPE HTML>
4 <html>
4 <html>
5
5
6 <head>
6 <head>
7 <meta charset="utf-8">
7 <meta charset="utf-8">
8
8
9 <title>{% block title %}IPython Notebook{% endblock %}</title>
9 <title>{% block title %}IPython Notebook{% endblock %}</title>
10 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
10 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
11 <meta http-equiv="X-UA-Compatible" content="chrome=1">
11 <meta http-equiv="X-UA-Compatible" content="chrome=1">
12 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
12 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
13 <meta name="viewport" content="width=device-width, initial-scale=1.0">
13 <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
14
15 {% block stylesheet %}
15 {% block stylesheet %}
16 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
16 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
17 {% endblock %}
17 {% endblock %}
18 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
19 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
19 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
20 <script>
20 <script>
21 require.config({
21 require.config({
22 baseUrl: '{{static_url("")}}',
22 baseUrl: '{{static_url("")}}',
23 paths: {
23 paths: {
24 nbextensions : '{{ base_project_url }}nbextensions',
24 nbextensions : '{{ base_url }}nbextensions',
25 underscore : '{{static_url("components/underscore/underscore-min")}}',
25 underscore : '{{static_url("components/underscore/underscore-min")}}',
26 backbone : '{{static_url("components/backbone/backbone-min")}}',
26 backbone : '{{static_url("components/backbone/backbone-min")}}',
27 },
27 },
28 shim: {
28 shim: {
29 underscore: {
29 underscore: {
30 exports: '_'
30 exports: '_'
31 },
31 },
32 backbone: {
32 backbone: {
33 deps: ["underscore", "jquery"],
33 deps: ["underscore", "jquery"],
34 exports: "Backbone"
34 exports: "Backbone"
35 }
35 }
36 }
36 }
37 });
37 });
38 </script>
38 </script>
39
39
40 {% block meta %}
40 {% block meta %}
41 {% endblock %}
41 {% endblock %}
42
42
43 </head>
43 </head>
44
44
45 <body {% block params %}{% endblock %}>
45 <body {% block params %}{% endblock %}>
46
46
47 <noscript>
47 <noscript>
48 <div id='noscript'>
48 <div id='noscript'>
49 IPython Notebook requires JavaScript.<br>
49 IPython Notebook requires JavaScript.<br>
50 Please enable it to proceed.
50 Please enable it to proceed.
51 </div>
51 </div>
52 </noscript>
52 </noscript>
53
53
54 <div id="header" class="navbar navbar-static-top">
54 <div id="header" class="navbar navbar-static-top">
55 <div class="navbar-inner navbar-nobg">
55 <div class="navbar-inner navbar-nobg">
56 <div class="container">
56 <div class="container">
57 <div id="ipython_notebook" class="nav brand pull-left"><a href="{{base_project_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
57 <div id="ipython_notebook" class="nav brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
58
58
59 {% block login_widget %}
59 {% block login_widget %}
60
60
61 <span id="login_widget">
61 <span id="login_widget">
62 {% if logged_in %}
62 {% if logged_in %}
63 <button id="logout">Logout</button>
63 <button id="logout">Logout</button>
64 {% elif login_available and not logged_in %}
64 {% elif login_available and not logged_in %}
65 <button id="login">Login</button>
65 <button id="login">Login</button>
66 {% endif %}
66 {% endif %}
67 </span>
67 </span>
68
68
69 {% endblock %}
69 {% endblock %}
70
70
71 {% block header %}
71 {% block header %}
72 {% endblock %}
72 {% endblock %}
73 </div>
73 </div>
74 </div>
74 </div>
75 </div>
75 </div>
76
76
77 <div id="site">
77 <div id="site">
78 {% block site %}
78 {% block site %}
79 {% endblock %}
79 {% endblock %}
80 </div>
80 </div>
81
81
82 <script src="{{static_url("components/jquery/jquery.min.js") }}" type="text/javascript" charset="utf-8"></script>
82 <script src="{{static_url("components/jquery/jquery.min.js") }}" type="text/javascript" charset="utf-8"></script>
83 <script src="{{static_url("components/jquery-ui/ui/minified/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script>
83 <script src="{{static_url("components/jquery-ui/ui/minified/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script>
84 <script src="{{static_url("components/bootstrap/bootstrap/js/bootstrap.min.js") }}" type="text/javascript" charset="utf-8"></script>
84 <script src="{{static_url("components/bootstrap/bootstrap/js/bootstrap.min.js") }}" type="text/javascript" charset="utf-8"></script>
85 <script src="{{static_url("base/js/namespace.js") }}" type="text/javascript" charset="utf-8"></script>
85 <script src="{{static_url("base/js/namespace.js") }}" type="text/javascript" charset="utf-8"></script>
86 <script src="{{static_url("base/js/page.js") }}" type="text/javascript" charset="utf-8"></script>
86 <script src="{{static_url("base/js/page.js") }}" type="text/javascript" charset="utf-8"></script>
87 <script src="{{static_url("auth/js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script>
87 <script src="{{static_url("auth/js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script>
88
88
89 {% block script %}
89 {% block script %}
90 {% endblock %}
90 {% endblock %}
91
91
92 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
92 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
93
93
94 </body>
94 </body>
95
95
96 </html>
96 </html>
@@ -1,99 +1,99
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block title %}{{page_title}}{% endblock %}
3 {% block title %}{{page_title}}{% endblock %}
4
4
5
5
6 {% block stylesheet %}
6 {% block stylesheet %}
7 {{super()}}
7 {{super()}}
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block params %}
11 {% block params %}
12
12
13 data-project="{{project}}"
13 data-project="{{project}}"
14 data-base-project-url="{{base_project_url}}"
14 data-base-url="{{base_url}}"
15 data-notebook-path="{{notebook_path}}"
15 data-notebook-path="{{notebook_path}}"
16 data-base-kernel-url="{{base_kernel_url}}"
16 data-base-kernel-url="{{base_kernel_url}}"
17
17
18 {% endblock %}
18 {% endblock %}
19
19
20
20
21 {% block site %}
21 {% block site %}
22
22
23 <div id="ipython-main-app" class="container">
23 <div id="ipython-main-app" class="container">
24
24
25 <div id="tab_content" class="tabbable">
25 <div id="tab_content" class="tabbable">
26 <ul id="tabs" class="nav nav-tabs">
26 <ul id="tabs" class="nav nav-tabs">
27 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
27 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
28 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
28 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
29 </ul>
29 </ul>
30
30
31 <div class="tab-content">
31 <div class="tab-content">
32 <div id="notebooks" class="tab-pane active">
32 <div id="notebooks" class="tab-pane active">
33 <div id="notebook_toolbar" class="row-fluid">
33 <div id="notebook_toolbar" class="row-fluid">
34 <div class="span8">
34 <div class="span8">
35 <form id='alternate_upload' class='alternate_upload' >
35 <form id='alternate_upload' class='alternate_upload' >
36 <span id="drag_info" style="position:absolute" >
36 <span id="drag_info" style="position:absolute" >
37 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
37 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
38 </span>
38 </span>
39 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
39 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
40 </form>
40 </form>
41 </div>
41 </div>
42 <div class="span4 clearfix">
42 <div class="span4 clearfix">
43 <span id="notebook_buttons" class="pull-right">
43 <span id="notebook_buttons" class="pull-right">
44 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
44 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
45 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small"><i class="icon-refresh"></i></button>
45 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small"><i class="icon-refresh"></i></button>
46 </span>
46 </span>
47 </div>
47 </div>
48 </div>
48 </div>
49
49
50 <div id="notebook_list">
50 <div id="notebook_list">
51 <div id="notebook_list_header" class="row-fluid list_header">
51 <div id="notebook_list_header" class="row-fluid list_header">
52 <div id="project_name">
52 <div id="project_name">
53 <ul class="breadcrumb">
53 <ul class="breadcrumb">
54 <li><a href="{{breadcrumbs[0][0]}}"><i class="icon-home"></i></a><span>/</span></li>
54 <li><a href="{{breadcrumbs[0][0]}}"><i class="icon-home"></i></a><span>/</span></li>
55 {% for crumb in breadcrumbs[1:] %}
55 {% for crumb in breadcrumbs[1:] %}
56 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a> <span>/</span></li>
56 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a> <span>/</span></li>
57 {% endfor %}
57 {% endfor %}
58 </ul>
58 </ul>
59 </div>
59 </div>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div id="clusters" class="tab-pane">
64 <div id="clusters" class="tab-pane">
65
65
66 <div id="cluster_toolbar" class="row-fluid">
66 <div id="cluster_toolbar" class="row-fluid">
67 <div class="span8">
67 <div class="span8">
68 <span id="cluster_list_info">IPython parallel computing clusters</span>
68 <span id="cluster_list_info">IPython parallel computing clusters</span>
69 </div>
69 </div>
70 <div class="span4" class="clearfix">
70 <div class="span4" class="clearfix">
71 <span id="cluster_buttons" class="pull-right">
71 <span id="cluster_buttons" class="pull-right">
72 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small"><i class="icon-refresh"></i></button>
72 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small"><i class="icon-refresh"></i></button>
73 </span>
73 </span>
74 </div>
74 </div>
75 </div>
75 </div>
76
76
77 <div id="cluster_list">
77 <div id="cluster_list">
78 <div id="cluster_list_header" class="row-fluid list_header">
78 <div id="cluster_list_header" class="row-fluid list_header">
79 <div class="profile_col span4">profile</div>
79 <div class="profile_col span4">profile</div>
80 <div class="status_col span3">status</div>
80 <div class="status_col span3">status</div>
81 <div class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</div>
81 <div class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</div>
82 <div class="action_col span2">action</div>
82 <div class="action_col span2">action</div>
83 </div>
83 </div>
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87
87
88 </div>
88 </div>
89
89
90 {% endblock %}
90 {% endblock %}
91
91
92 {% block script %}
92 {% block script %}
93 {{super()}}
93 {{super()}}
94 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
94 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
95 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
95 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
96 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
96 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
97 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
97 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
98 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
98 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
99 {% endblock %}
99 {% endblock %}
@@ -1,100 +1,100
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 import os
18 import os
19
19
20 from tornado import web
20 from tornado import web
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
22 from ..utils import url_path_join, path2url, url2path, url_escape, is_hidden
22 from ..utils import url_path_join, path2url, url2path, url_escape, is_hidden
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Handlers
25 # Handlers
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28
28
29 class TreeHandler(IPythonHandler):
29 class TreeHandler(IPythonHandler):
30 """Render the tree view, listing notebooks, clusters, etc."""
30 """Render the tree view, listing notebooks, clusters, etc."""
31
31
32 def generate_breadcrumbs(self, path):
32 def generate_breadcrumbs(self, path):
33 breadcrumbs = [(url_escape(url_path_join(self.base_project_url, 'tree')), '')]
33 breadcrumbs = [(url_escape(url_path_join(self.base_url, 'tree')), '')]
34 comps = path.split('/')
34 comps = path.split('/')
35 ncomps = len(comps)
35 ncomps = len(comps)
36 for i in range(ncomps):
36 for i in range(ncomps):
37 if comps[i]:
37 if comps[i]:
38 link = url_escape(url_path_join(self.base_project_url, 'tree', *comps[0:i+1]))
38 link = url_escape(url_path_join(self.base_url, 'tree', *comps[0:i+1]))
39 breadcrumbs.append((link, comps[i]))
39 breadcrumbs.append((link, comps[i]))
40 return breadcrumbs
40 return breadcrumbs
41
41
42 def generate_page_title(self, path):
42 def generate_page_title(self, path):
43 comps = path.split('/')
43 comps = path.split('/')
44 if len(comps) > 3:
44 if len(comps) > 3:
45 for i in range(len(comps)-2):
45 for i in range(len(comps)-2):
46 comps.pop(0)
46 comps.pop(0)
47 page_title = url_escape(url_path_join(*comps))
47 page_title = url_escape(url_path_join(*comps))
48 if page_title:
48 if page_title:
49 return page_title+'/'
49 return page_title+'/'
50 else:
50 else:
51 return 'Home'
51 return 'Home'
52
52
53 @web.authenticated
53 @web.authenticated
54 def get(self, path='', name=None):
54 def get(self, path='', name=None):
55 path = path.strip('/')
55 path = path.strip('/')
56 nbm = self.notebook_manager
56 nbm = self.notebook_manager
57 if name is not None:
57 if name is not None:
58 # is a notebook, redirect to notebook handler
58 # is a notebook, redirect to notebook handler
59 url = url_escape(url_path_join(
59 url = url_escape(url_path_join(
60 self.base_project_url, 'notebooks', path, name
60 self.base_url, 'notebooks', path, name
61 ))
61 ))
62 self.log.debug("Redirecting %s to %s", self.request.path, url)
62 self.log.debug("Redirecting %s to %s", self.request.path, url)
63 self.redirect(url)
63 self.redirect(url)
64 else:
64 else:
65 if not nbm.path_exists(path=path) or nbm.is_hidden(path):
65 if not nbm.path_exists(path=path) or nbm.is_hidden(path):
66 # Directory is hidden or does not exist.
66 # Directory is hidden or does not exist.
67 raise web.HTTPError(404)
67 raise web.HTTPError(404)
68 breadcrumbs = self.generate_breadcrumbs(path)
68 breadcrumbs = self.generate_breadcrumbs(path)
69 page_title = self.generate_page_title(path)
69 page_title = self.generate_page_title(path)
70 self.write(self.render_template('tree.html',
70 self.write(self.render_template('tree.html',
71 project=self.project_dir,
71 project=self.project_dir,
72 page_title=page_title,
72 page_title=page_title,
73 notebook_path=path,
73 notebook_path=path,
74 breadcrumbs=breadcrumbs
74 breadcrumbs=breadcrumbs
75 ))
75 ))
76
76
77
77
78 class TreeRedirectHandler(IPythonHandler):
78 class TreeRedirectHandler(IPythonHandler):
79 """Redirect a request to the corresponding tree URL"""
79 """Redirect a request to the corresponding tree URL"""
80
80
81 @web.authenticated
81 @web.authenticated
82 def get(self, path=''):
82 def get(self, path=''):
83 url = url_escape(url_path_join(
83 url = url_escape(url_path_join(
84 self.base_project_url, 'tree', path.strip('/')
84 self.base_url, 'tree', path.strip('/')
85 ))
85 ))
86 self.log.debug("Redirecting %s to %s", self.request.path, url)
86 self.log.debug("Redirecting %s to %s", self.request.path, url)
87 self.redirect(url)
87 self.redirect(url)
88
88
89
89
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91 # URL to handler mappings
91 # URL to handler mappings
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93
93
94
94
95 default_handlers = [
95 default_handlers = [
96 (r"/tree%s" % notebook_path_regex, TreeHandler),
96 (r"/tree%s" % notebook_path_regex, TreeHandler),
97 (r"/tree%s" % path_regex, TreeHandler),
97 (r"/tree%s" % path_regex, TreeHandler),
98 (r"/tree", TreeHandler),
98 (r"/tree", TreeHandler),
99 (r"/", TreeRedirectHandler),
99 (r"/", TreeRedirectHandler),
100 ]
100 ]
General Comments 0
You need to be logged in to leave comments. Login now