##// END OF EJS Templates
move read_only flag to page-level...
MinRK -
Show More
@@ -1,491 +1,496 b''
1 """Tornado handlers for the notebook.
1 """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) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-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 logging
19 import logging
20 import Cookie
20 import Cookie
21 import uuid
21 import uuid
22
22
23 from tornado import web
23 from tornado import web
24 from tornado import websocket
24 from tornado import websocket
25
25
26 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28
28
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.zmq.session import Session
30 from IPython.zmq.session import Session
31
31
32 try:
32 try:
33 from docutils.core import publish_string
33 from docutils.core import publish_string
34 except ImportError:
34 except ImportError:
35 publish_string = None
35 publish_string = None
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Decorator for disabling read-only handlers
39 # Decorator for disabling read-only handlers
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 @decorator
42 @decorator
43 def not_if_readonly(f, self, *args, **kwargs):
43 def not_if_readonly(f, self, *args, **kwargs):
44 if self.application.ipython_app.read_only:
44 if self.application.read_only:
45 raise web.HTTPError(403, "Notebook server is read-only")
45 raise web.HTTPError(403, "Notebook server is read-only")
46 else:
46 else:
47 return f(self, *args, **kwargs)
47 return f(self, *args, **kwargs)
48
48
49 @decorator
49 @decorator
50 def authenticate_unless_readonly(f, self, *args, **kwargs):
50 def authenticate_unless_readonly(f, self, *args, **kwargs):
51 """authenticate this page *unless* readonly view is active.
51 """authenticate this page *unless* readonly view is active.
52
52
53 In read-only mode, the notebook list and print view should
53 In read-only mode, the notebook list and print view should
54 be accessible without authentication.
54 be accessible without authentication.
55 """
55 """
56
56
57 @web.authenticated
57 @web.authenticated
58 def auth_f(self, *args, **kwargs):
58 def auth_f(self, *args, **kwargs):
59 return f(self, *args, **kwargs)
59 return f(self, *args, **kwargs)
60 if self.application.ipython_app.read_only:
60 if self.application.read_only:
61 return f(self, *args, **kwargs)
61 return f(self, *args, **kwargs)
62 else:
62 else:
63 return auth_f(self, *args, **kwargs)
63 return auth_f(self, *args, **kwargs)
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Top-level handlers
66 # Top-level handlers
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 class AuthenticatedHandler(web.RequestHandler):
69 class AuthenticatedHandler(web.RequestHandler):
70 """A RequestHandler with an authenticated user."""
70 """A RequestHandler with an authenticated user."""
71
71
72 def get_current_user(self):
72 def get_current_user(self):
73 user_id = self.get_secure_cookie("username")
73 user_id = self.get_secure_cookie("username")
74 # For now the user_id should not return empty, but it could eventually
74 # For now the user_id should not return empty, but it could eventually
75 if user_id == '':
75 if user_id == '':
76 user_id = 'anonymous'
76 user_id = 'anonymous'
77 if user_id is None:
77 if user_id is None:
78 # prevent extra Invalid cookie sig warnings:
78 # prevent extra Invalid cookie sig warnings:
79 self.clear_cookie('username')
79 self.clear_cookie('username')
80 if not self.application.password and not self.application.ipython_app.read_only:
80 if not self.application.password and not self.application.read_only:
81 user_id = 'anonymous'
81 user_id = 'anonymous'
82 return user_id
82 return user_id
83
83
84 @property
85 def read_only(self):
86 if self.application.read_only:
87 if self.application.password:
88 return self.get_current_user() is None
89 else:
90 return True
91 else:
92 return False
93
94
84
95
85 class ProjectDashboardHandler(AuthenticatedHandler):
96 class ProjectDashboardHandler(AuthenticatedHandler):
86
97
87 @authenticate_unless_readonly
98 @authenticate_unless_readonly
88 def get(self):
99 def get(self):
89 nbm = self.application.notebook_manager
100 nbm = self.application.notebook_manager
90 project = nbm.notebook_dir
101 project = nbm.notebook_dir
91 self.render(
102 self.render(
92 'projectdashboard.html', project=project,
103 'projectdashboard.html', project=project,
93 base_project_url=u'/', base_kernel_url=u'/'
104 base_project_url=u'/', base_kernel_url=u'/',
105 read_only=self.read_only,
94 )
106 )
95
107
96
108
97 class LoginHandler(AuthenticatedHandler):
109 class LoginHandler(AuthenticatedHandler):
98
110
99 def get(self):
111 def get(self):
100 self.render('login.html', next=self.get_argument('next', default='/'))
112 self.render('login.html',
113 next=self.get_argument('next', default='/'),
114 read_only=self.read_only,
115 )
101
116
102 def post(self):
117 def post(self):
103 pwd = self.get_argument('password', default=u'')
118 pwd = self.get_argument('password', default=u'')
104 if self.application.password and pwd == self.application.password:
119 if self.application.password and pwd == self.application.password:
105 self.set_secure_cookie('username', str(uuid.uuid4()))
120 self.set_secure_cookie('username', str(uuid.uuid4()))
106 url = self.get_argument('next', default='/')
121 self.redirect(self.get_argument('next', default='/'))
107 self.redirect(url)
108
122
109
123
110 class NewHandler(AuthenticatedHandler):
124 class NewHandler(AuthenticatedHandler):
111
125
112 @web.authenticated
126 @web.authenticated
113 def get(self):
127 def get(self):
114 nbm = self.application.notebook_manager
128 nbm = self.application.notebook_manager
115 project = nbm.notebook_dir
129 project = nbm.notebook_dir
116 notebook_id = nbm.new_notebook()
130 notebook_id = nbm.new_notebook()
117 self.render(
131 self.render(
118 'notebook.html', project=project,
132 'notebook.html', project=project,
119 notebook_id=notebook_id,
133 notebook_id=notebook_id,
120 base_project_url=u'/', base_kernel_url=u'/',
134 base_project_url=u'/', base_kernel_url=u'/',
121 kill_kernel=False
135 kill_kernel=False,
136 read_only=False,
122 )
137 )
123
138
124
139
125 class NamedNotebookHandler(AuthenticatedHandler):
140 class NamedNotebookHandler(AuthenticatedHandler):
126
141
127 @authenticate_unless_readonly
142 @authenticate_unless_readonly
128 def get(self, notebook_id):
143 def get(self, notebook_id):
129 nbm = self.application.notebook_manager
144 nbm = self.application.notebook_manager
130 project = nbm.notebook_dir
145 project = nbm.notebook_dir
131 if not nbm.notebook_exists(notebook_id):
146 if not nbm.notebook_exists(notebook_id):
132 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
147 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
148
133 self.render(
149 self.render(
134 'notebook.html', project=project,
150 'notebook.html', project=project,
135 notebook_id=notebook_id,
151 notebook_id=notebook_id,
136 base_project_url=u'/', base_kernel_url=u'/',
152 base_project_url=u'/', base_kernel_url=u'/',
137 kill_kernel=False
153 kill_kernel=False,
154 read_only=self.read_only,
138 )
155 )
139
156
140
157
141 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
142 # Kernel handlers
159 # Kernel handlers
143 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
144
161
145
162
146 class MainKernelHandler(AuthenticatedHandler):
163 class MainKernelHandler(AuthenticatedHandler):
147
164
148 @web.authenticated
165 @web.authenticated
149 def get(self):
166 def get(self):
150 km = self.application.kernel_manager
167 km = self.application.kernel_manager
151 self.finish(jsonapi.dumps(km.kernel_ids))
168 self.finish(jsonapi.dumps(km.kernel_ids))
152
169
153 @web.authenticated
170 @web.authenticated
154 def post(self):
171 def post(self):
155 km = self.application.kernel_manager
172 km = self.application.kernel_manager
156 notebook_id = self.get_argument('notebook', default=None)
173 notebook_id = self.get_argument('notebook', default=None)
157 kernel_id = km.start_kernel(notebook_id)
174 kernel_id = km.start_kernel(notebook_id)
158 ws_url = self.application.ipython_app.get_ws_url()
175 ws_url = self.application.ipython_app.get_ws_url()
159 data = {'ws_url':ws_url,'kernel_id':kernel_id}
176 data = {'ws_url':ws_url,'kernel_id':kernel_id}
160 self.set_header('Location', '/'+kernel_id)
177 self.set_header('Location', '/'+kernel_id)
161 self.finish(jsonapi.dumps(data))
178 self.finish(jsonapi.dumps(data))
162
179
163
180
164 class KernelHandler(AuthenticatedHandler):
181 class KernelHandler(AuthenticatedHandler):
165
182
166 SUPPORTED_METHODS = ('DELETE')
183 SUPPORTED_METHODS = ('DELETE')
167
184
168 @web.authenticated
185 @web.authenticated
169 def delete(self, kernel_id):
186 def delete(self, kernel_id):
170 km = self.application.kernel_manager
187 km = self.application.kernel_manager
171 km.kill_kernel(kernel_id)
188 km.kill_kernel(kernel_id)
172 self.set_status(204)
189 self.set_status(204)
173 self.finish()
190 self.finish()
174
191
175
192
176 class KernelActionHandler(AuthenticatedHandler):
193 class KernelActionHandler(AuthenticatedHandler):
177
194
178 @web.authenticated
195 @web.authenticated
179 def post(self, kernel_id, action):
196 def post(self, kernel_id, action):
180 km = self.application.kernel_manager
197 km = self.application.kernel_manager
181 if action == 'interrupt':
198 if action == 'interrupt':
182 km.interrupt_kernel(kernel_id)
199 km.interrupt_kernel(kernel_id)
183 self.set_status(204)
200 self.set_status(204)
184 if action == 'restart':
201 if action == 'restart':
185 new_kernel_id = km.restart_kernel(kernel_id)
202 new_kernel_id = km.restart_kernel(kernel_id)
186 ws_url = self.application.ipython_app.get_ws_url()
203 ws_url = self.application.ipython_app.get_ws_url()
187 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
204 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
188 self.set_header('Location', '/'+new_kernel_id)
205 self.set_header('Location', '/'+new_kernel_id)
189 self.write(jsonapi.dumps(data))
206 self.write(jsonapi.dumps(data))
190 self.finish()
207 self.finish()
191
208
192
209
193 class ZMQStreamHandler(websocket.WebSocketHandler):
210 class ZMQStreamHandler(websocket.WebSocketHandler):
194
211
195 def _reserialize_reply(self, msg_list):
212 def _reserialize_reply(self, msg_list):
196 """Reserialize a reply message using JSON.
213 """Reserialize a reply message using JSON.
197
214
198 This takes the msg list from the ZMQ socket, unserializes it using
215 This takes the msg list from the ZMQ socket, unserializes it using
199 self.session and then serializes the result using JSON. This method
216 self.session and then serializes the result using JSON. This method
200 should be used by self._on_zmq_reply to build messages that can
217 should be used by self._on_zmq_reply to build messages that can
201 be sent back to the browser.
218 be sent back to the browser.
202 """
219 """
203 idents, msg_list = self.session.feed_identities(msg_list)
220 idents, msg_list = self.session.feed_identities(msg_list)
204 msg = self.session.unserialize(msg_list)
221 msg = self.session.unserialize(msg_list)
205 try:
222 try:
206 msg['header'].pop('date')
223 msg['header'].pop('date')
207 except KeyError:
224 except KeyError:
208 pass
225 pass
209 try:
226 try:
210 msg['parent_header'].pop('date')
227 msg['parent_header'].pop('date')
211 except KeyError:
228 except KeyError:
212 pass
229 pass
213 msg.pop('buffers')
230 msg.pop('buffers')
214 return jsonapi.dumps(msg)
231 return jsonapi.dumps(msg)
215
232
216 def _on_zmq_reply(self, msg_list):
233 def _on_zmq_reply(self, msg_list):
217 try:
234 try:
218 msg = self._reserialize_reply(msg_list)
235 msg = self._reserialize_reply(msg_list)
219 except:
236 except:
220 self.application.log.critical("Malformed message: %r" % msg_list)
237 self.application.log.critical("Malformed message: %r" % msg_list)
221 else:
238 else:
222 self.write_message(msg)
239 self.write_message(msg)
223
240
224
241
225 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
242 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
226
243
227 def open(self, kernel_id):
244 def open(self, kernel_id):
228 self.kernel_id = kernel_id.decode('ascii')
245 self.kernel_id = kernel_id.decode('ascii')
229 try:
246 try:
230 cfg = self.application.ipython_app.config
247 cfg = self.application.ipython_app.config
231 except AttributeError:
248 except AttributeError:
232 # protect from the case where this is run from something other than
249 # protect from the case where this is run from something other than
233 # the notebook app:
250 # the notebook app:
234 cfg = None
251 cfg = None
235 self.session = Session(config=cfg)
252 self.session = Session(config=cfg)
236 self.save_on_message = self.on_message
253 self.save_on_message = self.on_message
237 self.on_message = self.on_first_message
254 self.on_message = self.on_first_message
238
255
239 def get_current_user(self):
256 def get_current_user(self):
240 user_id = self.get_secure_cookie("username")
257 user_id = self.get_secure_cookie("username")
241 if user_id == '' or (user_id is None and not self.application.password):
258 if user_id == '' or (user_id is None and not self.application.password):
242 user_id = 'anonymous'
259 user_id = 'anonymous'
243 return user_id
260 return user_id
244
261
245 def _inject_cookie_message(self, msg):
262 def _inject_cookie_message(self, msg):
246 """Inject the first message, which is the document cookie,
263 """Inject the first message, which is the document cookie,
247 for authentication."""
264 for authentication."""
248 if isinstance(msg, unicode):
265 if isinstance(msg, unicode):
249 # Cookie can't constructor doesn't accept unicode strings for some reason
266 # Cookie can't constructor doesn't accept unicode strings for some reason
250 msg = msg.encode('utf8', 'replace')
267 msg = msg.encode('utf8', 'replace')
251 try:
268 try:
252 self.request._cookies = Cookie.SimpleCookie(msg)
269 self.request._cookies = Cookie.SimpleCookie(msg)
253 except:
270 except:
254 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
271 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
255
272
256 def on_first_message(self, msg):
273 def on_first_message(self, msg):
257 self._inject_cookie_message(msg)
274 self._inject_cookie_message(msg)
258 if self.get_current_user() is None:
275 if self.get_current_user() is None:
259 logging.warn("Couldn't authenticate WebSocket connection")
276 logging.warn("Couldn't authenticate WebSocket connection")
260 raise web.HTTPError(403)
277 raise web.HTTPError(403)
261 self.on_message = self.save_on_message
278 self.on_message = self.save_on_message
262
279
263
280
264 class IOPubHandler(AuthenticatedZMQStreamHandler):
281 class IOPubHandler(AuthenticatedZMQStreamHandler):
265
282
266 def initialize(self, *args, **kwargs):
283 def initialize(self, *args, **kwargs):
267 self._kernel_alive = True
284 self._kernel_alive = True
268 self._beating = False
285 self._beating = False
269 self.iopub_stream = None
286 self.iopub_stream = None
270 self.hb_stream = None
287 self.hb_stream = None
271
288
272 def on_first_message(self, msg):
289 def on_first_message(self, msg):
273 try:
290 try:
274 super(IOPubHandler, self).on_first_message(msg)
291 super(IOPubHandler, self).on_first_message(msg)
275 except web.HTTPError:
292 except web.HTTPError:
276 self.close()
293 self.close()
277 return
294 return
278 km = self.application.kernel_manager
295 km = self.application.kernel_manager
279 self.time_to_dead = km.time_to_dead
296 self.time_to_dead = km.time_to_dead
280 kernel_id = self.kernel_id
297 kernel_id = self.kernel_id
281 try:
298 try:
282 self.iopub_stream = km.create_iopub_stream(kernel_id)
299 self.iopub_stream = km.create_iopub_stream(kernel_id)
283 self.hb_stream = km.create_hb_stream(kernel_id)
300 self.hb_stream = km.create_hb_stream(kernel_id)
284 except web.HTTPError:
301 except web.HTTPError:
285 # WebSockets don't response to traditional error codes so we
302 # WebSockets don't response to traditional error codes so we
286 # close the connection.
303 # close the connection.
287 if not self.stream.closed():
304 if not self.stream.closed():
288 self.stream.close()
305 self.stream.close()
289 self.close()
306 self.close()
290 else:
307 else:
291 self.iopub_stream.on_recv(self._on_zmq_reply)
308 self.iopub_stream.on_recv(self._on_zmq_reply)
292 self.start_hb(self.kernel_died)
309 self.start_hb(self.kernel_died)
293
310
294 def on_message(self, msg):
311 def on_message(self, msg):
295 pass
312 pass
296
313
297 def on_close(self):
314 def on_close(self):
298 # This method can be called twice, once by self.kernel_died and once
315 # This method can be called twice, once by self.kernel_died and once
299 # from the WebSocket close event. If the WebSocket connection is
316 # from the WebSocket close event. If the WebSocket connection is
300 # closed before the ZMQ streams are setup, they could be None.
317 # closed before the ZMQ streams are setup, they could be None.
301 self.stop_hb()
318 self.stop_hb()
302 if self.iopub_stream is not None and not self.iopub_stream.closed():
319 if self.iopub_stream is not None and not self.iopub_stream.closed():
303 self.iopub_stream.on_recv(None)
320 self.iopub_stream.on_recv(None)
304 self.iopub_stream.close()
321 self.iopub_stream.close()
305 if self.hb_stream is not None and not self.hb_stream.closed():
322 if self.hb_stream is not None and not self.hb_stream.closed():
306 self.hb_stream.close()
323 self.hb_stream.close()
307
324
308 def start_hb(self, callback):
325 def start_hb(self, callback):
309 """Start the heartbeating and call the callback if the kernel dies."""
326 """Start the heartbeating and call the callback if the kernel dies."""
310 if not self._beating:
327 if not self._beating:
311 self._kernel_alive = True
328 self._kernel_alive = True
312
329
313 def ping_or_dead():
330 def ping_or_dead():
314 if self._kernel_alive:
331 if self._kernel_alive:
315 self._kernel_alive = False
332 self._kernel_alive = False
316 self.hb_stream.send(b'ping')
333 self.hb_stream.send(b'ping')
317 else:
334 else:
318 try:
335 try:
319 callback()
336 callback()
320 except:
337 except:
321 pass
338 pass
322 finally:
339 finally:
323 self._hb_periodic_callback.stop()
340 self._hb_periodic_callback.stop()
324
341
325 def beat_received(msg):
342 def beat_received(msg):
326 self._kernel_alive = True
343 self._kernel_alive = True
327
344
328 self.hb_stream.on_recv(beat_received)
345 self.hb_stream.on_recv(beat_received)
329 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
346 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
330 self._hb_periodic_callback.start()
347 self._hb_periodic_callback.start()
331 self._beating= True
348 self._beating= True
332
349
333 def stop_hb(self):
350 def stop_hb(self):
334 """Stop the heartbeating and cancel all related callbacks."""
351 """Stop the heartbeating and cancel all related callbacks."""
335 if self._beating:
352 if self._beating:
336 self._hb_periodic_callback.stop()
353 self._hb_periodic_callback.stop()
337 if not self.hb_stream.closed():
354 if not self.hb_stream.closed():
338 self.hb_stream.on_recv(None)
355 self.hb_stream.on_recv(None)
339
356
340 def kernel_died(self):
357 def kernel_died(self):
341 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
358 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
342 self.write_message(
359 self.write_message(
343 {'header': {'msg_type': 'status'},
360 {'header': {'msg_type': 'status'},
344 'parent_header': {},
361 'parent_header': {},
345 'content': {'execution_state':'dead'}
362 'content': {'execution_state':'dead'}
346 }
363 }
347 )
364 )
348 self.on_close()
365 self.on_close()
349
366
350
367
351 class ShellHandler(AuthenticatedZMQStreamHandler):
368 class ShellHandler(AuthenticatedZMQStreamHandler):
352
369
353 def initialize(self, *args, **kwargs):
370 def initialize(self, *args, **kwargs):
354 self.shell_stream = None
371 self.shell_stream = None
355
372
356 def on_first_message(self, msg):
373 def on_first_message(self, msg):
357 try:
374 try:
358 super(ShellHandler, self).on_first_message(msg)
375 super(ShellHandler, self).on_first_message(msg)
359 except web.HTTPError:
376 except web.HTTPError:
360 self.close()
377 self.close()
361 return
378 return
362 km = self.application.kernel_manager
379 km = self.application.kernel_manager
363 self.max_msg_size = km.max_msg_size
380 self.max_msg_size = km.max_msg_size
364 kernel_id = self.kernel_id
381 kernel_id = self.kernel_id
365 try:
382 try:
366 self.shell_stream = km.create_shell_stream(kernel_id)
383 self.shell_stream = km.create_shell_stream(kernel_id)
367 except web.HTTPError:
384 except web.HTTPError:
368 # WebSockets don't response to traditional error codes so we
385 # WebSockets don't response to traditional error codes so we
369 # close the connection.
386 # close the connection.
370 if not self.stream.closed():
387 if not self.stream.closed():
371 self.stream.close()
388 self.stream.close()
372 self.close()
389 self.close()
373 else:
390 else:
374 self.shell_stream.on_recv(self._on_zmq_reply)
391 self.shell_stream.on_recv(self._on_zmq_reply)
375
392
376 def on_message(self, msg):
393 def on_message(self, msg):
377 if len(msg) < self.max_msg_size:
394 if len(msg) < self.max_msg_size:
378 msg = jsonapi.loads(msg)
395 msg = jsonapi.loads(msg)
379 self.session.send(self.shell_stream, msg)
396 self.session.send(self.shell_stream, msg)
380
397
381 def on_close(self):
398 def on_close(self):
382 # Make sure the stream exists and is not already closed.
399 # Make sure the stream exists and is not already closed.
383 if self.shell_stream is not None and not self.shell_stream.closed():
400 if self.shell_stream is not None and not self.shell_stream.closed():
384 self.shell_stream.close()
401 self.shell_stream.close()
385
402
386
403
387 #-----------------------------------------------------------------------------
404 #-----------------------------------------------------------------------------
388 # Notebook web service handlers
405 # Notebook web service handlers
389 #-----------------------------------------------------------------------------
406 #-----------------------------------------------------------------------------
390
407
391 class NotebookRootHandler(AuthenticatedHandler):
408 class NotebookRootHandler(AuthenticatedHandler):
392
409
393 @authenticate_unless_readonly
410 @authenticate_unless_readonly
394 def get(self):
411 def get(self):
395
412
396 # communicate read-only via Allow header
397 if self.application.ipython_app.read_only and not self.get_current_user():
398 self.set_header('Allow', 'GET')
399 else:
400 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
401
402 nbm = self.application.notebook_manager
413 nbm = self.application.notebook_manager
403 files = nbm.list_notebooks()
414 files = nbm.list_notebooks()
404 self.finish(jsonapi.dumps(files))
415 self.finish(jsonapi.dumps(files))
405
416
406 @web.authenticated
417 @web.authenticated
407 def post(self):
418 def post(self):
408 nbm = self.application.notebook_manager
419 nbm = self.application.notebook_manager
409 body = self.request.body.strip()
420 body = self.request.body.strip()
410 format = self.get_argument('format', default='json')
421 format = self.get_argument('format', default='json')
411 name = self.get_argument('name', default=None)
422 name = self.get_argument('name', default=None)
412 if body:
423 if body:
413 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
424 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
414 else:
425 else:
415 notebook_id = nbm.new_notebook()
426 notebook_id = nbm.new_notebook()
416 self.set_header('Location', '/'+notebook_id)
427 self.set_header('Location', '/'+notebook_id)
417 self.finish(jsonapi.dumps(notebook_id))
428 self.finish(jsonapi.dumps(notebook_id))
418
429
419
430
420 class NotebookHandler(AuthenticatedHandler):
431 class NotebookHandler(AuthenticatedHandler):
421
432
422 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
433 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
423
434
424 @authenticate_unless_readonly
435 @authenticate_unless_readonly
425 def get(self, notebook_id):
436 def get(self, notebook_id):
426 nbm = self.application.notebook_manager
437 nbm = self.application.notebook_manager
427 format = self.get_argument('format', default='json')
438 format = self.get_argument('format', default='json')
428 last_mod, name, data = nbm.get_notebook(notebook_id, format)
439 last_mod, name, data = nbm.get_notebook(notebook_id, format)
429
440
430 # communicate read-only via Allow header
431 if self.application.ipython_app.read_only and not self.get_current_user():
432 self.set_header('Allow', 'GET')
433 else:
434 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
435
436 if format == u'json':
441 if format == u'json':
437 self.set_header('Content-Type', 'application/json')
442 self.set_header('Content-Type', 'application/json')
438 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
443 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
439 elif format == u'py':
444 elif format == u'py':
440 self.set_header('Content-Type', 'application/x-python')
445 self.set_header('Content-Type', 'application/x-python')
441 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
446 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
442 self.set_header('Last-Modified', last_mod)
447 self.set_header('Last-Modified', last_mod)
443 self.finish(data)
448 self.finish(data)
444
449
445 @web.authenticated
450 @web.authenticated
446 def put(self, notebook_id):
451 def put(self, notebook_id):
447 nbm = self.application.notebook_manager
452 nbm = self.application.notebook_manager
448 format = self.get_argument('format', default='json')
453 format = self.get_argument('format', default='json')
449 name = self.get_argument('name', default=None)
454 name = self.get_argument('name', default=None)
450 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
455 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
451 self.set_status(204)
456 self.set_status(204)
452 self.finish()
457 self.finish()
453
458
454 @web.authenticated
459 @web.authenticated
455 def delete(self, notebook_id):
460 def delete(self, notebook_id):
456 nbm = self.application.notebook_manager
461 nbm = self.application.notebook_manager
457 nbm.delete_notebook(notebook_id)
462 nbm.delete_notebook(notebook_id)
458 self.set_status(204)
463 self.set_status(204)
459 self.finish()
464 self.finish()
460
465
461 #-----------------------------------------------------------------------------
466 #-----------------------------------------------------------------------------
462 # RST web service handlers
467 # RST web service handlers
463 #-----------------------------------------------------------------------------
468 #-----------------------------------------------------------------------------
464
469
465
470
466 class RSTHandler(AuthenticatedHandler):
471 class RSTHandler(AuthenticatedHandler):
467
472
468 @web.authenticated
473 @web.authenticated
469 def post(self):
474 def post(self):
470 if publish_string is None:
475 if publish_string is None:
471 raise web.HTTPError(503, u'docutils not available')
476 raise web.HTTPError(503, u'docutils not available')
472 body = self.request.body.strip()
477 body = self.request.body.strip()
473 source = body
478 source = body
474 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
479 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
475 defaults = {'file_insertion_enabled': 0,
480 defaults = {'file_insertion_enabled': 0,
476 'raw_enabled': 0,
481 'raw_enabled': 0,
477 '_disable_config': 1,
482 '_disable_config': 1,
478 'stylesheet_path': 0
483 'stylesheet_path': 0
479 # 'template': template_path
484 # 'template': template_path
480 }
485 }
481 try:
486 try:
482 html = publish_string(source, writer_name='html',
487 html = publish_string(source, writer_name='html',
483 settings_overrides=defaults
488 settings_overrides=defaults
484 )
489 )
485 except:
490 except:
486 raise web.HTTPError(400, u'Invalid RST')
491 raise web.HTTPError(400, u'Invalid RST')
487 print html
492 print html
488 self.set_header('Content-Type', 'text/html')
493 self.set_header('Content-Type', 'text/html')
489 self.finish(html)
494 self.finish(html)
490
495
491
496
@@ -1,331 +1,332 b''
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-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 errno
19 import errno
20 import logging
20 import logging
21 import os
21 import os
22 import signal
22 import signal
23 import socket
23 import socket
24 import sys
24 import sys
25 import webbrowser
25 import webbrowser
26
26
27 import zmq
27 import zmq
28
28
29 # Install the pyzmq ioloop. This has to be done before anything else from
29 # Install the pyzmq ioloop. This has to be done before anything else from
30 # tornado is imported.
30 # tornado is imported.
31 from zmq.eventloop import ioloop
31 from zmq.eventloop import ioloop
32 import tornado.ioloop
32 import tornado.ioloop
33 tornado.ioloop.IOLoop = ioloop.IOLoop
33 tornado.ioloop.IOLoop = ioloop.IOLoop
34
34
35 from tornado import httpserver
35 from tornado import httpserver
36 from tornado import web
36 from tornado import web
37
37
38 from .kernelmanager import MappingKernelManager
38 from .kernelmanager import MappingKernelManager
39 from .handlers import (LoginHandler,
39 from .handlers import (LoginHandler,
40 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
40 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
41 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
41 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
42 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
42 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
43 )
43 )
44 from .notebookmanager import NotebookManager
44 from .notebookmanager import NotebookManager
45
45
46 from IPython.core.application import BaseIPythonApplication
46 from IPython.core.application import BaseIPythonApplication
47 from IPython.core.profiledir import ProfileDir
47 from IPython.core.profiledir import ProfileDir
48 from IPython.zmq.session import Session, default_secure
48 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.zmqshell import ZMQInteractiveShell
49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.zmq.ipkernel import (
50 from IPython.zmq.ipkernel import (
51 flags as ipkernel_flags,
51 flags as ipkernel_flags,
52 aliases as ipkernel_aliases,
52 aliases as ipkernel_aliases,
53 IPKernelApp
53 IPKernelApp
54 )
54 )
55 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
55 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Module globals
58 # Module globals
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
61 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
62 _kernel_action_regex = r"(?P<action>restart|interrupt)"
62 _kernel_action_regex = r"(?P<action>restart|interrupt)"
63 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
63 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
64
64
65 LOCALHOST = '127.0.0.1'
65 LOCALHOST = '127.0.0.1'
66
66
67 _examples = """
67 _examples = """
68 ipython notebook # start the notebook
68 ipython notebook # start the notebook
69 ipython notebook --profile=sympy # use the sympy profile
69 ipython notebook --profile=sympy # use the sympy profile
70 ipython notebook --pylab=inline # pylab in inline plotting mode
70 ipython notebook --pylab=inline # pylab in inline plotting mode
71 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
71 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
72 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
72 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
73 """
73 """
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # The Tornado web application
76 # The Tornado web application
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79 class NotebookWebApplication(web.Application):
79 class NotebookWebApplication(web.Application):
80
80
81 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
81 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
82 handlers = [
82 handlers = [
83 (r"/", ProjectDashboardHandler),
83 (r"/", ProjectDashboardHandler),
84 (r"/login", LoginHandler),
84 (r"/login", LoginHandler),
85 (r"/new", NewHandler),
85 (r"/new", NewHandler),
86 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
86 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
87 (r"/kernels", MainKernelHandler),
87 (r"/kernels", MainKernelHandler),
88 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
88 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
89 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
89 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
90 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
90 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
91 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
91 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
92 (r"/notebooks", NotebookRootHandler),
92 (r"/notebooks", NotebookRootHandler),
93 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
93 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
94 (r"/rstservice/render", RSTHandler)
94 (r"/rstservice/render", RSTHandler)
95 ]
95 ]
96 settings = dict(
96 settings = dict(
97 template_path=os.path.join(os.path.dirname(__file__), "templates"),
97 template_path=os.path.join(os.path.dirname(__file__), "templates"),
98 static_path=os.path.join(os.path.dirname(__file__), "static"),
98 static_path=os.path.join(os.path.dirname(__file__), "static"),
99 cookie_secret=os.urandom(1024),
99 cookie_secret=os.urandom(1024),
100 login_url="/login",
100 login_url="/login",
101 )
101 )
102 web.Application.__init__(self, handlers, **settings)
102 web.Application.__init__(self, handlers, **settings)
103
103
104 self.kernel_manager = kernel_manager
104 self.kernel_manager = kernel_manager
105 self.log = log
105 self.log = log
106 self.notebook_manager = notebook_manager
106 self.notebook_manager = notebook_manager
107 self.ipython_app = ipython_app
107 self.ipython_app = ipython_app
108 self.read_only = self.ipython_app.read_only
108
109
109
110
110 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
111 # Aliases and Flags
112 # Aliases and Flags
112 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
113
114
114 flags = dict(ipkernel_flags)
115 flags = dict(ipkernel_flags)
115 flags['no-browser']=(
116 flags['no-browser']=(
116 {'NotebookApp' : {'open_browser' : False}},
117 {'NotebookApp' : {'open_browser' : False}},
117 "Don't open the notebook in a browser after startup."
118 "Don't open the notebook in a browser after startup."
118 )
119 )
119 flags['read-only'] = (
120 flags['read-only'] = (
120 {'NotebookApp' : {'read_only' : True}},
121 {'NotebookApp' : {'read_only' : True}},
121 """Allow read-only access to notebooks.
122 """Allow read-only access to notebooks.
122
123
123 When using a password to protect the notebook server, this flag
124 When using a password to protect the notebook server, this flag
124 allows unauthenticated clients to view the notebook list, and
125 allows unauthenticated clients to view the notebook list, and
125 individual notebooks, but not edit them, start kernels, or run
126 individual notebooks, but not edit them, start kernels, or run
126 code.
127 code.
127
128
128 If no password is set, the server will be entirely read-only.
129 If no password is set, the server will be entirely read-only.
129 """
130 """
130 )
131 )
131
132
132 # the flags that are specific to the frontend
133 # the flags that are specific to the frontend
133 # these must be scrubbed before being passed to the kernel,
134 # these must be scrubbed before being passed to the kernel,
134 # or it will raise an error on unrecognized flags
135 # or it will raise an error on unrecognized flags
135 notebook_flags = ['no-browser', 'read-only']
136 notebook_flags = ['no-browser', 'read-only']
136
137
137 aliases = dict(ipkernel_aliases)
138 aliases = dict(ipkernel_aliases)
138
139
139 aliases.update({
140 aliases.update({
140 'ip': 'NotebookApp.ip',
141 'ip': 'NotebookApp.ip',
141 'port': 'NotebookApp.port',
142 'port': 'NotebookApp.port',
142 'keyfile': 'NotebookApp.keyfile',
143 'keyfile': 'NotebookApp.keyfile',
143 'certfile': 'NotebookApp.certfile',
144 'certfile': 'NotebookApp.certfile',
144 'ws-hostname': 'NotebookApp.ws_hostname',
145 'ws-hostname': 'NotebookApp.ws_hostname',
145 'notebook-dir': 'NotebookManager.notebook_dir',
146 'notebook-dir': 'NotebookManager.notebook_dir',
146 })
147 })
147
148
148 # remove ipkernel flags that are singletons, and don't make sense in
149 # remove ipkernel flags that are singletons, and don't make sense in
149 # multi-kernel evironment:
150 # multi-kernel evironment:
150 aliases.pop('f', None)
151 aliases.pop('f', None)
151
152
152 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
153 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
153 u'notebook-dir']
154 u'notebook-dir']
154
155
155 #-----------------------------------------------------------------------------
156 #-----------------------------------------------------------------------------
156 # NotebookApp
157 # NotebookApp
157 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
158
159
159 class NotebookApp(BaseIPythonApplication):
160 class NotebookApp(BaseIPythonApplication):
160
161
161 name = 'ipython-notebook'
162 name = 'ipython-notebook'
162 default_config_file_name='ipython_notebook_config.py'
163 default_config_file_name='ipython_notebook_config.py'
163
164
164 description = """
165 description = """
165 The IPython HTML Notebook.
166 The IPython HTML Notebook.
166
167
167 This launches a Tornado based HTML Notebook Server that serves up an
168 This launches a Tornado based HTML Notebook Server that serves up an
168 HTML5/Javascript Notebook client.
169 HTML5/Javascript Notebook client.
169 """
170 """
170 examples = _examples
171 examples = _examples
171
172
172 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
173 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
173 MappingKernelManager, NotebookManager]
174 MappingKernelManager, NotebookManager]
174 flags = Dict(flags)
175 flags = Dict(flags)
175 aliases = Dict(aliases)
176 aliases = Dict(aliases)
176
177
177 kernel_argv = List(Unicode)
178 kernel_argv = List(Unicode)
178
179
179 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
180 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
180 default_value=logging.INFO,
181 default_value=logging.INFO,
181 config=True,
182 config=True,
182 help="Set the log level by value or name.")
183 help="Set the log level by value or name.")
183
184
184 # Network related information.
185 # Network related information.
185
186
186 ip = Unicode(LOCALHOST, config=True,
187 ip = Unicode(LOCALHOST, config=True,
187 help="The IP address the notebook server will listen on."
188 help="The IP address the notebook server will listen on."
188 )
189 )
189
190
190 def _ip_changed(self, name, old, new):
191 def _ip_changed(self, name, old, new):
191 if new == u'*': self.ip = u''
192 if new == u'*': self.ip = u''
192
193
193 port = Int(8888, config=True,
194 port = Int(8888, config=True,
194 help="The port the notebook server will listen on."
195 help="The port the notebook server will listen on."
195 )
196 )
196
197
197 ws_hostname = Unicode(LOCALHOST, config=True,
198 ws_hostname = Unicode(LOCALHOST, config=True,
198 help="""The FQDN or IP for WebSocket connections. The default will work
199 help="""The FQDN or IP for WebSocket connections. The default will work
199 fine when the server is listening on localhost, but this needs to
200 fine when the server is listening on localhost, but this needs to
200 be set if the ip option is used. It will be used as the hostname part
201 be set if the ip option is used. It will be used as the hostname part
201 of the WebSocket url: ws://hostname/path."""
202 of the WebSocket url: ws://hostname/path."""
202 )
203 )
203
204
204 certfile = Unicode(u'', config=True,
205 certfile = Unicode(u'', config=True,
205 help="""The full path to an SSL/TLS certificate file."""
206 help="""The full path to an SSL/TLS certificate file."""
206 )
207 )
207
208
208 keyfile = Unicode(u'', config=True,
209 keyfile = Unicode(u'', config=True,
209 help="""The full path to a private key file for usage with SSL/TLS."""
210 help="""The full path to a private key file for usage with SSL/TLS."""
210 )
211 )
211
212
212 password = Unicode(u'', config=True,
213 password = Unicode(u'', config=True,
213 help="""Password to use for web authentication"""
214 help="""Password to use for web authentication"""
214 )
215 )
215
216
216 open_browser = Bool(True, config=True,
217 open_browser = Bool(True, config=True,
217 help="Whether to open in a browser after starting.")
218 help="Whether to open in a browser after starting.")
218
219
219 read_only = Bool(False, config=True,
220 read_only = Bool(False, config=True,
220 help="Whether to prevent editing/execution of notebooks."
221 help="Whether to prevent editing/execution of notebooks."
221 )
222 )
222
223
223 def get_ws_url(self):
224 def get_ws_url(self):
224 """Return the WebSocket URL for this server."""
225 """Return the WebSocket URL for this server."""
225 if self.certfile:
226 if self.certfile:
226 prefix = u'wss://'
227 prefix = u'wss://'
227 else:
228 else:
228 prefix = u'ws://'
229 prefix = u'ws://'
229 return prefix + self.ws_hostname + u':' + unicode(self.port)
230 return prefix + self.ws_hostname + u':' + unicode(self.port)
230
231
231 def parse_command_line(self, argv=None):
232 def parse_command_line(self, argv=None):
232 super(NotebookApp, self).parse_command_line(argv)
233 super(NotebookApp, self).parse_command_line(argv)
233 if argv is None:
234 if argv is None:
234 argv = sys.argv[1:]
235 argv = sys.argv[1:]
235
236
236 self.kernel_argv = list(argv) # copy
237 self.kernel_argv = list(argv) # copy
237 # Kernel should inherit default config file from frontend
238 # Kernel should inherit default config file from frontend
238 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
239 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
239 # Scrub frontend-specific flags
240 # Scrub frontend-specific flags
240 for a in argv:
241 for a in argv:
241 if a.startswith('-') and a.lstrip('-') in notebook_flags:
242 if a.startswith('-') and a.lstrip('-') in notebook_flags:
242 self.kernel_argv.remove(a)
243 self.kernel_argv.remove(a)
243 swallow_next = False
244 swallow_next = False
244 for a in argv:
245 for a in argv:
245 if swallow_next:
246 if swallow_next:
246 self.kernel_argv.remove(a)
247 self.kernel_argv.remove(a)
247 swallow_next = False
248 swallow_next = False
248 continue
249 continue
249 if a.startswith('-'):
250 if a.startswith('-'):
250 split = a.lstrip('-').split('=')
251 split = a.lstrip('-').split('=')
251 alias = split[0]
252 alias = split[0]
252 if alias in notebook_aliases:
253 if alias in notebook_aliases:
253 self.kernel_argv.remove(a)
254 self.kernel_argv.remove(a)
254 if len(split) == 1:
255 if len(split) == 1:
255 # alias passed with arg via space
256 # alias passed with arg via space
256 swallow_next = True
257 swallow_next = True
257
258
258 def init_configurables(self):
259 def init_configurables(self):
259 # Don't let Qt or ZMQ swallow KeyboardInterupts.
260 # Don't let Qt or ZMQ swallow KeyboardInterupts.
260 signal.signal(signal.SIGINT, signal.SIG_DFL)
261 signal.signal(signal.SIGINT, signal.SIG_DFL)
261
262
262 # force Session default to be secure
263 # force Session default to be secure
263 default_secure(self.config)
264 default_secure(self.config)
264 # Create a KernelManager and start a kernel.
265 # Create a KernelManager and start a kernel.
265 self.kernel_manager = MappingKernelManager(
266 self.kernel_manager = MappingKernelManager(
266 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
267 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
267 connection_dir = self.profile_dir.security_dir,
268 connection_dir = self.profile_dir.security_dir,
268 )
269 )
269 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
270 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
270 self.notebook_manager.list_notebooks()
271 self.notebook_manager.list_notebooks()
271
272
272 def init_logging(self):
273 def init_logging(self):
273 super(NotebookApp, self).init_logging()
274 super(NotebookApp, self).init_logging()
274 # This prevents double log messages because tornado use a root logger that
275 # This prevents double log messages because tornado use a root logger that
275 # self.log is a child of. The logging module dipatches log messages to a log
276 # self.log is a child of. The logging module dipatches log messages to a log
276 # and all of its ancenstors until propagate is set to False.
277 # and all of its ancenstors until propagate is set to False.
277 self.log.propagate = False
278 self.log.propagate = False
278
279
279 def initialize(self, argv=None):
280 def initialize(self, argv=None):
280 super(NotebookApp, self).initialize(argv)
281 super(NotebookApp, self).initialize(argv)
281 self.init_configurables()
282 self.init_configurables()
282 self.web_app = NotebookWebApplication(
283 self.web_app = NotebookWebApplication(
283 self, self.kernel_manager, self.notebook_manager, self.log
284 self, self.kernel_manager, self.notebook_manager, self.log
284 )
285 )
285 if self.certfile:
286 if self.certfile:
286 ssl_options = dict(certfile=self.certfile)
287 ssl_options = dict(certfile=self.certfile)
287 if self.keyfile:
288 if self.keyfile:
288 ssl_options['keyfile'] = self.keyfile
289 ssl_options['keyfile'] = self.keyfile
289 else:
290 else:
290 ssl_options = None
291 ssl_options = None
291 self.web_app.password = self.password
292 self.web_app.password = self.password
292 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
293 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
293 if ssl_options is None and not self.ip:
294 if ssl_options is None and not self.ip:
294 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
295 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
295 'but not using any encryption or authentication. This is highly '
296 'but not using any encryption or authentication. This is highly '
296 'insecure and not recommended.')
297 'insecure and not recommended.')
297
298
298 # Try random ports centered around the default.
299 # Try random ports centered around the default.
299 from random import randint
300 from random import randint
300 n = 50 # Max number of attempts, keep reasonably large.
301 n = 50 # Max number of attempts, keep reasonably large.
301 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
302 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
302 try:
303 try:
303 self.http_server.listen(port, self.ip)
304 self.http_server.listen(port, self.ip)
304 except socket.error, e:
305 except socket.error, e:
305 if e.errno != errno.EADDRINUSE:
306 if e.errno != errno.EADDRINUSE:
306 raise
307 raise
307 self.log.info('The port %i is already in use, trying another random port.' % port)
308 self.log.info('The port %i is already in use, trying another random port.' % port)
308 else:
309 else:
309 self.port = port
310 self.port = port
310 break
311 break
311
312
312 def start(self):
313 def start(self):
313 ip = self.ip if self.ip else '[all ip addresses on your system]'
314 ip = self.ip if self.ip else '[all ip addresses on your system]'
314 proto = 'https' if self.certfile else 'http'
315 proto = 'https' if self.certfile else 'http'
315 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
316 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
316 ip,
317 ip,
317 self.port))
318 self.port))
318 if self.open_browser:
319 if self.open_browser:
319 ip = self.ip or '127.0.0.1'
320 ip = self.ip or '127.0.0.1'
320 webbrowser.open("%s://%s:%i" % (proto, ip, self.port), new=2)
321 webbrowser.open("%s://%s:%i" % (proto, ip, self.port), new=2)
321 ioloop.IOLoop.instance().start()
322 ioloop.IOLoop.instance().start()
322
323
323 #-----------------------------------------------------------------------------
324 #-----------------------------------------------------------------------------
324 # Main entry point
325 # Main entry point
325 #-----------------------------------------------------------------------------
326 #-----------------------------------------------------------------------------
326
327
327 def launch_new_instance():
328 def launch_new_instance():
328 app = NotebookApp()
329 app = NotebookApp()
329 app.initialize()
330 app.initialize()
330 app.start()
331 app.start()
331
332
@@ -1,102 +1,104 b''
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 // LeftPanel
9 // LeftPanel
10 //============================================================================
10 //============================================================================
11
11
12
12
13 var IPython = (function (IPython) {
13 var IPython = (function (IPython) {
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var LeftPanel = function (left_panel_selector, left_panel_splitter_selector) {
17 var LeftPanel = function (left_panel_selector, left_panel_splitter_selector) {
18 this.left_panel_element = $(left_panel_selector);
18 this.left_panel_element = $(left_panel_selector);
19 this.left_panel_splitter_element = $(left_panel_splitter_selector);
19 this.left_panel_splitter_element = $(left_panel_splitter_selector);
20 this.expanded = true;
20 this.expanded = true;
21 this.width = 300;
21 this.width = 300;
22 this.style();
22 this.style();
23 this.bind_events();
23 this.bind_events();
24 this.create_children();
24 this.create_children();
25 };
25 };
26
26
27
27
28 LeftPanel.prototype.style = function () {
28 LeftPanel.prototype.style = function () {
29 this.left_panel_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
29 this.left_panel_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
30 this.left_panel_element.addClass('border-box-sizing ui-widget');
30 this.left_panel_element.addClass('border-box-sizing ui-widget');
31 this.left_panel_element.width(this.width);
31 this.left_panel_element.width(this.width);
32 this.left_panel_splitter_element.css({left : this.width});
32 this.left_panel_splitter_element.css({left : this.width});
33 this.left_panel_splitter_element.attr('title', 'Click to Show/Hide left panel');
33 this.left_panel_splitter_element.attr('title', 'Click to Show/Hide left panel');
34 };
34 };
35
35
36
36
37 LeftPanel.prototype.bind_events = function () {
37 LeftPanel.prototype.bind_events = function () {
38 var that = this;
38 var that = this;
39
39
40 this.left_panel_element.bind('collapse_left_panel', function () {
40 this.left_panel_element.bind('collapse_left_panel', function () {
41 that.left_panel_element.hide('fast');
41 that.left_panel_element.hide('fast');
42 that.left_panel_splitter_element.animate({left : 0}, 'fast');
42 that.left_panel_splitter_element.animate({left : 0}, 'fast');
43 });
43 });
44
44
45 this.left_panel_element.bind('expand_left_panel', function () {
45 this.left_panel_element.bind('expand_left_panel', function () {
46 that.left_panel_element.show('fast');
46 that.left_panel_element.show('fast');
47 that.left_panel_splitter_element.animate({left : that.width}, 'fast');
47 that.left_panel_splitter_element.animate({left : that.width}, 'fast');
48 });
48 });
49
49
50 this.left_panel_splitter_element.hover(
50 this.left_panel_splitter_element.hover(
51 function () {
51 function () {
52 that.left_panel_splitter_element.addClass('ui-state-hover');
52 that.left_panel_splitter_element.addClass('ui-state-hover');
53 },
53 },
54 function () {
54 function () {
55 that.left_panel_splitter_element.removeClass('ui-state-hover');
55 that.left_panel_splitter_element.removeClass('ui-state-hover');
56 }
56 }
57 );
57 );
58
58
59 this.left_panel_splitter_element.click(function () {
59 this.left_panel_splitter_element.click(function () {
60 that.toggle();
60 that.toggle();
61 });
61 });
62
62
63 };
63 };
64
64
65
65
66 LeftPanel.prototype.create_children = function () {
66 LeftPanel.prototype.create_children = function () {
67 this.notebook_section = new IPython.NotebookSection('div#notebook_section');
67 this.notebook_section = new IPython.NotebookSection('div#notebook_section');
68 if (! IPython.read_only){
68 this.cell_section = new IPython.CellSection('div#cell_section');
69 this.cell_section = new IPython.CellSection('div#cell_section');
69 this.kernel_section = new IPython.KernelSection('div#kernel_section');
70 this.kernel_section = new IPython.KernelSection('div#kernel_section');
71 }
70 this.help_section = new IPython.HelpSection('div#help_section');
72 this.help_section = new IPython.HelpSection('div#help_section');
71 }
73 }
72
74
73 LeftPanel.prototype.collapse = function () {
75 LeftPanel.prototype.collapse = function () {
74 if (this.expanded === true) {
76 if (this.expanded === true) {
75 this.left_panel_element.add($('div#notebook')).trigger('collapse_left_panel');
77 this.left_panel_element.add($('div#notebook')).trigger('collapse_left_panel');
76 this.expanded = false;
78 this.expanded = false;
77 };
79 };
78 };
80 };
79
81
80
82
81 LeftPanel.prototype.expand = function () {
83 LeftPanel.prototype.expand = function () {
82 if (this.expanded !== true) {
84 if (this.expanded !== true) {
83 this.left_panel_element.add($('div#notebook')).trigger('expand_left_panel');
85 this.left_panel_element.add($('div#notebook')).trigger('expand_left_panel');
84 this.expanded = true;
86 this.expanded = true;
85 };
87 };
86 };
88 };
87
89
88
90
89 LeftPanel.prototype.toggle = function () {
91 LeftPanel.prototype.toggle = function () {
90 if (this.expanded === true) {
92 if (this.expanded === true) {
91 this.collapse();
93 this.collapse();
92 } else {
94 } else {
93 this.expand();
95 this.expand();
94 };
96 };
95 };
97 };
96
98
97 IPython.LeftPanel = LeftPanel;
99 IPython.LeftPanel = LeftPanel;
98
100
99 return IPython;
101 return IPython;
100
102
101 }(IPython));
103 }(IPython));
102
104
@@ -1,1025 +1,1006 b''
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 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var Notebook = function (selector) {
16 var Notebook = function (selector) {
17 this.read_only = false;
17 this.read_only = IPython.read_only;
18 this.element = $(selector);
18 this.element = $(selector);
19 this.element.scroll();
19 this.element.scroll();
20 this.element.data("notebook", this);
20 this.element.data("notebook", this);
21 this.next_prompt_number = 1;
21 this.next_prompt_number = 1;
22 this.kernel = null;
22 this.kernel = null;
23 this.dirty = false;
23 this.dirty = false;
24 this.msg_cell_map = {};
24 this.msg_cell_map = {};
25 this.metadata = {};
25 this.metadata = {};
26 this.control_key_active = false;
26 this.control_key_active = false;
27 this.style();
27 this.style();
28 this.create_elements();
28 this.create_elements();
29 this.bind_events();
29 this.bind_events();
30 };
30 };
31
31
32
32
33 Notebook.prototype.style = function () {
33 Notebook.prototype.style = function () {
34 $('div#notebook').addClass('border-box-sizing');
34 $('div#notebook').addClass('border-box-sizing');
35 };
35 };
36
36
37
37
38 Notebook.prototype.create_elements = function () {
38 Notebook.prototype.create_elements = function () {
39 // We add this end_space div to the end of the notebook div to:
39 // We add this end_space div to the end of the notebook div to:
40 // i) provide a margin between the last cell and the end of the notebook
40 // i) provide a margin between the last cell and the end of the notebook
41 // ii) to prevent the div from scrolling up when the last cell is being
41 // ii) to prevent the div from scrolling up when the last cell is being
42 // edited, but is too low on the page, which browsers will do automatically.
42 // edited, but is too low on the page, which browsers will do automatically.
43 var that = this;
43 var that = this;
44 var end_space = $('<div class="end_space"></div>').height(150);
44 var end_space = $('<div class="end_space"></div>').height(150);
45 end_space.dblclick(function (e) {
45 end_space.dblclick(function (e) {
46 if (that.read_only) return;
46 if (that.read_only) return;
47 var ncells = that.ncells();
47 var ncells = that.ncells();
48 that.insert_code_cell_below(ncells-1);
48 that.insert_code_cell_below(ncells-1);
49 });
49 });
50 this.element.append(end_space);
50 this.element.append(end_space);
51 $('div#notebook').addClass('border-box-sizing');
51 $('div#notebook').addClass('border-box-sizing');
52 };
52 };
53
53
54
54
55 Notebook.prototype.bind_events = function () {
55 Notebook.prototype.bind_events = function () {
56 var that = this;
56 var that = this;
57 $(document).keydown(function (event) {
57 $(document).keydown(function (event) {
58 // console.log(event);
58 // console.log(event);
59 if (that.read_only) return;
59 if (that.read_only) return;
60 if (event.which === 38) {
60 if (event.which === 38) {
61 var cell = that.selected_cell();
61 var cell = that.selected_cell();
62 if (cell.at_top()) {
62 if (cell.at_top()) {
63 event.preventDefault();
63 event.preventDefault();
64 that.select_prev();
64 that.select_prev();
65 };
65 };
66 } else if (event.which === 40) {
66 } else if (event.which === 40) {
67 var cell = that.selected_cell();
67 var cell = that.selected_cell();
68 if (cell.at_bottom()) {
68 if (cell.at_bottom()) {
69 event.preventDefault();
69 event.preventDefault();
70 that.select_next();
70 that.select_next();
71 };
71 };
72 } else if (event.which === 13 && event.shiftKey) {
72 } else if (event.which === 13 && event.shiftKey) {
73 that.execute_selected_cell();
73 that.execute_selected_cell();
74 return false;
74 return false;
75 } else if (event.which === 13 && event.ctrlKey) {
75 } else if (event.which === 13 && event.ctrlKey) {
76 that.execute_selected_cell({terminal:true});
76 that.execute_selected_cell({terminal:true});
77 return false;
77 return false;
78 } else if (event.which === 77 && event.ctrlKey) {
78 } else if (event.which === 77 && event.ctrlKey) {
79 that.control_key_active = true;
79 that.control_key_active = true;
80 return false;
80 return false;
81 } else if (event.which === 68 && that.control_key_active) {
81 } else if (event.which === 68 && that.control_key_active) {
82 // Delete selected cell = d
82 // Delete selected cell = d
83 that.delete_cell();
83 that.delete_cell();
84 that.control_key_active = false;
84 that.control_key_active = false;
85 return false;
85 return false;
86 } else if (event.which === 65 && that.control_key_active) {
86 } else if (event.which === 65 && that.control_key_active) {
87 // Insert code cell above selected = a
87 // Insert code cell above selected = a
88 that.insert_code_cell_above();
88 that.insert_code_cell_above();
89 that.control_key_active = false;
89 that.control_key_active = false;
90 return false;
90 return false;
91 } else if (event.which === 66 && that.control_key_active) {
91 } else if (event.which === 66 && that.control_key_active) {
92 // Insert code cell below selected = b
92 // Insert code cell below selected = b
93 that.insert_code_cell_below();
93 that.insert_code_cell_below();
94 that.control_key_active = false;
94 that.control_key_active = false;
95 return false;
95 return false;
96 } else if (event.which === 67 && that.control_key_active) {
96 } else if (event.which === 67 && that.control_key_active) {
97 // To code = c
97 // To code = c
98 that.to_code();
98 that.to_code();
99 that.control_key_active = false;
99 that.control_key_active = false;
100 return false;
100 return false;
101 } else if (event.which === 77 && that.control_key_active) {
101 } else if (event.which === 77 && that.control_key_active) {
102 // To markdown = m
102 // To markdown = m
103 that.to_markdown();
103 that.to_markdown();
104 that.control_key_active = false;
104 that.control_key_active = false;
105 return false;
105 return false;
106 } else if (event.which === 84 && that.control_key_active) {
106 } else if (event.which === 84 && that.control_key_active) {
107 // Toggle output = t
107 // Toggle output = t
108 that.toggle_output();
108 that.toggle_output();
109 that.control_key_active = false;
109 that.control_key_active = false;
110 return false;
110 return false;
111 } else if (event.which === 83 && that.control_key_active) {
111 } else if (event.which === 83 && that.control_key_active) {
112 // Save notebook = s
112 // Save notebook = s
113 IPython.save_widget.save_notebook();
113 IPython.save_widget.save_notebook();
114 that.control_key_active = false;
114 that.control_key_active = false;
115 return false;
115 return false;
116 } else if (event.which === 74 && that.control_key_active) {
116 } else if (event.which === 74 && that.control_key_active) {
117 // Move cell down = j
117 // Move cell down = j
118 that.move_cell_down();
118 that.move_cell_down();
119 that.control_key_active = false;
119 that.control_key_active = false;
120 return false;
120 return false;
121 } else if (event.which === 75 && that.control_key_active) {
121 } else if (event.which === 75 && that.control_key_active) {
122 // Move cell up = k
122 // Move cell up = k
123 that.move_cell_up();
123 that.move_cell_up();
124 that.control_key_active = false;
124 that.control_key_active = false;
125 return false;
125 return false;
126 } else if (event.which === 80 && that.control_key_active) {
126 } else if (event.which === 80 && that.control_key_active) {
127 // Select previous = p
127 // Select previous = p
128 that.select_prev();
128 that.select_prev();
129 that.control_key_active = false;
129 that.control_key_active = false;
130 return false;
130 return false;
131 } else if (event.which === 78 && that.control_key_active) {
131 } else if (event.which === 78 && that.control_key_active) {
132 // Select next = n
132 // Select next = n
133 that.select_next();
133 that.select_next();
134 that.control_key_active = false;
134 that.control_key_active = false;
135 return false;
135 return false;
136 } else if (event.which === 76 && that.control_key_active) {
136 } else if (event.which === 76 && that.control_key_active) {
137 // Toggle line numbers = l
137 // Toggle line numbers = l
138 that.cell_toggle_line_numbers();
138 that.cell_toggle_line_numbers();
139 that.control_key_active = false;
139 that.control_key_active = false;
140 return false;
140 return false;
141 } else if (event.which === 73 && that.control_key_active) {
141 } else if (event.which === 73 && that.control_key_active) {
142 // Interrupt kernel = i
142 // Interrupt kernel = i
143 IPython.notebook.kernel.interrupt();
143 IPython.notebook.kernel.interrupt();
144 that.control_key_active = false;
144 that.control_key_active = false;
145 return false;
145 return false;
146 } else if (event.which === 190 && that.control_key_active) {
146 } else if (event.which === 190 && that.control_key_active) {
147 // Restart kernel = . # matches qt console
147 // Restart kernel = . # matches qt console
148 IPython.notebook.restart_kernel();
148 IPython.notebook.restart_kernel();
149 that.control_key_active = false;
149 that.control_key_active = false;
150 return false;
150 return false;
151 } else if (event.which === 72 && that.control_key_active) {
151 } else if (event.which === 72 && that.control_key_active) {
152 // Show keyboard shortcuts = h
152 // Show keyboard shortcuts = h
153 that.toggle_keyboard_shortcuts();
153 that.toggle_keyboard_shortcuts();
154 that.control_key_active = false;
154 that.control_key_active = false;
155 return false;
155 return false;
156 } else if (that.control_key_active) {
156 } else if (that.control_key_active) {
157 that.control_key_active = false;
157 that.control_key_active = false;
158 return true;
158 return true;
159 };
159 };
160 });
160 });
161
161
162 this.element.bind('collapse_pager', function () {
162 this.element.bind('collapse_pager', function () {
163 var app_height = $('div#main_app').height(); // content height
163 var app_height = $('div#main_app').height(); // content height
164 var splitter_height = $('div#pager_splitter').outerHeight(true);
164 var splitter_height = $('div#pager_splitter').outerHeight(true);
165 var new_height = app_height - splitter_height;
165 var new_height = app_height - splitter_height;
166 that.element.animate({height : new_height + 'px'}, 'fast');
166 that.element.animate({height : new_height + 'px'}, 'fast');
167 });
167 });
168
168
169 this.element.bind('expand_pager', function () {
169 this.element.bind('expand_pager', function () {
170 var app_height = $('div#main_app').height(); // content height
170 var app_height = $('div#main_app').height(); // content height
171 var splitter_height = $('div#pager_splitter').outerHeight(true);
171 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 var pager_height = $('div#pager').outerHeight(true);
172 var pager_height = $('div#pager').outerHeight(true);
173 var new_height = app_height - pager_height - splitter_height;
173 var new_height = app_height - pager_height - splitter_height;
174 that.element.animate({height : new_height + 'px'}, 'fast');
174 that.element.animate({height : new_height + 'px'}, 'fast');
175 });
175 });
176
176
177 this.element.bind('collapse_left_panel', function () {
177 this.element.bind('collapse_left_panel', function () {
178 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
178 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
179 var new_margin = splitter_width;
179 var new_margin = splitter_width;
180 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
180 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
181 });
181 });
182
182
183 this.element.bind('expand_left_panel', function () {
183 this.element.bind('expand_left_panel', function () {
184 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
184 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
185 var left_panel_width = IPython.left_panel.width;
185 var left_panel_width = IPython.left_panel.width;
186 var new_margin = splitter_width + left_panel_width;
186 var new_margin = splitter_width + left_panel_width;
187 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
187 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
188 });
188 });
189
189
190 $(window).bind('beforeunload', function () {
190 $(window).bind('beforeunload', function () {
191 var kill_kernel = $('#kill_kernel').prop('checked');
191 var kill_kernel = $('#kill_kernel').prop('checked');
192 if (kill_kernel) {
192 if (kill_kernel) {
193 that.kernel.kill();
193 that.kernel.kill();
194 }
194 }
195 if (that.dirty && ! that.read_only) {
195 if (that.dirty && ! that.read_only) {
196 return "You have unsaved changes that will be lost if you leave this page.";
196 return "You have unsaved changes that will be lost if you leave this page.";
197 };
197 };
198 });
198 });
199 };
199 };
200
200
201
201
202 Notebook.prototype.toggle_keyboard_shortcuts = function () {
202 Notebook.prototype.toggle_keyboard_shortcuts = function () {
203 // toggles display of keyboard shortcut dialog
203 // toggles display of keyboard shortcut dialog
204 var that = this;
204 var that = this;
205 if ( this.shortcut_dialog ){
205 if ( this.shortcut_dialog ){
206 // if dialog is already shown, close it
206 // if dialog is already shown, close it
207 this.shortcut_dialog.dialog("close");
207 this.shortcut_dialog.dialog("close");
208 this.shortcut_dialog = null;
208 this.shortcut_dialog = null;
209 return;
209 return;
210 }
210 }
211 var dialog = $('<div/>');
211 var dialog = $('<div/>');
212 this.shortcut_dialog = dialog;
212 this.shortcut_dialog = dialog;
213 var shortcuts = [
213 var shortcuts = [
214 {key: 'Shift-Enter', help: 'run cell'},
214 {key: 'Shift-Enter', help: 'run cell'},
215 {key: 'Ctrl-Enter', help: 'run cell in-place'},
215 {key: 'Ctrl-Enter', help: 'run cell in-place'},
216 {key: 'Ctrl-m d', help: 'delete cell'},
216 {key: 'Ctrl-m d', help: 'delete cell'},
217 {key: 'Ctrl-m a', help: 'insert cell above'},
217 {key: 'Ctrl-m a', help: 'insert cell above'},
218 {key: 'Ctrl-m b', help: 'insert cell below'},
218 {key: 'Ctrl-m b', help: 'insert cell below'},
219 {key: 'Ctrl-m t', help: 'toggle output'},
219 {key: 'Ctrl-m t', help: 'toggle output'},
220 {key: 'Ctrl-m l', help: 'toggle line numbers'},
220 {key: 'Ctrl-m l', help: 'toggle line numbers'},
221 {key: 'Ctrl-m s', help: 'save notebook'},
221 {key: 'Ctrl-m s', help: 'save notebook'},
222 {key: 'Ctrl-m j', help: 'move cell down'},
222 {key: 'Ctrl-m j', help: 'move cell down'},
223 {key: 'Ctrl-m k', help: 'move cell up'},
223 {key: 'Ctrl-m k', help: 'move cell up'},
224 {key: 'Ctrl-m c', help: 'code cell'},
224 {key: 'Ctrl-m c', help: 'code cell'},
225 {key: 'Ctrl-m m', help: 'markdown cell'},
225 {key: 'Ctrl-m m', help: 'markdown cell'},
226 {key: 'Ctrl-m p', help: 'select previous'},
226 {key: 'Ctrl-m p', help: 'select previous'},
227 {key: 'Ctrl-m n', help: 'select next'},
227 {key: 'Ctrl-m n', help: 'select next'},
228 {key: 'Ctrl-m i', help: 'interrupt kernel'},
228 {key: 'Ctrl-m i', help: 'interrupt kernel'},
229 {key: 'Ctrl-m .', help: 'restart kernel'},
229 {key: 'Ctrl-m .', help: 'restart kernel'},
230 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
230 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
231 ];
231 ];
232 for (var i=0; i<shortcuts.length; i++) {
232 for (var i=0; i<shortcuts.length; i++) {
233 dialog.append($('<div>').
233 dialog.append($('<div>').
234 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
234 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
235 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
235 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
236 );
236 );
237 };
237 };
238 dialog.bind('dialogclose', function(event) {
238 dialog.bind('dialogclose', function(event) {
239 // dialog has been closed, allow it to be drawn again.
239 // dialog has been closed, allow it to be drawn again.
240 that.shortcut_dialog = null;
240 that.shortcut_dialog = null;
241 });
241 });
242 dialog.dialog({title: 'Keyboard shortcuts'});
242 dialog.dialog({title: 'Keyboard shortcuts'});
243 };
243 };
244
244
245
245
246 Notebook.prototype.scroll_to_bottom = function () {
246 Notebook.prototype.scroll_to_bottom = function () {
247 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
247 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
248 };
248 };
249
249
250
250
251 Notebook.prototype.scroll_to_top = function () {
251 Notebook.prototype.scroll_to_top = function () {
252 this.element.animate({scrollTop:0}, 0);
252 this.element.animate({scrollTop:0}, 0);
253 };
253 };
254
254
255
255
256 // Cell indexing, retrieval, etc.
256 // Cell indexing, retrieval, etc.
257
257
258
258
259 Notebook.prototype.cell_elements = function () {
259 Notebook.prototype.cell_elements = function () {
260 return this.element.children("div.cell");
260 return this.element.children("div.cell");
261 }
261 }
262
262
263
263
264 Notebook.prototype.ncells = function (cell) {
264 Notebook.prototype.ncells = function (cell) {
265 return this.cell_elements().length;
265 return this.cell_elements().length;
266 }
266 }
267
267
268
268
269 // TODO: we are often calling cells as cells()[i], which we should optimize
269 // TODO: we are often calling cells as cells()[i], which we should optimize
270 // to cells(i) or a new method.
270 // to cells(i) or a new method.
271 Notebook.prototype.cells = function () {
271 Notebook.prototype.cells = function () {
272 return this.cell_elements().toArray().map(function (e) {
272 return this.cell_elements().toArray().map(function (e) {
273 return $(e).data("cell");
273 return $(e).data("cell");
274 });
274 });
275 }
275 }
276
276
277
277
278 Notebook.prototype.find_cell_index = function (cell) {
278 Notebook.prototype.find_cell_index = function (cell) {
279 var result = null;
279 var result = null;
280 this.cell_elements().filter(function (index) {
280 this.cell_elements().filter(function (index) {
281 if ($(this).data("cell") === cell) {
281 if ($(this).data("cell") === cell) {
282 result = index;
282 result = index;
283 };
283 };
284 });
284 });
285 return result;
285 return result;
286 };
286 };
287
287
288
288
289 Notebook.prototype.index_or_selected = function (index) {
289 Notebook.prototype.index_or_selected = function (index) {
290 return index || this.selected_index() || 0;
290 return index || this.selected_index() || 0;
291 }
291 }
292
292
293
293
294 Notebook.prototype.select = function (index) {
294 Notebook.prototype.select = function (index) {
295 if (index !== undefined && index >= 0 && index < this.ncells()) {
295 if (index !== undefined && index >= 0 && index < this.ncells()) {
296 if (this.selected_index() !== null) {
296 if (this.selected_index() !== null) {
297 this.selected_cell().unselect();
297 this.selected_cell().unselect();
298 };
298 };
299 this.cells()[index].select();
299 this.cells()[index].select();
300 };
300 };
301 return this;
301 return this;
302 };
302 };
303
303
304
304
305 Notebook.prototype.select_next = function () {
305 Notebook.prototype.select_next = function () {
306 var index = this.selected_index();
306 var index = this.selected_index();
307 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
307 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
308 this.select(index+1);
308 this.select(index+1);
309 };
309 };
310 return this;
310 return this;
311 };
311 };
312
312
313
313
314 Notebook.prototype.select_prev = function () {
314 Notebook.prototype.select_prev = function () {
315 var index = this.selected_index();
315 var index = this.selected_index();
316 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
316 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
317 this.select(index-1);
317 this.select(index-1);
318 };
318 };
319 return this;
319 return this;
320 };
320 };
321
321
322
322
323 Notebook.prototype.selected_index = function () {
323 Notebook.prototype.selected_index = function () {
324 var result = null;
324 var result = null;
325 this.cell_elements().filter(function (index) {
325 this.cell_elements().filter(function (index) {
326 if ($(this).data("cell").selected === true) {
326 if ($(this).data("cell").selected === true) {
327 result = index;
327 result = index;
328 };
328 };
329 });
329 });
330 return result;
330 return result;
331 };
331 };
332
332
333
333
334 Notebook.prototype.cell_for_msg = function (msg_id) {
334 Notebook.prototype.cell_for_msg = function (msg_id) {
335 var cell_id = this.msg_cell_map[msg_id];
335 var cell_id = this.msg_cell_map[msg_id];
336 var result = null;
336 var result = null;
337 this.cell_elements().filter(function (index) {
337 this.cell_elements().filter(function (index) {
338 cell = $(this).data("cell");
338 cell = $(this).data("cell");
339 if (cell.cell_id === cell_id) {
339 if (cell.cell_id === cell_id) {
340 result = cell;
340 result = cell;
341 };
341 };
342 });
342 });
343 return result;
343 return result;
344 };
344 };
345
345
346
346
347 Notebook.prototype.selected_cell = function () {
347 Notebook.prototype.selected_cell = function () {
348 return this.cell_elements().eq(this.selected_index()).data("cell");
348 return this.cell_elements().eq(this.selected_index()).data("cell");
349 }
349 }
350
350
351
351
352 // Cell insertion, deletion and moving.
352 // Cell insertion, deletion and moving.
353
353
354
354
355 Notebook.prototype.delete_cell = function (index) {
355 Notebook.prototype.delete_cell = function (index) {
356 var i = index || this.selected_index();
356 var i = index || this.selected_index();
357 if (i !== null && i >= 0 && i < this.ncells()) {
357 if (i !== null && i >= 0 && i < this.ncells()) {
358 this.cell_elements().eq(i).remove();
358 this.cell_elements().eq(i).remove();
359 if (i === (this.ncells())) {
359 if (i === (this.ncells())) {
360 this.select(i-1);
360 this.select(i-1);
361 } else {
361 } else {
362 this.select(i);
362 this.select(i);
363 };
363 };
364 };
364 };
365 this.dirty = true;
365 this.dirty = true;
366 return this;
366 return this;
367 };
367 };
368
368
369
369
370 Notebook.prototype.append_cell = function (cell) {
370 Notebook.prototype.append_cell = function (cell) {
371 this.element.find('div.end_space').before(cell.element);
371 this.element.find('div.end_space').before(cell.element);
372 this.dirty = true;
372 this.dirty = true;
373 return this;
373 return this;
374 };
374 };
375
375
376
376
377 Notebook.prototype.insert_cell_below = function (cell, index) {
377 Notebook.prototype.insert_cell_below = function (cell, index) {
378 var ncells = this.ncells();
378 var ncells = this.ncells();
379 if (ncells === 0) {
379 if (ncells === 0) {
380 this.append_cell(cell);
380 this.append_cell(cell);
381 return this;
381 return this;
382 };
382 };
383 if (index >= 0 && index < ncells) {
383 if (index >= 0 && index < ncells) {
384 this.cell_elements().eq(index).after(cell.element);
384 this.cell_elements().eq(index).after(cell.element);
385 };
385 };
386 this.dirty = true;
386 this.dirty = true;
387 return this
387 return this
388 };
388 };
389
389
390
390
391 Notebook.prototype.insert_cell_above = function (cell, index) {
391 Notebook.prototype.insert_cell_above = function (cell, index) {
392 var ncells = this.ncells();
392 var ncells = this.ncells();
393 if (ncells === 0) {
393 if (ncells === 0) {
394 this.append_cell(cell);
394 this.append_cell(cell);
395 return this;
395 return this;
396 };
396 };
397 if (index >= 0 && index < ncells) {
397 if (index >= 0 && index < ncells) {
398 this.cell_elements().eq(index).before(cell.element);
398 this.cell_elements().eq(index).before(cell.element);
399 };
399 };
400 this.dirty = true;
400 this.dirty = true;
401 return this;
401 return this;
402 };
402 };
403
403
404
404
405 Notebook.prototype.move_cell_up = function (index) {
405 Notebook.prototype.move_cell_up = function (index) {
406 var i = index || this.selected_index();
406 var i = index || this.selected_index();
407 if (i !== null && i < this.ncells() && i > 0) {
407 if (i !== null && i < this.ncells() && i > 0) {
408 var pivot = this.cell_elements().eq(i-1);
408 var pivot = this.cell_elements().eq(i-1);
409 var tomove = this.cell_elements().eq(i);
409 var tomove = this.cell_elements().eq(i);
410 if (pivot !== null && tomove !== null) {
410 if (pivot !== null && tomove !== null) {
411 tomove.detach();
411 tomove.detach();
412 pivot.before(tomove);
412 pivot.before(tomove);
413 this.select(i-1);
413 this.select(i-1);
414 };
414 };
415 };
415 };
416 this.dirty = true;
416 this.dirty = true;
417 return this;
417 return this;
418 }
418 }
419
419
420
420
421 Notebook.prototype.move_cell_down = function (index) {
421 Notebook.prototype.move_cell_down = function (index) {
422 var i = index || this.selected_index();
422 var i = index || this.selected_index();
423 if (i !== null && i < (this.ncells()-1) && i >= 0) {
423 if (i !== null && i < (this.ncells()-1) && i >= 0) {
424 var pivot = this.cell_elements().eq(i+1)
424 var pivot = this.cell_elements().eq(i+1)
425 var tomove = this.cell_elements().eq(i)
425 var tomove = this.cell_elements().eq(i)
426 if (pivot !== null && tomove !== null) {
426 if (pivot !== null && tomove !== null) {
427 tomove.detach();
427 tomove.detach();
428 pivot.after(tomove);
428 pivot.after(tomove);
429 this.select(i+1);
429 this.select(i+1);
430 };
430 };
431 };
431 };
432 this.dirty = true;
432 this.dirty = true;
433 return this;
433 return this;
434 }
434 }
435
435
436
436
437 Notebook.prototype.sort_cells = function () {
437 Notebook.prototype.sort_cells = function () {
438 var ncells = this.ncells();
438 var ncells = this.ncells();
439 var sindex = this.selected_index();
439 var sindex = this.selected_index();
440 var swapped;
440 var swapped;
441 do {
441 do {
442 swapped = false
442 swapped = false
443 for (var i=1; i<ncells; i++) {
443 for (var i=1; i<ncells; i++) {
444 current = this.cell_elements().eq(i).data("cell");
444 current = this.cell_elements().eq(i).data("cell");
445 previous = this.cell_elements().eq(i-1).data("cell");
445 previous = this.cell_elements().eq(i-1).data("cell");
446 if (previous.input_prompt_number > current.input_prompt_number) {
446 if (previous.input_prompt_number > current.input_prompt_number) {
447 this.move_cell_up(i);
447 this.move_cell_up(i);
448 swapped = true;
448 swapped = true;
449 };
449 };
450 };
450 };
451 } while (swapped);
451 } while (swapped);
452 this.select(sindex);
452 this.select(sindex);
453 return this;
453 return this;
454 };
454 };
455
455
456
456
457 Notebook.prototype.insert_code_cell_above = function (index) {
457 Notebook.prototype.insert_code_cell_above = function (index) {
458 // TODO: Bounds check for i
458 // TODO: Bounds check for i
459 var i = this.index_or_selected(index);
459 var i = this.index_or_selected(index);
460 var cell = new IPython.CodeCell(this);
460 var cell = new IPython.CodeCell(this);
461 cell.set_input_prompt();
461 cell.set_input_prompt();
462 this.insert_cell_above(cell, i);
462 this.insert_cell_above(cell, i);
463 this.select(this.find_cell_index(cell));
463 this.select(this.find_cell_index(cell));
464 return cell;
464 return cell;
465 }
465 }
466
466
467
467
468 Notebook.prototype.insert_code_cell_below = function (index) {
468 Notebook.prototype.insert_code_cell_below = function (index) {
469 // TODO: Bounds check for i
469 // TODO: Bounds check for i
470 var i = this.index_or_selected(index);
470 var i = this.index_or_selected(index);
471 var cell = new IPython.CodeCell(this);
471 var cell = new IPython.CodeCell(this);
472 cell.set_input_prompt();
472 cell.set_input_prompt();
473 this.insert_cell_below(cell, i);
473 this.insert_cell_below(cell, i);
474 this.select(this.find_cell_index(cell));
474 this.select(this.find_cell_index(cell));
475 return cell;
475 return cell;
476 }
476 }
477
477
478
478
479 Notebook.prototype.insert_html_cell_above = function (index) {
479 Notebook.prototype.insert_html_cell_above = function (index) {
480 // TODO: Bounds check for i
480 // TODO: Bounds check for i
481 var i = this.index_or_selected(index);
481 var i = this.index_or_selected(index);
482 var cell = new IPython.HTMLCell(this);
482 var cell = new IPython.HTMLCell(this);
483 cell.config_mathjax();
483 cell.config_mathjax();
484 this.insert_cell_above(cell, i);
484 this.insert_cell_above(cell, i);
485 this.select(this.find_cell_index(cell));
485 this.select(this.find_cell_index(cell));
486 return cell;
486 return cell;
487 }
487 }
488
488
489
489
490 Notebook.prototype.insert_html_cell_below = function (index) {
490 Notebook.prototype.insert_html_cell_below = function (index) {
491 // TODO: Bounds check for i
491 // TODO: Bounds check for i
492 var i = this.index_or_selected(index);
492 var i = this.index_or_selected(index);
493 var cell = new IPython.HTMLCell(this);
493 var cell = new IPython.HTMLCell(this);
494 cell.config_mathjax();
494 cell.config_mathjax();
495 this.insert_cell_below(cell, i);
495 this.insert_cell_below(cell, i);
496 this.select(this.find_cell_index(cell));
496 this.select(this.find_cell_index(cell));
497 return cell;
497 return cell;
498 }
498 }
499
499
500
500
501 Notebook.prototype.insert_markdown_cell_above = function (index) {
501 Notebook.prototype.insert_markdown_cell_above = function (index) {
502 // TODO: Bounds check for i
502 // TODO: Bounds check for i
503 var i = this.index_or_selected(index);
503 var i = this.index_or_selected(index);
504 var cell = new IPython.MarkdownCell(this);
504 var cell = new IPython.MarkdownCell(this);
505 cell.config_mathjax();
505 cell.config_mathjax();
506 this.insert_cell_above(cell, i);
506 this.insert_cell_above(cell, i);
507 this.select(this.find_cell_index(cell));
507 this.select(this.find_cell_index(cell));
508 return cell;
508 return cell;
509 }
509 }
510
510
511
511
512 Notebook.prototype.insert_markdown_cell_below = function (index) {
512 Notebook.prototype.insert_markdown_cell_below = function (index) {
513 // TODO: Bounds check for i
513 // TODO: Bounds check for i
514 var i = this.index_or_selected(index);
514 var i = this.index_or_selected(index);
515 var cell = new IPython.MarkdownCell(this);
515 var cell = new IPython.MarkdownCell(this);
516 cell.config_mathjax();
516 cell.config_mathjax();
517 this.insert_cell_below(cell, i);
517 this.insert_cell_below(cell, i);
518 this.select(this.find_cell_index(cell));
518 this.select(this.find_cell_index(cell));
519 return cell;
519 return cell;
520 }
520 }
521
521
522
522
523 Notebook.prototype.to_code = function (index) {
523 Notebook.prototype.to_code = function (index) {
524 // TODO: Bounds check for i
524 // TODO: Bounds check for i
525 var i = this.index_or_selected(index);
525 var i = this.index_or_selected(index);
526 var source_element = this.cell_elements().eq(i);
526 var source_element = this.cell_elements().eq(i);
527 var source_cell = source_element.data("cell");
527 var source_cell = source_element.data("cell");
528 if (source_cell instanceof IPython.HTMLCell ||
528 if (source_cell instanceof IPython.HTMLCell ||
529 source_cell instanceof IPython.MarkdownCell) {
529 source_cell instanceof IPython.MarkdownCell) {
530 this.insert_code_cell_below(i);
530 this.insert_code_cell_below(i);
531 var target_cell = this.cells()[i+1];
531 var target_cell = this.cells()[i+1];
532 target_cell.set_code(source_cell.get_source());
532 target_cell.set_code(source_cell.get_source());
533 source_element.remove();
533 source_element.remove();
534 target_cell.select();
534 target_cell.select();
535 };
535 };
536 this.dirty = true;
536 this.dirty = true;
537 };
537 };
538
538
539
539
540 Notebook.prototype.to_markdown = function (index) {
540 Notebook.prototype.to_markdown = function (index) {
541 // TODO: Bounds check for i
541 // TODO: Bounds check for i
542 var i = this.index_or_selected(index);
542 var i = this.index_or_selected(index);
543 var source_element = this.cell_elements().eq(i);
543 var source_element = this.cell_elements().eq(i);
544 var source_cell = source_element.data("cell");
544 var source_cell = source_element.data("cell");
545 var target_cell = null;
545 var target_cell = null;
546 if (source_cell instanceof IPython.CodeCell) {
546 if (source_cell instanceof IPython.CodeCell) {
547 this.insert_markdown_cell_below(i);
547 this.insert_markdown_cell_below(i);
548 var target_cell = this.cells()[i+1];
548 var target_cell = this.cells()[i+1];
549 var text = source_cell.get_code();
549 var text = source_cell.get_code();
550 } else if (source_cell instanceof IPython.HTMLCell) {
550 } else if (source_cell instanceof IPython.HTMLCell) {
551 this.insert_markdown_cell_below(i);
551 this.insert_markdown_cell_below(i);
552 var target_cell = this.cells()[i+1];
552 var target_cell = this.cells()[i+1];
553 var text = source_cell.get_source();
553 var text = source_cell.get_source();
554 if (text === source_cell.placeholder) {
554 if (text === source_cell.placeholder) {
555 text = target_cell.placeholder;
555 text = target_cell.placeholder;
556 }
556 }
557 }
557 }
558 if (target_cell !== null) {
558 if (target_cell !== null) {
559 if (text === "") {text = target_cell.placeholder;};
559 if (text === "") {text = target_cell.placeholder;};
560 target_cell.set_source(text);
560 target_cell.set_source(text);
561 source_element.remove();
561 source_element.remove();
562 target_cell.edit();
562 target_cell.edit();
563 }
563 }
564 this.dirty = true;
564 this.dirty = true;
565 };
565 };
566
566
567
567
568 Notebook.prototype.to_html = function (index) {
568 Notebook.prototype.to_html = function (index) {
569 // TODO: Bounds check for i
569 // TODO: Bounds check for i
570 var i = this.index_or_selected(index);
570 var i = this.index_or_selected(index);
571 var source_element = this.cell_elements().eq(i);
571 var source_element = this.cell_elements().eq(i);
572 var source_cell = source_element.data("cell");
572 var source_cell = source_element.data("cell");
573 var target_cell = null;
573 var target_cell = null;
574 if (source_cell instanceof IPython.CodeCell) {
574 if (source_cell instanceof IPython.CodeCell) {
575 this.insert_html_cell_below(i);
575 this.insert_html_cell_below(i);
576 var target_cell = this.cells()[i+1];
576 var target_cell = this.cells()[i+1];
577 var text = source_cell.get_code();
577 var text = source_cell.get_code();
578 } else if (source_cell instanceof IPython.MarkdownCell) {
578 } else if (source_cell instanceof IPython.MarkdownCell) {
579 this.insert_html_cell_below(i);
579 this.insert_html_cell_below(i);
580 var target_cell = this.cells()[i+1];
580 var target_cell = this.cells()[i+1];
581 var text = source_cell.get_source();
581 var text = source_cell.get_source();
582 if (text === source_cell.placeholder) {
582 if (text === source_cell.placeholder) {
583 text = target_cell.placeholder;
583 text = target_cell.placeholder;
584 }
584 }
585 }
585 }
586 if (target_cell !== null) {
586 if (target_cell !== null) {
587 if (text === "") {text = target_cell.placeholder;};
587 if (text === "") {text = target_cell.placeholder;};
588 target_cell.set_source(text);
588 target_cell.set_source(text);
589 source_element.remove();
589 source_element.remove();
590 target_cell.edit();
590 target_cell.edit();
591 }
591 }
592 this.dirty = true;
592 this.dirty = true;
593 };
593 };
594
594
595
595
596 // Cell collapsing and output clearing
596 // Cell collapsing and output clearing
597
597
598 Notebook.prototype.collapse = function (index) {
598 Notebook.prototype.collapse = function (index) {
599 var i = this.index_or_selected(index);
599 var i = this.index_or_selected(index);
600 this.cells()[i].collapse();
600 this.cells()[i].collapse();
601 this.dirty = true;
601 this.dirty = true;
602 };
602 };
603
603
604
604
605 Notebook.prototype.expand = function (index) {
605 Notebook.prototype.expand = function (index) {
606 var i = this.index_or_selected(index);
606 var i = this.index_or_selected(index);
607 this.cells()[i].expand();
607 this.cells()[i].expand();
608 this.dirty = true;
608 this.dirty = true;
609 };
609 };
610
610
611
611
612 Notebook.prototype.toggle_output = function (index) {
612 Notebook.prototype.toggle_output = function (index) {
613 var i = this.index_or_selected(index);
613 var i = this.index_or_selected(index);
614 this.cells()[i].toggle_output();
614 this.cells()[i].toggle_output();
615 this.dirty = true;
615 this.dirty = true;
616 };
616 };
617
617
618
618
619 Notebook.prototype.set_autoindent = function (state) {
619 Notebook.prototype.set_autoindent = function (state) {
620 var cells = this.cells();
620 var cells = this.cells();
621 len = cells.length;
621 len = cells.length;
622 for (var i=0; i<len; i++) {
622 for (var i=0; i<len; i++) {
623 cells[i].set_autoindent(state)
623 cells[i].set_autoindent(state)
624 };
624 };
625 };
625 };
626
626
627
627
628 Notebook.prototype.clear_all_output = function () {
628 Notebook.prototype.clear_all_output = function () {
629 var ncells = this.ncells();
629 var ncells = this.ncells();
630 var cells = this.cells();
630 var cells = this.cells();
631 for (var i=0; i<ncells; i++) {
631 for (var i=0; i<ncells; i++) {
632 if (cells[i] instanceof IPython.CodeCell) {
632 if (cells[i] instanceof IPython.CodeCell) {
633 cells[i].clear_output(true,true,true);
633 cells[i].clear_output(true,true,true);
634 }
634 }
635 };
635 };
636 this.dirty = true;
636 this.dirty = true;
637 };
637 };
638
638
639 // Other cell functions: line numbers, ...
639 // Other cell functions: line numbers, ...
640
640
641 Notebook.prototype.cell_toggle_line_numbers = function() {
641 Notebook.prototype.cell_toggle_line_numbers = function() {
642 this.selected_cell().toggle_line_numbers()
642 this.selected_cell().toggle_line_numbers()
643 };
643 };
644
644
645 // Kernel related things
645 // Kernel related things
646
646
647 Notebook.prototype.start_kernel = function () {
647 Notebook.prototype.start_kernel = function () {
648 this.kernel = new IPython.Kernel();
648 this.kernel = new IPython.Kernel();
649 var notebook_id = IPython.save_widget.get_notebook_id();
649 var notebook_id = IPython.save_widget.get_notebook_id();
650 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
650 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
651 };
651 };
652
652
653
653
654 Notebook.prototype.restart_kernel = function () {
654 Notebook.prototype.restart_kernel = function () {
655 var that = this;
655 var that = this;
656 var notebook_id = IPython.save_widget.get_notebook_id();
656 var notebook_id = IPython.save_widget.get_notebook_id();
657
657
658 var dialog = $('<div/>');
658 var dialog = $('<div/>');
659 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
659 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
660 $(document).append(dialog);
660 $(document).append(dialog);
661 dialog.dialog({
661 dialog.dialog({
662 resizable: false,
662 resizable: false,
663 modal: true,
663 modal: true,
664 title: "Restart kernel or continue running?",
664 title: "Restart kernel or continue running?",
665 buttons : {
665 buttons : {
666 "Restart": function () {
666 "Restart": function () {
667 that.kernel.restart($.proxy(that.kernel_started, that));
667 that.kernel.restart($.proxy(that.kernel_started, that));
668 $(this).dialog('close');
668 $(this).dialog('close');
669 },
669 },
670 "Continue running": function () {
670 "Continue running": function () {
671 $(this).dialog('close');
671 $(this).dialog('close');
672 }
672 }
673 }
673 }
674 });
674 });
675 };
675 };
676
676
677
677
678 Notebook.prototype.kernel_started = function () {
678 Notebook.prototype.kernel_started = function () {
679 console.log("Kernel started: ", this.kernel.kernel_id);
679 console.log("Kernel started: ", this.kernel.kernel_id);
680 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
680 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
681 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
681 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
682 };
682 };
683
683
684
684
685 Notebook.prototype.handle_shell_reply = function (e) {
685 Notebook.prototype.handle_shell_reply = function (e) {
686 reply = $.parseJSON(e.data);
686 reply = $.parseJSON(e.data);
687 var header = reply.header;
687 var header = reply.header;
688 var content = reply.content;
688 var content = reply.content;
689 var msg_type = header.msg_type;
689 var msg_type = header.msg_type;
690 // console.log(reply);
690 // console.log(reply);
691 var cell = this.cell_for_msg(reply.parent_header.msg_id);
691 var cell = this.cell_for_msg(reply.parent_header.msg_id);
692 if (msg_type === "execute_reply") {
692 if (msg_type === "execute_reply") {
693 cell.set_input_prompt(content.execution_count);
693 cell.set_input_prompt(content.execution_count);
694 this.dirty = true;
694 this.dirty = true;
695 } else if (msg_type === "complete_reply") {
695 } else if (msg_type === "complete_reply") {
696 cell.finish_completing(content.matched_text, content.matches);
696 cell.finish_completing(content.matched_text, content.matches);
697 };
697 };
698 var payload = content.payload || [];
698 var payload = content.payload || [];
699 this.handle_payload(cell, payload);
699 this.handle_payload(cell, payload);
700 };
700 };
701
701
702
702
703 Notebook.prototype.handle_payload = function (cell, payload) {
703 Notebook.prototype.handle_payload = function (cell, payload) {
704 var l = payload.length;
704 var l = payload.length;
705 for (var i=0; i<l; i++) {
705 for (var i=0; i<l; i++) {
706 if (payload[i].source === 'IPython.zmq.page.page') {
706 if (payload[i].source === 'IPython.zmq.page.page') {
707 if (payload[i].text.trim() !== '') {
707 if (payload[i].text.trim() !== '') {
708 IPython.pager.clear();
708 IPython.pager.clear();
709 IPython.pager.expand();
709 IPython.pager.expand();
710 IPython.pager.append_text(payload[i].text);
710 IPython.pager.append_text(payload[i].text);
711 }
711 }
712 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
712 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
713 var index = this.find_cell_index(cell);
713 var index = this.find_cell_index(cell);
714 var new_cell = this.insert_code_cell_below(index);
714 var new_cell = this.insert_code_cell_below(index);
715 new_cell.set_code(payload[i].text);
715 new_cell.set_code(payload[i].text);
716 this.dirty = true;
716 this.dirty = true;
717 }
717 }
718 };
718 };
719 };
719 };
720
720
721
721
722 Notebook.prototype.handle_iopub_reply = function (e) {
722 Notebook.prototype.handle_iopub_reply = function (e) {
723 reply = $.parseJSON(e.data);
723 reply = $.parseJSON(e.data);
724 var content = reply.content;
724 var content = reply.content;
725 // console.log(reply);
725 // console.log(reply);
726 var msg_type = reply.header.msg_type;
726 var msg_type = reply.header.msg_type;
727 var cell = this.cell_for_msg(reply.parent_header.msg_id);
727 var cell = this.cell_for_msg(reply.parent_header.msg_id);
728 var output_types = ['stream','display_data','pyout','pyerr'];
728 var output_types = ['stream','display_data','pyout','pyerr'];
729 if (output_types.indexOf(msg_type) >= 0) {
729 if (output_types.indexOf(msg_type) >= 0) {
730 this.handle_output(cell, msg_type, content);
730 this.handle_output(cell, msg_type, content);
731 } else if (msg_type === 'status') {
731 } else if (msg_type === 'status') {
732 if (content.execution_state === 'busy') {
732 if (content.execution_state === 'busy') {
733 IPython.kernel_status_widget.status_busy();
733 IPython.kernel_status_widget.status_busy();
734 } else if (content.execution_state === 'idle') {
734 } else if (content.execution_state === 'idle') {
735 IPython.kernel_status_widget.status_idle();
735 IPython.kernel_status_widget.status_idle();
736 } else if (content.execution_state === 'dead') {
736 } else if (content.execution_state === 'dead') {
737 this.handle_status_dead();
737 this.handle_status_dead();
738 };
738 };
739 } else if (msg_type === 'clear_output') {
739 } else if (msg_type === 'clear_output') {
740 cell.clear_output(content.stdout, content.stderr, content.other);
740 cell.clear_output(content.stdout, content.stderr, content.other);
741 };
741 };
742 };
742 };
743
743
744
744
745 Notebook.prototype.handle_status_dead = function () {
745 Notebook.prototype.handle_status_dead = function () {
746 var that = this;
746 var that = this;
747 this.kernel.stop_channels();
747 this.kernel.stop_channels();
748 var dialog = $('<div/>');
748 var dialog = $('<div/>');
749 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
749 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
750 $(document).append(dialog);
750 $(document).append(dialog);
751 dialog.dialog({
751 dialog.dialog({
752 resizable: false,
752 resizable: false,
753 modal: true,
753 modal: true,
754 title: "Dead kernel",
754 title: "Dead kernel",
755 buttons : {
755 buttons : {
756 "Restart": function () {
756 "Restart": function () {
757 that.start_kernel();
757 that.start_kernel();
758 $(this).dialog('close');
758 $(this).dialog('close');
759 },
759 },
760 "Continue running": function () {
760 "Continue running": function () {
761 $(this).dialog('close');
761 $(this).dialog('close');
762 }
762 }
763 }
763 }
764 });
764 });
765 };
765 };
766
766
767
767
768 Notebook.prototype.handle_output = function (cell, msg_type, content) {
768 Notebook.prototype.handle_output = function (cell, msg_type, content) {
769 var json = {};
769 var json = {};
770 json.output_type = msg_type;
770 json.output_type = msg_type;
771 if (msg_type === "stream") {
771 if (msg_type === "stream") {
772 json.text = utils.fixConsole(content.data);
772 json.text = utils.fixConsole(content.data);
773 json.stream = content.name;
773 json.stream = content.name;
774 } else if (msg_type === "display_data") {
774 } else if (msg_type === "display_data") {
775 json = this.convert_mime_types(json, content.data);
775 json = this.convert_mime_types(json, content.data);
776 } else if (msg_type === "pyout") {
776 } else if (msg_type === "pyout") {
777 json.prompt_number = content.execution_count;
777 json.prompt_number = content.execution_count;
778 json = this.convert_mime_types(json, content.data);
778 json = this.convert_mime_types(json, content.data);
779 } else if (msg_type === "pyerr") {
779 } else if (msg_type === "pyerr") {
780 json.ename = content.ename;
780 json.ename = content.ename;
781 json.evalue = content.evalue;
781 json.evalue = content.evalue;
782 var traceback = [];
782 var traceback = [];
783 for (var i=0; i<content.traceback.length; i++) {
783 for (var i=0; i<content.traceback.length; i++) {
784 traceback.push(utils.fixConsole(content.traceback[i]));
784 traceback.push(utils.fixConsole(content.traceback[i]));
785 }
785 }
786 json.traceback = traceback;
786 json.traceback = traceback;
787 };
787 };
788 cell.append_output(json);
788 cell.append_output(json);
789 this.dirty = true;
789 this.dirty = true;
790 };
790 };
791
791
792
792
793 Notebook.prototype.convert_mime_types = function (json, data) {
793 Notebook.prototype.convert_mime_types = function (json, data) {
794 if (data['text/plain'] !== undefined) {
794 if (data['text/plain'] !== undefined) {
795 json.text = utils.fixConsole(data['text/plain']);
795 json.text = utils.fixConsole(data['text/plain']);
796 };
796 };
797 if (data['text/html'] !== undefined) {
797 if (data['text/html'] !== undefined) {
798 json.html = data['text/html'];
798 json.html = data['text/html'];
799 };
799 };
800 if (data['image/svg+xml'] !== undefined) {
800 if (data['image/svg+xml'] !== undefined) {
801 json.svg = data['image/svg+xml'];
801 json.svg = data['image/svg+xml'];
802 };
802 };
803 if (data['image/png'] !== undefined) {
803 if (data['image/png'] !== undefined) {
804 json.png = data['image/png'];
804 json.png = data['image/png'];
805 };
805 };
806 if (data['image/jpeg'] !== undefined) {
806 if (data['image/jpeg'] !== undefined) {
807 json.jpeg = data['image/jpeg'];
807 json.jpeg = data['image/jpeg'];
808 };
808 };
809 if (data['text/latex'] !== undefined) {
809 if (data['text/latex'] !== undefined) {
810 json.latex = data['text/latex'];
810 json.latex = data['text/latex'];
811 };
811 };
812 if (data['application/json'] !== undefined) {
812 if (data['application/json'] !== undefined) {
813 json.json = data['application/json'];
813 json.json = data['application/json'];
814 };
814 };
815 if (data['application/javascript'] !== undefined) {
815 if (data['application/javascript'] !== undefined) {
816 json.javascript = data['application/javascript'];
816 json.javascript = data['application/javascript'];
817 }
817 }
818 return json;
818 return json;
819 };
819 };
820
820
821
821
822 Notebook.prototype.execute_selected_cell = function (options) {
822 Notebook.prototype.execute_selected_cell = function (options) {
823 // add_new: should a new cell be added if we are at the end of the nb
823 // add_new: should a new cell be added if we are at the end of the nb
824 // terminal: execute in terminal mode, which stays in the current cell
824 // terminal: execute in terminal mode, which stays in the current cell
825 default_options = {terminal: false, add_new: true}
825 default_options = {terminal: false, add_new: true}
826 $.extend(default_options, options)
826 $.extend(default_options, options)
827 var that = this;
827 var that = this;
828 var cell = that.selected_cell();
828 var cell = that.selected_cell();
829 var cell_index = that.find_cell_index(cell);
829 var cell_index = that.find_cell_index(cell);
830 if (cell instanceof IPython.CodeCell) {
830 if (cell instanceof IPython.CodeCell) {
831 cell.clear_output(true, true, true);
831 cell.clear_output(true, true, true);
832 var code = cell.get_code();
832 var code = cell.get_code();
833 var msg_id = that.kernel.execute(cell.get_code());
833 var msg_id = that.kernel.execute(cell.get_code());
834 that.msg_cell_map[msg_id] = cell.cell_id;
834 that.msg_cell_map[msg_id] = cell.cell_id;
835 } else if (cell instanceof IPython.HTMLCell) {
835 } else if (cell instanceof IPython.HTMLCell) {
836 cell.render();
836 cell.render();
837 }
837 }
838 if (default_options.terminal) {
838 if (default_options.terminal) {
839 cell.select_all();
839 cell.select_all();
840 } else {
840 } else {
841 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
841 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
842 that.insert_code_cell_below();
842 that.insert_code_cell_below();
843 // If we are adding a new cell at the end, scroll down to show it.
843 // If we are adding a new cell at the end, scroll down to show it.
844 that.scroll_to_bottom();
844 that.scroll_to_bottom();
845 } else {
845 } else {
846 that.select(cell_index+1);
846 that.select(cell_index+1);
847 };
847 };
848 };
848 };
849 this.dirty = true;
849 this.dirty = true;
850 };
850 };
851
851
852
852
853 Notebook.prototype.execute_all_cells = function () {
853 Notebook.prototype.execute_all_cells = function () {
854 var ncells = this.ncells();
854 var ncells = this.ncells();
855 for (var i=0; i<ncells; i++) {
855 for (var i=0; i<ncells; i++) {
856 this.select(i);
856 this.select(i);
857 this.execute_selected_cell({add_new:false});
857 this.execute_selected_cell({add_new:false});
858 };
858 };
859 this.scroll_to_bottom();
859 this.scroll_to_bottom();
860 };
860 };
861
861
862
862
863 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
863 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
864 var msg_id = this.kernel.complete(line, cursor_pos);
864 var msg_id = this.kernel.complete(line, cursor_pos);
865 this.msg_cell_map[msg_id] = cell.cell_id;
865 this.msg_cell_map[msg_id] = cell.cell_id;
866 };
866 };
867
867
868 // Persistance and loading
868 // Persistance and loading
869
869
870
870
871 Notebook.prototype.fromJSON = function (data) {
871 Notebook.prototype.fromJSON = function (data) {
872 var ncells = this.ncells();
872 var ncells = this.ncells();
873 for (var i=0; i<ncells; i++) {
873 for (var i=0; i<ncells; i++) {
874 // Always delete cell 0 as they get renumbered as they are deleted.
874 // Always delete cell 0 as they get renumbered as they are deleted.
875 this.delete_cell(0);
875 this.delete_cell(0);
876 };
876 };
877 // Save the metadata
877 // Save the metadata
878 this.metadata = data.metadata;
878 this.metadata = data.metadata;
879 // Only handle 1 worksheet for now.
879 // Only handle 1 worksheet for now.
880 var worksheet = data.worksheets[0];
880 var worksheet = data.worksheets[0];
881 if (worksheet !== undefined) {
881 if (worksheet !== undefined) {
882 var new_cells = worksheet.cells;
882 var new_cells = worksheet.cells;
883 ncells = new_cells.length;
883 ncells = new_cells.length;
884 var cell_data = null;
884 var cell_data = null;
885 var new_cell = null;
885 var new_cell = null;
886 for (var i=0; i<ncells; i++) {
886 for (var i=0; i<ncells; i++) {
887 cell_data = new_cells[i];
887 cell_data = new_cells[i];
888 if (cell_data.cell_type == 'code') {
888 if (cell_data.cell_type == 'code') {
889 new_cell = this.insert_code_cell_below();
889 new_cell = this.insert_code_cell_below();
890 new_cell.fromJSON(cell_data);
890 new_cell.fromJSON(cell_data);
891 } else if (cell_data.cell_type === 'html') {
891 } else if (cell_data.cell_type === 'html') {
892 new_cell = this.insert_html_cell_below();
892 new_cell = this.insert_html_cell_below();
893 new_cell.fromJSON(cell_data);
893 new_cell.fromJSON(cell_data);
894 } else if (cell_data.cell_type === 'markdown') {
894 } else if (cell_data.cell_type === 'markdown') {
895 new_cell = this.insert_markdown_cell_below();
895 new_cell = this.insert_markdown_cell_below();
896 new_cell.fromJSON(cell_data);
896 new_cell.fromJSON(cell_data);
897 };
897 };
898 };
898 };
899 };
899 };
900 };
900 };
901
901
902
902
903 Notebook.prototype.toJSON = function () {
903 Notebook.prototype.toJSON = function () {
904 var cells = this.cells();
904 var cells = this.cells();
905 var ncells = cells.length;
905 var ncells = cells.length;
906 cell_array = new Array(ncells);
906 cell_array = new Array(ncells);
907 for (var i=0; i<ncells; i++) {
907 for (var i=0; i<ncells; i++) {
908 cell_array[i] = cells[i].toJSON();
908 cell_array[i] = cells[i].toJSON();
909 };
909 };
910 data = {
910 data = {
911 // Only handle 1 worksheet for now.
911 // Only handle 1 worksheet for now.
912 worksheets : [{cells:cell_array}],
912 worksheets : [{cells:cell_array}],
913 metadata : this.metadata
913 metadata : this.metadata
914 }
914 }
915 return data
915 return data
916 };
916 };
917
917
918 Notebook.prototype.save_notebook = function () {
918 Notebook.prototype.save_notebook = function () {
919 if (IPython.save_widget.test_notebook_name()) {
919 if (IPython.save_widget.test_notebook_name()) {
920 var notebook_id = IPython.save_widget.get_notebook_id();
920 var notebook_id = IPython.save_widget.get_notebook_id();
921 var nbname = IPython.save_widget.get_notebook_name();
921 var nbname = IPython.save_widget.get_notebook_name();
922 // We may want to move the name/id/nbformat logic inside toJSON?
922 // We may want to move the name/id/nbformat logic inside toJSON?
923 var data = this.toJSON();
923 var data = this.toJSON();
924 data.metadata.name = nbname;
924 data.metadata.name = nbname;
925 data.nbformat = 2;
925 data.nbformat = 2;
926 // We do the call with settings so we can set cache to false.
926 // We do the call with settings so we can set cache to false.
927 var settings = {
927 var settings = {
928 processData : false,
928 processData : false,
929 cache : false,
929 cache : false,
930 type : "PUT",
930 type : "PUT",
931 data : JSON.stringify(data),
931 data : JSON.stringify(data),
932 headers : {'Content-Type': 'application/json'},
932 headers : {'Content-Type': 'application/json'},
933 success : $.proxy(this.notebook_saved,this),
933 success : $.proxy(this.notebook_saved,this),
934 error : $.proxy(this.notebook_save_failed,this)
934 error : $.proxy(this.notebook_save_failed,this)
935 };
935 };
936 IPython.save_widget.status_saving();
936 IPython.save_widget.status_saving();
937 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
937 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
938 $.ajax(url, settings);
938 $.ajax(url, settings);
939 };
939 };
940 };
940 };
941
941
942
942
943 Notebook.prototype.notebook_saved = function (data, status, xhr) {
943 Notebook.prototype.notebook_saved = function (data, status, xhr) {
944 this.dirty = false;
944 this.dirty = false;
945 IPython.save_widget.notebook_saved();
945 IPython.save_widget.notebook_saved();
946 IPython.save_widget.status_save();
946 IPython.save_widget.status_save();
947 }
947 }
948
948
949
949
950 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
950 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
951 // Notify the user and reset the save button
951 // Notify the user and reset the save button
952 // TODO: Handle different types of errors (timeout etc.)
952 // TODO: Handle different types of errors (timeout etc.)
953 alert('An unexpected error occured while saving the notebook.');
953 alert('An unexpected error occured while saving the notebook.');
954 IPython.save_widget.reset_status();
954 IPython.save_widget.reset_status();
955 }
955 }
956
956
957
957
958 Notebook.prototype.load_notebook = function (callback) {
958 Notebook.prototype.load_notebook = function (callback) {
959 var that = this;
959 var that = this;
960 var notebook_id = IPython.save_widget.get_notebook_id();
960 var notebook_id = IPython.save_widget.get_notebook_id();
961 // We do the call with settings so we can set cache to false.
961 // We do the call with settings so we can set cache to false.
962 var settings = {
962 var settings = {
963 processData : false,
963 processData : false,
964 cache : false,
964 cache : false,
965 type : "GET",
965 type : "GET",
966 dataType : "json",
966 dataType : "json",
967 success : function (data, status, xhr) {
967 success : function (data, status, xhr) {
968 that.notebook_loaded(data, status, xhr);
968 that.notebook_loaded(data, status, xhr);
969 if (callback !== undefined) {
969 if (callback !== undefined) {
970 callback();
970 callback();
971 };
971 };
972 }
972 }
973 };
973 };
974 IPython.save_widget.status_loading();
974 IPython.save_widget.status_loading();
975 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
975 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
976 $.ajax(url, settings);
976 $.ajax(url, settings);
977 }
977 }
978
978
979
979
980 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
980 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
981 var allowed = xhr.getResponseHeader('Allow');
981 var allowed = xhr.getResponseHeader('Allow');
982 if (allowed && allowed.indexOf('PUT') == -1){
983 this.read_only = true;
984 // unhide login button if it's relevant
985 $('span#login_widget').removeClass('hidden');
986 }else{
987 this.read_only = false;
988 }
989 this.fromJSON(data);
982 this.fromJSON(data);
990 if (this.ncells() === 0) {
983 if (this.ncells() === 0) {
991 this.insert_code_cell_below();
984 this.insert_code_cell_below();
992 };
985 };
993 IPython.save_widget.status_save();
986 IPython.save_widget.status_save();
994 IPython.save_widget.set_notebook_name(data.metadata.name);
987 IPython.save_widget.set_notebook_name(data.metadata.name);
995 this.dirty = false;
988 this.dirty = false;
996 if (this.read_only) {
989 if (! this.read_only) {
997 this.handle_read_only();
998 }else{
999 this.start_kernel();
990 this.start_kernel();
1000 }
991 }
1001 // fromJSON always selects the last cell inserted. We need to wait
992 // fromJSON always selects the last cell inserted. We need to wait
1002 // until that is done before scrolling to the top.
993 // until that is done before scrolling to the top.
1003 setTimeout(function () {
994 setTimeout(function () {
1004 IPython.notebook.select(0);
995 IPython.notebook.select(0);
1005 IPython.notebook.scroll_to_top();
996 IPython.notebook.scroll_to_top();
1006 }, 50);
997 }, 50);
1007 };
998 };
1008
999
1009
1010 Notebook.prototype.handle_read_only = function(){
1011 IPython.left_panel.collapse();
1012 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
1013 $('button#new_notebook').addClass('hidden');
1014 $('div#cell_section').addClass('hidden');
1015 $('div#kernel_section').addClass('hidden');
1016 }
1017
1018
1019 IPython.Notebook = Notebook;
1000 IPython.Notebook = Notebook;
1020
1001
1021
1002
1022 return IPython;
1003 return IPython;
1023
1004
1024 }(IPython));
1005 }(IPython));
1025
1006
@@ -1,257 +1,248 b''
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 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var NotebookList = function (selector) {
14 var NotebookList = function (selector) {
15 this.selector = selector;
15 this.selector = selector;
16 if (this.selector !== undefined) {
16 if (this.selector !== undefined) {
17 this.element = $(selector);
17 this.element = $(selector);
18 this.style();
18 this.style();
19 this.bind_events();
19 this.bind_events();
20 }
20 }
21 };
21 };
22
22
23 NotebookList.prototype.style = function () {
23 NotebookList.prototype.style = function () {
24 this.element.addClass('ui-widget ui-widget-content');
24 this.element.addClass('ui-widget ui-widget-content');
25 $('div#project_name').addClass('ui-widget ui-widget-header');
25 $('div#project_name').addClass('ui-widget ui-widget-header');
26 };
26 };
27
27
28
28
29 NotebookList.prototype.bind_events = function () {
29 NotebookList.prototype.bind_events = function () {
30 var that = this;
30 var that = this;
31 this.element.bind('dragover', function () {
31 this.element.bind('dragover', function () {
32 return false;
32 return false;
33 });
33 });
34 this.element.bind('drop', function (event) {
34 this.element.bind('drop', function (event) {
35 var files = event.originalEvent.dataTransfer.files;
35 var files = event.originalEvent.dataTransfer.files;
36 for (var i = 0, f; f = files[i]; i++) {
36 for (var i = 0, f; f = files[i]; i++) {
37 var reader = new FileReader();
37 var reader = new FileReader();
38 reader.readAsText(f);
38 reader.readAsText(f);
39 var fname = f.name.split('.');
39 var fname = f.name.split('.');
40 var nbname = fname.slice(0,-1).join('.');
40 var nbname = fname.slice(0,-1).join('.');
41 var nbformat = fname.slice(-1)[0];
41 var nbformat = fname.slice(-1)[0];
42 if (nbformat === 'ipynb') {nbformat = 'json';};
42 if (nbformat === 'ipynb') {nbformat = 'json';};
43 if (nbformat === 'py' || nbformat === 'json') {
43 if (nbformat === 'py' || nbformat === 'json') {
44 var item = that.new_notebook_item(0);
44 var item = that.new_notebook_item(0);
45 that.add_name_input(nbname, item);
45 that.add_name_input(nbname, item);
46 item.data('nbformat', nbformat);
46 item.data('nbformat', nbformat);
47 // Store the notebook item in the reader so we can use it later
47 // Store the notebook item in the reader so we can use it later
48 // to know which item it belongs to.
48 // to know which item it belongs to.
49 $(reader).data('item', item);
49 $(reader).data('item', item);
50 reader.onload = function (event) {
50 reader.onload = function (event) {
51 var nbitem = $(event.target).data('item');
51 var nbitem = $(event.target).data('item');
52 that.add_notebook_data(event.target.result, nbitem);
52 that.add_notebook_data(event.target.result, nbitem);
53 that.add_upload_button(nbitem);
53 that.add_upload_button(nbitem);
54 };
54 };
55 };
55 };
56 }
56 }
57 return false;
57 return false;
58 });
58 });
59 };
59 };
60
60
61
61
62 NotebookList.prototype.load_list = function () {
62 NotebookList.prototype.load_list = function () {
63 var settings = {
63 var settings = {
64 processData : false,
64 processData : false,
65 cache : false,
65 cache : false,
66 type : "GET",
66 type : "GET",
67 dataType : "json",
67 dataType : "json",
68 success : $.proxy(this.list_loaded, this)
68 success : $.proxy(this.list_loaded, this)
69 };
69 };
70 var url = $('body').data('baseProjectUrl') + 'notebooks'
70 var url = $('body').data('baseProjectUrl') + 'notebooks'
71 $.ajax(url, settings);
71 $.ajax(url, settings);
72 };
72 };
73
73
74
74
75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
76 var allowed = xhr.getResponseHeader('Allow');
77 if (allowed && allowed.indexOf('PUT') == -1){
78 this.read_only = true;
79 $('#new_notebook').addClass('hidden');
80 // unhide login button if it's relevant
81 $('span#login_widget').removeClass('hidden');
82 }else{
83 this.read_only = false;
84 }
85 var len = data.length;
76 var len = data.length;
86 // Todo: remove old children
77 // Todo: remove old children
87 for (var i=0; i<len; i++) {
78 for (var i=0; i<len; i++) {
88 var notebook_id = data[i].notebook_id;
79 var notebook_id = data[i].notebook_id;
89 var nbname = data[i].name;
80 var nbname = data[i].name;
90 var item = this.new_notebook_item(i);
81 var item = this.new_notebook_item(i);
91 this.add_link(notebook_id, nbname, item);
82 this.add_link(notebook_id, nbname, item);
92 if (!this.read_only){
83 if (!IPython.read_only){
93 // hide delete buttons when readonly
84 // hide delete buttons when readonly
94 this.add_delete_button(item);
85 this.add_delete_button(item);
95 }
86 }
96 };
87 };
97 };
88 };
98
89
99
90
100 NotebookList.prototype.new_notebook_item = function (index) {
91 NotebookList.prototype.new_notebook_item = function (index) {
101 var item = $('<div/>');
92 var item = $('<div/>');
102 item.addClass('notebook_item ui-widget ui-widget-content ui-helper-clearfix');
93 item.addClass('notebook_item ui-widget ui-widget-content ui-helper-clearfix');
103 var item_name = $('<span/>').addClass('item_name');
94 var item_name = $('<span/>').addClass('item_name');
104
95
105 item.append(item_name);
96 item.append(item_name);
106 if (index === -1) {
97 if (index === -1) {
107 this.element.append(item);
98 this.element.append(item);
108 } else {
99 } else {
109 this.element.children().eq(index).after(item);
100 this.element.children().eq(index).after(item);
110 }
101 }
111 return item;
102 return item;
112 };
103 };
113
104
114
105
115 NotebookList.prototype.add_link = function (notebook_id, nbname, item) {
106 NotebookList.prototype.add_link = function (notebook_id, nbname, item) {
116 item.data('nbname', nbname);
107 item.data('nbname', nbname);
117 item.data('notebook_id', notebook_id);
108 item.data('notebook_id', notebook_id);
118 var new_item_name = $('<span/>').addClass('item_name');
109 var new_item_name = $('<span/>').addClass('item_name');
119 new_item_name.append(
110 new_item_name.append(
120 $('<a/>').
111 $('<a/>').
121 attr('href', $('body').data('baseProjectURL')+notebook_id).
112 attr('href', $('body').data('baseProjectURL')+notebook_id).
122 attr('target','_blank').
113 attr('target','_blank').
123 text(nbname)
114 text(nbname)
124 );
115 );
125 var e = item.find('.item_name');
116 var e = item.find('.item_name');
126 if (e.length === 0) {
117 if (e.length === 0) {
127 item.append(new_item_name);
118 item.append(new_item_name);
128 } else {
119 } else {
129 e.replaceWith(new_item_name);
120 e.replaceWith(new_item_name);
130 };
121 };
131 };
122 };
132
123
133
124
134 NotebookList.prototype.add_name_input = function (nbname, item) {
125 NotebookList.prototype.add_name_input = function (nbname, item) {
135 item.data('nbname', nbname);
126 item.data('nbname', nbname);
136 var new_item_name = $('<span/>').addClass('item_name');
127 var new_item_name = $('<span/>').addClass('item_name');
137 new_item_name.append(
128 new_item_name.append(
138 $('<input/>').addClass('ui-widget ui-widget-content').
129 $('<input/>').addClass('ui-widget ui-widget-content').
139 attr('value', nbname).
130 attr('value', nbname).
140 attr('size', '30').
131 attr('size', '30').
141 attr('type', 'text')
132 attr('type', 'text')
142 );
133 );
143 var e = item.find('.item_name');
134 var e = item.find('.item_name');
144 if (e.length === 0) {
135 if (e.length === 0) {
145 item.append(new_item_name);
136 item.append(new_item_name);
146 } else {
137 } else {
147 e.replaceWith(new_item_name);
138 e.replaceWith(new_item_name);
148 };
139 };
149 };
140 };
150
141
151
142
152 NotebookList.prototype.add_notebook_data = function (data, item) {
143 NotebookList.prototype.add_notebook_data = function (data, item) {
153 item.data('nbdata',data);
144 item.data('nbdata',data);
154 };
145 };
155
146
156
147
157 NotebookList.prototype.add_delete_button = function (item) {
148 NotebookList.prototype.add_delete_button = function (item) {
158 var new_buttons = $('<span/>').addClass('item_buttons');
149 var new_buttons = $('<span/>').addClass('item_buttons');
159 var delete_button = $('<button>Delete</button>').button().
150 var delete_button = $('<button>Delete</button>').button().
160 click(function (e) {
151 click(function (e) {
161 // $(this) is the button that was clicked.
152 // $(this) is the button that was clicked.
162 var that = $(this);
153 var that = $(this);
163 // We use the nbname and notebook_id from the parent notebook_item element's
154 // We use the nbname and notebook_id from the parent notebook_item element's
164 // data because the outer scopes values change as we iterate through the loop.
155 // data because the outer scopes values change as we iterate through the loop.
165 var parent_item = that.parents('div.notebook_item');
156 var parent_item = that.parents('div.notebook_item');
166 var nbname = parent_item.data('nbname');
157 var nbname = parent_item.data('nbname');
167 var notebook_id = parent_item.data('notebook_id');
158 var notebook_id = parent_item.data('notebook_id');
168 var dialog = $('<div/>');
159 var dialog = $('<div/>');
169 dialog.html('Are you sure you want to permanently delete the notebook: ' + nbname + '?');
160 dialog.html('Are you sure you want to permanently delete the notebook: ' + nbname + '?');
170 parent_item.append(dialog);
161 parent_item.append(dialog);
171 dialog.dialog({
162 dialog.dialog({
172 resizable: false,
163 resizable: false,
173 modal: true,
164 modal: true,
174 title: "Delete notebook",
165 title: "Delete notebook",
175 buttons : {
166 buttons : {
176 "Delete": function () {
167 "Delete": function () {
177 var settings = {
168 var settings = {
178 processData : false,
169 processData : false,
179 cache : false,
170 cache : false,
180 type : "DELETE",
171 type : "DELETE",
181 dataType : "json",
172 dataType : "json",
182 success : function (data, status, xhr) {
173 success : function (data, status, xhr) {
183 parent_item.remove();
174 parent_item.remove();
184 }
175 }
185 };
176 };
186 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
177 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
187 $.ajax(url, settings);
178 $.ajax(url, settings);
188 $(this).dialog('close');
179 $(this).dialog('close');
189 },
180 },
190 "Cancel": function () {
181 "Cancel": function () {
191 $(this).dialog('close');
182 $(this).dialog('close');
192 }
183 }
193 }
184 }
194 });
185 });
195 });
186 });
196 new_buttons.append(delete_button);
187 new_buttons.append(delete_button);
197 var e = item.find('.item_buttons');
188 var e = item.find('.item_buttons');
198 if (e.length === 0) {
189 if (e.length === 0) {
199 item.append(new_buttons);
190 item.append(new_buttons);
200 } else {
191 } else {
201 e.replaceWith(new_buttons);
192 e.replaceWith(new_buttons);
202 };
193 };
203 };
194 };
204
195
205
196
206 NotebookList.prototype.add_upload_button = function (item) {
197 NotebookList.prototype.add_upload_button = function (item) {
207 var that = this;
198 var that = this;
208 var new_buttons = $('<span/>').addClass('item_buttons');
199 var new_buttons = $('<span/>').addClass('item_buttons');
209 var upload_button = $('<button>Upload</button>').button().
200 var upload_button = $('<button>Upload</button>').button().
210 click(function (e) {
201 click(function (e) {
211 var nbname = item.find('.item_name > input').attr('value');
202 var nbname = item.find('.item_name > input').attr('value');
212 var nbformat = item.data('nbformat');
203 var nbformat = item.data('nbformat');
213 var nbdata = item.data('nbdata');
204 var nbdata = item.data('nbdata');
214 var content_type = 'text/plain';
205 var content_type = 'text/plain';
215 if (nbformat === 'json') {
206 if (nbformat === 'json') {
216 content_type = 'application/json';
207 content_type = 'application/json';
217 } else if (nbformat === 'py') {
208 } else if (nbformat === 'py') {
218 content_type = 'application/x-python';
209 content_type = 'application/x-python';
219 };
210 };
220 var settings = {
211 var settings = {
221 processData : false,
212 processData : false,
222 cache : false,
213 cache : false,
223 type : 'POST',
214 type : 'POST',
224 dataType : 'json',
215 dataType : 'json',
225 data : nbdata,
216 data : nbdata,
226 headers : {'Content-Type': content_type},
217 headers : {'Content-Type': content_type},
227 success : function (data, status, xhr) {
218 success : function (data, status, xhr) {
228 that.add_link(data, nbname, item);
219 that.add_link(data, nbname, item);
229 that.add_delete_button(item);
220 that.add_delete_button(item);
230 }
221 }
231 };
222 };
232
223
233 var qs = $.param({name:nbname, format:nbformat});
224 var qs = $.param({name:nbname, format:nbformat});
234 var url = $('body').data('baseProjectUrl') + 'notebooks?' + qs
225 var url = $('body').data('baseProjectUrl') + 'notebooks?' + qs
235 $.ajax(url, settings);
226 $.ajax(url, settings);
236 });
227 });
237 var cancel_button = $('<button>Cancel</button>').button().
228 var cancel_button = $('<button>Cancel</button>').button().
238 click(function (e) {
229 click(function (e) {
239 item.remove();
230 item.remove();
240 });
231 });
241 upload_button.addClass('upload_button');
232 upload_button.addClass('upload_button');
242 new_buttons.append(upload_button).append(cancel_button);
233 new_buttons.append(upload_button).append(cancel_button);
243 var e = item.find('.item_buttons');
234 var e = item.find('.item_buttons');
244 if (e.length === 0) {
235 if (e.length === 0) {
245 item.append(new_buttons);
236 item.append(new_buttons);
246 } else {
237 } else {
247 e.replaceWith(new_buttons);
238 e.replaceWith(new_buttons);
248 };
239 };
249 };
240 };
250
241
251
242
252 IPython.NotebookList = NotebookList;
243 IPython.NotebookList = NotebookList;
253
244
254 return IPython;
245 return IPython;
255
246
256 }(IPython));
247 }(IPython));
257
248
@@ -1,60 +1,84 b''
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 MathJax.Hub.Config({
15 MathJax.Hub.Config({
16 tex2jax: {
16 tex2jax: {
17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
19 },
19 },
20 displayAlign: 'left', // Change this to 'center' to center equations.
20 displayAlign: 'left', // Change this to 'center' to center equations.
21 "HTML-CSS": {
21 "HTML-CSS": {
22 styles: {'.MathJax_Display': {"margin": 0}}
22 styles: {'.MathJax_Display': {"margin": 0}}
23 }
23 }
24 });
24 });
25 IPython.markdown_converter = new Markdown.Converter();
25 IPython.markdown_converter = new Markdown.Converter();
26 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
26
27
27 $('div#header').addClass('border-box-sizing');
28 $('div#header').addClass('border-box-sizing');
28 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
29 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
29 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
30 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
30
31
31 IPython.layout_manager = new IPython.LayoutManager();
32 IPython.layout_manager = new IPython.LayoutManager();
32 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
33 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
33 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
37 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
37 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
38 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
38 IPython.notebook = new IPython.Notebook('div#notebook');
39 IPython.notebook = new IPython.Notebook('div#notebook');
39 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
40 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
40 IPython.kernel_status_widget.status_idle();
41 IPython.kernel_status_widget.status_idle();
41
42
42 IPython.layout_manager.do_resize();
43 IPython.layout_manager.do_resize();
43
44
44 // These have display: none in the css file and are made visible here to prevent FLOUC.
45 // These have display: none in the css file and are made visible here to prevent FLOUC.
45 $('div#header').css('display','block');
46 $('div#header').css('display','block');
47
48 if(IPython.read_only){
49 // hide various elements from read-only view
50 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 $('button#new_notebook').addClass('hidden');
53 $('div#cell_section').addClass('hidden');
54 $('div#kernel_section').addClass('hidden');
55 $('span#login_widget').removeClass('hidden');
56 // left panel starts collapsed, but the collapse must happen after
57 // elements start drawing. Don't draw contents of the panel until
58 // after they are collapsed
59 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
60 }
61
46 $('div#main_app').css('display','block');
62 $('div#main_app').css('display','block');
47
63
48 // Perform these actions after the notebook has been loaded.
64 // Perform these actions after the notebook has been loaded.
49 // We wait 100 milliseconds because the notebook scrolls to the top after a load
65 // We wait 100 milliseconds because the notebook scrolls to the top after a load
50 // is completed and we need to wait for that to mostly finish.
66 // is completed and we need to wait for that to mostly finish.
51 IPython.notebook.load_notebook(function () {
67 IPython.notebook.load_notebook(function () {
52 setTimeout(function () {
68 setTimeout(function () {
53 IPython.save_widget.update_url();
69 IPython.save_widget.update_url();
54 IPython.layout_manager.do_resize();
70 IPython.layout_manager.do_resize();
55 IPython.pager.collapse();
71 IPython.pager.collapse();
72 if(IPython.read_only){
73 // collapse the left panel on read-only
74 IPython.left_panel.collapse();
75 // and finally unhide the panel contents after collapse
76 setTimeout(function(){
77 IPython.left_panel.left_panel_element.css('visibility', 'visible');
78 }, 200)
79 }
56 },100);
80 },100);
57 });
81 });
58
82
59 });
83 });
60
84
@@ -1,40 +1,48 b''
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 $('div#header').addClass('border-box-sizing');
15 $('div#header').addClass('border-box-sizing');
16 $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
16 $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
17
17
18 $('div#main_app').addClass('border-box-sizing ui-widget');
18 $('div#main_app').addClass('border-box-sizing ui-widget');
19 $('div#app_hbox').addClass('hbox');
19 $('div#app_hbox').addClass('hbox');
20
20
21 $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix');
21 $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix');
22
22
23 $('#new_notebook').button().click(function (e) {
23 $('#new_notebook').button().click(function (e) {
24 window.open($('body').data('baseProjectUrl')+'new');
24 window.open($('body').data('baseProjectUrl')+'new');
25 });
25 });
26
26
27 $('div#left_panel').addClass('box-flex');
27 $('div#left_panel').addClass('box-flex');
28 $('div#right_panel').addClass('box-flex');
28 $('div#right_panel').addClass('box-flex');
29
29
30 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
31
30 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
32 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
31 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
33 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
34
35 if (IPython.read_only){
36 $('#new_notebook').addClass('hidden');
37 // unhide login button if it's relevant
38 $('span#login_widget').removeClass('hidden');
39 }
32 IPython.notebook_list.load_list();
40 IPython.notebook_list.load_list();
33
41
34 // These have display: none in the css file and are made visible here to prevent FLOUC.
42 // These have display: none in the css file and are made visible here to prevent FLOUC.
35 $('div#header').css('display','block');
43 $('div#header').css('display','block');
36 $('div#main_app').css('display','block');
44 $('div#main_app').css('display','block');
37
45
38
46
39 });
47 });
40
48
@@ -1,53 +1,55 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>IPython Notebook</title>
7 <title>IPython Notebook</title>
8
8
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
13
13
14 <meta name="read_only" content="{{read_only}}"/>
15
14 </head>
16 </head>
15
17
16 <body>
18 <body>
17
19
18 <div id="header">
20 <div id="header">
19 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
20 </div>
22 </div>
21
23
22 <div id="header_border"></div>
24 <div id="header_border"></div>
23
25
24 <div id="main_app">
26 <div id="main_app">
25
27
26 <div id="app_hbox">
28 <div id="app_hbox">
27
29
28 <div id="left_panel">
30 <div id="left_panel">
29 </div>
31 </div>
30
32
31 <div id="content_panel">
33 <div id="content_panel">
32 <form action="/login?next={{url_escape(next)}}" method="post">
34 <form action="/login?next={{url_escape(next)}}" method="post">
33 Password: <input type="password" name="password">
35 Password: <input type="password" name="password">
34 <input type="submit" value="Sign in" id="signin">
36 <input type="submit" value="Sign in" id="signin">
35 </form>
37 </form>
36 </div>
38 </div>
37 <div id="right_panel">
39 <div id="right_panel">
38 </div>
40 </div>
39
41
40 </div>
42 </div>
41
43
42 </div>
44 </div>
43
45
44 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
46 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
45 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
47 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
46 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
48 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
47 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
49 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
48
50
49 </body>
51 </body>
50
52
51 </html>
53 </html>
52
54
53
55
@@ -1,294 +1,295 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>IPython Notebook</title>
7 <title>IPython Notebook</title>
8
8
9 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
9 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
11 <script type="text/javascript">
11 <script type="text/javascript">
12 function CheckMathJax(){
12 function CheckMathJax(){
13 var div=document.getElementById("MathJaxFetchingWarning")
13 var div=document.getElementById("MathJaxFetchingWarning")
14 if(window.MathJax){
14 if(window.MathJax){
15 document.body.removeChild(div)
15 document.body.removeChild(div)
16 }
16 }
17 else{
17 else{
18 div.style.display = "block";
18 div.style.display = "block";
19 }
19 }
20 }
20 }
21 if (typeof MathJax == 'undefined') {
21 if (typeof MathJax == 'undefined') {
22 console.log("No local MathJax, loading from CDN");
22 console.log("No local MathJax, loading from CDN");
23 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
23 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
24 }else{
24 }else{
25 console.log("Using local MathJax");
25 console.log("Using local MathJax");
26 }
26 }
27 </script>
27 </script>
28
28
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
30 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
30 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
35
35
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
37
37
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43
43
44 <meta name="read_only" content="{{read_only}}"/>
44
45
45 </head>
46 </head>
46
47
47 <body onload='CheckMathJax();'
48 <body onload='CheckMathJax();'
48 data-project={{project}} data-notebook-id={{notebook_id}}
49 data-project={{project}} data-notebook-id={{notebook_id}}
49 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
50 >
51 >
51
52
52 <div id="header">
53 <div id="header">
53 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
54 <span id="save_widget">
55 <span id="save_widget">
55 <input type="text" id="notebook_name" size="20"></textarea>
56 <input type="text" id="notebook_name" size="20"></textarea>
56 <button id="save_notebook"><u>S</u>ave</button>
57 <button id="save_notebook"><u>S</u>ave</button>
57 </span>
58 </span>
58 <span id="quick_help_area">
59 <span id="quick_help_area">
59 <button id="quick_help">Quick<u>H</u>elp</button>
60 <button id="quick_help">Quick<u>H</u>elp</button>
60 </span>
61 </span>
61 <span id="login_widget" class="hidden">
62 <span id="login_widget" class="hidden">
62 <button id="login">Login</button>
63 <button id="login">Login</button>
63 </span>
64 </span>
64 <span id="kernel_status">Idle</span>
65 <span id="kernel_status">Idle</span>
65 </div>
66 </div>
66
67
67 <div id="MathJaxFetchingWarning"
68 <div id="MathJaxFetchingWarning"
68 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
69 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
69 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
70 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
70 from the internet.</p>
71 from the internet.</p>
71
72
72 <p style="padding:0.2em"> With a working internet connection, you can run
73 <p style="padding:0.2em"> With a working internet connection, you can run
73 the following at a Python or IPython prompt, which will install a local
74 the following at a Python or IPython prompt, which will install a local
74 copy of MathJax:</p>
75 copy of MathJax:</p>
75
76
76 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
77 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
77 from IPython.external import mathjax; mathjax.install_mathjax()
78 from IPython.external import mathjax; mathjax.install_mathjax()
78 </pre>
79 </pre>
79 This will try to install MathJax into the directory where you installed
80 This will try to install MathJax into the directory where you installed
80 IPython. If you installed IPython to a location that requires
81 IPython. If you installed IPython to a location that requires
81 administrative privileges to write, you will need to make this call as
82 administrative privileges to write, you will need to make this call as
82 an administrator. On OSX/Linux/Unix, this can be done at the
83 an administrator. On OSX/Linux/Unix, this can be done at the
83 command-line via:
84 command-line via:
84 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
85 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
85 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
86 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
86 </pre>
87 </pre>
87 </p>
88 </p>
88 </div>
89 </div>
89
90
90 <div id="main_app">
91 <div id="main_app">
91
92
92 <div id="left_panel">
93 <div id="left_panel">
93
94
94 <div id="notebook_section">
95 <div id="notebook_section">
95 <div class="section_header">
96 <div class="section_header">
96 <h3>Notebook</h3>
97 <h3>Notebook</h3>
97 </div>
98 </div>
98 <div class="section_content">
99 <div class="section_content">
99 <div class="section_row">
100 <div class="section_row">
100 <span id="new_open" class="section_row_buttons">
101 <span id="new_open" class="section_row_buttons">
101 <button id="new_notebook">New</button>
102 <button id="new_notebook">New</button>
102 <button id="open_notebook">Open</button>
103 <button id="open_notebook">Open</button>
103 </span>
104 </span>
104 <span class="section_row_header">Actions</span>
105 <span class="section_row_header">Actions</span>
105 </div>
106 </div>
106 <div class="section_row">
107 <div class="section_row">
107 <span>
108 <span>
108 <select id="download_format">
109 <select id="download_format">
109 <option value="json">ipynb</option>
110 <option value="json">ipynb</option>
110 <option value="py">py</option>
111 <option value="py">py</option>
111 </select>
112 </select>
112 </span>
113 </span>
113 <span class="section_row_buttons">
114 <span class="section_row_buttons">
114 <button id="download_notebook">Download</button>
115 <button id="download_notebook">Download</button>
115 </span>
116 </span>
116 </div>
117 </div>
117 <div class="section_row">
118 <div class="section_row">
118 <span class="section_row_buttons">
119 <span class="section_row_buttons">
119 <span id="print_widget">
120 <span id="print_widget">
120 <button id="print_notebook">Print</button>
121 <button id="print_notebook">Print</button>
121 </span>
122 </span>
122 </span>
123 </span>
123 </div>
124 </div>
124 </div>
125 </div>
125 </div>
126 </div>
126
127
127 <div id="cell_section">
128 <div id="cell_section">
128 <div class="section_header">
129 <div class="section_header">
129 <h3>Cell</h3>
130 <h3>Cell</h3>
130 </div>
131 </div>
131 <div class="section_content">
132 <div class="section_content">
132 <div class="section_row">
133 <div class="section_row">
133 <span class="section_row_buttons">
134 <span class="section_row_buttons">
134 <button id="delete_cell"><u>D</u>elete</button>
135 <button id="delete_cell"><u>D</u>elete</button>
135 </span>
136 </span>
136 <span class="section_row_header">Actions</span>
137 <span class="section_row_header">Actions</span>
137 </div>
138 </div>
138 <div class="section_row">
139 <div class="section_row">
139 <span id="cell_type" class="section_row_buttons">
140 <span id="cell_type" class="section_row_buttons">
140 <button id="to_code"><u>C</u>ode</button>
141 <button id="to_code"><u>C</u>ode</button>
141 <!-- <button id="to_html">HTML</button>-->
142 <!-- <button id="to_html">HTML</button>-->
142 <button id="to_markdown"><u>M</u>arkdown</button>
143 <button id="to_markdown"><u>M</u>arkdown</button>
143 </span>
144 </span>
144 <span class="button_label">Format</span>
145 <span class="button_label">Format</span>
145 </div>
146 </div>
146 <div class="section_row">
147 <div class="section_row">
147 <span id="cell_output" class="section_row_buttons">
148 <span id="cell_output" class="section_row_buttons">
148 <button id="toggle_output"><u>T</u>oggle</button>
149 <button id="toggle_output"><u>T</u>oggle</button>
149 <button id="clear_all_output">ClearAll</button>
150 <button id="clear_all_output">ClearAll</button>
150 </span>
151 </span>
151 <span class="button_label">Output</span>
152 <span class="button_label">Output</span>
152 </div>
153 </div>
153 <div class="section_row">
154 <div class="section_row">
154 <span id="insert" class="section_row_buttons">
155 <span id="insert" class="section_row_buttons">
155 <button id="insert_cell_above"><u>A</u>bove</button>
156 <button id="insert_cell_above"><u>A</u>bove</button>
156 <button id="insert_cell_below"><u>B</u>elow</button>
157 <button id="insert_cell_below"><u>B</u>elow</button>
157 </span>
158 </span>
158 <span class="button_label">Insert</span>
159 <span class="button_label">Insert</span>
159 </div>
160 </div>
160 <div class="section_row">
161 <div class="section_row">
161 <span id="move" class="section_row_buttons">
162 <span id="move" class="section_row_buttons">
162 <button id="move_cell_up">Up</button>
163 <button id="move_cell_up">Up</button>
163 <button id="move_cell_down">Down</button>
164 <button id="move_cell_down">Down</button>
164 </span>
165 </span>
165 <span class="button_label">Move</span>
166 <span class="button_label">Move</span>
166 </div>
167 </div>
167 <div class="section_row">
168 <div class="section_row">
168 <span id="run_cells" class="section_row_buttons">
169 <span id="run_cells" class="section_row_buttons">
169 <button id="run_selected_cell">Selected</button>
170 <button id="run_selected_cell">Selected</button>
170 <button id="run_all_cells">All</button>
171 <button id="run_all_cells">All</button>
171 </span>
172 </span>
172 <span class="button_label">Run</span>
173 <span class="button_label">Run</span>
173 </div>
174 </div>
174 <div class="section_row">
175 <div class="section_row">
175 <span id="autoindent_span">
176 <span id="autoindent_span">
176 <input type="checkbox" id="autoindent" checked="true"></input>
177 <input type="checkbox" id="autoindent" checked="true"></input>
177 </span>
178 </span>
178 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
179 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
179 </div>
180 </div>
180 </div>
181 </div>
181 </div>
182 </div>
182
183
183 <div id="kernel_section">
184 <div id="kernel_section">
184 <div class="section_header">
185 <div class="section_header">
185 <h3>Kernel</h3>
186 <h3>Kernel</h3>
186 </div>
187 </div>
187 <div class="section_content">
188 <div class="section_content">
188 <div class="section_row">
189 <div class="section_row">
189 <span id="int_restart" class="section_row_buttons">
190 <span id="int_restart" class="section_row_buttons">
190 <button id="int_kernel"><u>I</u>nterrupt</button>
191 <button id="int_kernel"><u>I</u>nterrupt</button>
191 <button id="restart_kernel">Restart</button>
192 <button id="restart_kernel">Restart</button>
192 </span>
193 </span>
193 <span class="section_row_header">Actions</span>
194 <span class="section_row_header">Actions</span>
194 </div>
195 </div>
195 <div class="section_row">
196 <div class="section_row">
196 <span id="kernel_persist">
197 <span id="kernel_persist">
197 {% if kill_kernel %}
198 {% if kill_kernel %}
198 <input type="checkbox" id="kill_kernel" checked="true"></input>
199 <input type="checkbox" id="kill_kernel" checked="true"></input>
199 {% else %}
200 {% else %}
200 <input type="checkbox" id="kill_kernel"></input>
201 <input type="checkbox" id="kill_kernel"></input>
201 {% end %}
202 {% end %}
202 </span>
203 </span>
203 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
204 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
204 </div>
205 </div>
205 </div>
206 </div>
206 </div>
207 </div>
207
208
208 <div id="help_section">
209 <div id="help_section">
209 <div class="section_header">
210 <div class="section_header">
210 <h3>Help</h3>
211 <h3>Help</h3>
211 </div>
212 </div>
212 <div class="section_content">
213 <div class="section_content">
213 <div class="section_row">
214 <div class="section_row">
214 <span id="help_buttons0" class="section_row_buttons">
215 <span id="help_buttons0" class="section_row_buttons">
215 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
216 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
216 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
217 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
217 </span>
218 </span>
218 <span class="section_row_header">Links</span>
219 <span class="section_row_header">Links</span>
219 </div>
220 </div>
220 <div class="section_row">
221 <div class="section_row">
221 <span id="help_buttons1" class="section_row_buttons">
222 <span id="help_buttons1" class="section_row_buttons">
222 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
223 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
223 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
224 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
224 </span>
225 </span>
225 </div>
226 </div>
226 <div class="section_row">
227 <div class="section_row">
227 <span id="help_buttons2" class="section_row_buttons">
228 <span id="help_buttons2" class="section_row_buttons">
228 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
229 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
229 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
230 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
230 </span>
231 </span>
231 </div>
232 </div>
232 <div class="section_row">
233 <div class="section_row">
233 <span class="help_string">run selected cell</span>
234 <span class="help_string">run selected cell</span>
234 <span class="help_string_label">Shift-Enter :</span>
235 <span class="help_string_label">Shift-Enter :</span>
235 </div>
236 </div>
236 <div class="section_row">
237 <div class="section_row">
237 <span class="help_string">run selected cell in-place</span>
238 <span class="help_string">run selected cell in-place</span>
238 <span class="help_string_label">Ctrl-Enter :</span>
239 <span class="help_string_label">Ctrl-Enter :</span>
239 </div>
240 </div>
240 <div class="section_row">
241 <div class="section_row">
241 <span class="help_string">show keyboard shortcuts</span>
242 <span class="help_string">show keyboard shortcuts</span>
242 <span class="help_string_label">Ctrl-m h :</span>
243 <span class="help_string_label">Ctrl-m h :</span>
243 </div>
244 </div>
244 </div>
245 </div>
245 </div>
246 </div>
246
247
247 </div>
248 </div>
248 <div id="left_panel_splitter"></div>
249 <div id="left_panel_splitter"></div>
249 <div id="notebook_panel">
250 <div id="notebook_panel">
250 <div id="notebook"></div>
251 <div id="notebook"></div>
251 <div id="pager_splitter"></div>
252 <div id="pager_splitter"></div>
252 <div id="pager"></div>
253 <div id="pager"></div>
253 </div>
254 </div>
254
255
255 </div>
256 </div>
256
257
257 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
258 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
258 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
259 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
259 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
260 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
260
261
261 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
262 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
262 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
263 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
263 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
264 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
264 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
265 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
265 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
266 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
266 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
267 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
267 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
268 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
268 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
269 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
269
270
270 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
271 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
271
272
272 <script src="static/prettify/prettify.js" charset="utf-8"></script>
273 <script src="static/prettify/prettify.js" charset="utf-8"></script>
273
274
274 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
275 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
275 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
276 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
276 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
277 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
277 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
278 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
278 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
291 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
291
292
292 </body>
293 </body>
293
294
294 </html>
295 </html>
@@ -1,67 +1,69 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>IPython Dashboard</title>
7 <title>IPython Dashboard</title>
8
8
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
13 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
13 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
14
14
15 <meta name="read_only" content="{{read_only}}"/>
16
15 </head>
17 </head>
16
18
17 <body data-project={{project}} data-base-project-url={{base_project_url}}
19 <body data-project={{project}} data-base-project-url={{base_project_url}}
18 data-base-kernel-url={{base_kernel_url}}>
20 data-base-kernel-url={{base_kernel_url}}>
19
21
20 <div id="header">
22 <div id="header">
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
22 <span id="login_widget" class="hidden">
24 <span id="login_widget" class="hidden">
23 <button id="login">Login</button>
25 <button id="login">Login</button>
24 </span>
26 </span>
25 </div>
27 </div>
26
28
27 <div id="header_border"></div>
29 <div id="header_border"></div>
28
30
29 <div id="main_app">
31 <div id="main_app">
30
32
31 <div id="app_hbox">
33 <div id="app_hbox">
32
34
33 <div id="left_panel">
35 <div id="left_panel">
34 </div>
36 </div>
35
37
36 <div id="content_panel">
38 <div id="content_panel">
37 <div id="content_toolbar">
39 <div id="content_toolbar">
38 <span id="drag_info">Drag files onto the list to import notebooks.</span>
40 <span id="drag_info">Drag files onto the list to import notebooks.</span>
39 <span id="notebooks_buttons">
41 <span id="notebooks_buttons">
40 <button id="new_notebook">New Notebook</button>
42 <button id="new_notebook">New Notebook</button>
41 </span>
43 </span>
42 </div>
44 </div>
43 <div id="notebook_list">
45 <div id="notebook_list">
44 <div id="project_name"><h2>{{project}}</h2></div>
46 <div id="project_name"><h2>{{project}}</h2></div>
45 </div>
47 </div>
46
48
47 </div>
49 </div>
48
50
49 <div id="right_panel">
51 <div id="right_panel">
50 </div>
52 </div>
51
53
52 </div>
54 </div>
53
55
54 </div>
56 </div>
55
57
56 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
58 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
57 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
58 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
61 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
62 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
61 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
63 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
62
64
63 </body>
65 </body>
64
66
65 </html>
67 </html>
66
68
67
69
General Comments 0
You need to be logged in to leave comments. Login now