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