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