Merge master

Gordon Ball 12 years ago
commit 8296f24904

1
.gitignore vendored

@ -7,6 +7,7 @@ docs/source/api/generated
docs/source/config/options
docs/gh-pages
IPython/html/notebook/static/mathjax
IPython/html/static/style/*.map
*.py[co]
__pycache__
*.egg-info

@ -13,19 +13,18 @@ Developers of the IPython Notebook will need to install the following tools:
We are moving to a model where our JavaScript dependencies are managed using
[bower](http://bower.io/). These packages are installed in `static/components`
and committed into our git repo. Our dependencies are described in the file
and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components).
Our dependencies are described in the file
`static/components/bower.json`. To update our bower packages, run `fab update`
in this directory.
Because CodeMirror does not use proper semantic versioning for its GitHub tags,
we maintain our own fork of CodeMirror that is used with bower. This fork should
track the upstream CodeMirror exactly; the only difference is that we are adding
semantic versioned tags to our repo.
## less
If you edit our `.less` files you will need to run the less compiler to build
our minified css files. This can be done by running `fab css` from this directory.
our minified css files. This can be done by running `fab css` from this directory,
or `python setup.py css` from the root of the repository.
If you are working frequently with `.less` files please consider installing git hooks that
rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`.
## JavaScript Documentation

@ -1,21 +1,7 @@
"""Base Tornado handlers for the notebook.
Authors:
* Brian Granger
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
"""Base Tornado handlers for the notebook server."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import functools
import json
@ -41,7 +27,7 @@ except ImportError:
from IPython.config import Application
from IPython.utils.path import filefind
from IPython.utils.py3compat import string_types
from IPython.html.utils import is_hidden
from IPython.html.utils import is_hidden, url_path_join, url_escape
#-----------------------------------------------------------------------------
# Top-level handlers
@ -53,6 +39,10 @@ class AuthenticatedHandler(web.RequestHandler):
def set_default_headers(self):
headers = self.settings.get('headers', {})
if "X-Frame-Options" not in headers:
headers["X-Frame-Options"] = "SAMEORIGIN"
for header_name,value in headers.items() :
try:
self.set_header(header_name, value)
@ -137,6 +127,10 @@ class IPythonHandler(AuthenticatedHandler):
@property
def base_url(self):
return self.settings.get('base_url', '/')
@property
def ws_url(self):
return self.settings.get('websocket_url', '')
#---------------------------------------------------------------
# Manager objects
@ -147,8 +141,8 @@ class IPythonHandler(AuthenticatedHandler):
return self.settings['kernel_manager']
@property
def notebook_manager(self):
return self.settings['notebook_manager']
def contents_manager(self):
return self.settings['contents_manager']
@property
def cluster_manager(self):
@ -162,9 +156,47 @@ class IPythonHandler(AuthenticatedHandler):
def kernel_spec_manager(self):
return self.settings['kernel_spec_manager']
#---------------------------------------------------------------
# CORS
#---------------------------------------------------------------
@property
def project_dir(self):
return self.notebook_manager.notebook_dir
def allow_origin(self):
"""Normal Access-Control-Allow-Origin"""
return self.settings.get('allow_origin', '')
@property
def allow_origin_pat(self):
"""Regular expression version of allow_origin"""
return self.settings.get('allow_origin_pat', None)
@property
def allow_credentials(self):
"""Whether to set Access-Control-Allow-Credentials"""
return self.settings.get('allow_credentials', False)
def set_default_headers(self):
"""Add CORS headers, if defined"""
super(IPythonHandler, self).set_default_headers()
if self.allow_origin:
self.set_header("Access-Control-Allow-Origin", self.allow_origin)
elif self.allow_origin_pat:
origin = self.get_origin()
if origin and self.allow_origin_pat.match(origin):
self.set_header("Access-Control-Allow-Origin", origin)
if self.allow_credentials:
self.set_header("Access-Control-Allow-Credentials", 'true')
def get_origin(self):
# Handle WebSocket Origin naming convention differences
# The difference between version 8 and 13 is that in 8 the
# client sends a "Sec-Websocket-Origin" header and in 13 it's
# simply "Origin".
if "Origin" in self.request.headers:
origin = self.request.headers.get("Origin")
else:
origin = self.request.headers.get("Sec-Websocket-Origin", None)
return origin
#---------------------------------------------------------------
# template rendering
@ -183,6 +215,7 @@ class IPythonHandler(AuthenticatedHandler):
def template_namespace(self):
return dict(
base_url=self.base_url,
ws_url=self.ws_url,
logged_in=self.logged_in,
login_available=self.login_available,
static_url=self.static_url,
@ -202,12 +235,13 @@ class IPythonHandler(AuthenticatedHandler):
raise web.HTTPError(400, u'Invalid JSON in body of request')
return model
def get_error_html(self, status_code, **kwargs):
def write_error(self, status_code, **kwargs):
"""render custom error pages"""
exception = kwargs.get('exception')
exc_info = kwargs.get('exc_info')
message = ''
status_message = responses.get(status_code, 'Unknown HTTP Error')
if exception:
if exc_info:
exception = exc_info[1]
# get the custom message, if defined
try:
message = exception.log_message % exception.args
@ -227,13 +261,16 @@ class IPythonHandler(AuthenticatedHandler):
exception=exception,
)
self.set_header('Content-Type', 'text/html')
# render the template
try:
html = self.render_template('%s.html' % status_code, **ns)
except TemplateNotFound:
self.log.debug("No template for %d", status_code)
html = self.render_template('error.html', **ns)
return html
self.write(html)
class Template404(IPythonHandler):
@ -372,6 +409,37 @@ class TrailingSlashHandler(web.RequestHandler):
def get(self):
self.redirect(self.request.uri.rstrip('/'))
class FilesRedirectHandler(IPythonHandler):
"""Handler for redirecting relative URLs to the /files/ handler"""
def get(self, path=''):
cm = self.contents_manager
if cm.path_exists(path):
# it's a *directory*, redirect to /tree
url = url_path_join(self.base_url, 'tree', path)
else:
orig_path = path
# otherwise, redirect to /files
parts = path.split('/')
path = '/'.join(parts[:-1])
name = parts[-1]
if not cm.file_exists(name=name, path=path) and 'files' in parts:
# redirect without files/ iff it would 404
# this preserves pre-2.0-style 'files/' links
self.log.warn("Deprecated files/ URL: %s", orig_path)
parts.remove('files')
path = '/'.join(parts[:-1])
if not cm.file_exists(name=name, path=path):
raise web.HTTPError(404)
url = url_path_join(self.base_url, 'files', path, name)
url = url_escape(url)
self.log.debug("Redirecting %s to %s", self.request.path, url)
self.redirect(url)
#-----------------------------------------------------------------------------
# URL pattern fragments for re-use
#-----------------------------------------------------------------------------
@ -379,6 +447,8 @@ class TrailingSlashHandler(web.RequestHandler):
path_regex = r"(?P<path>(?:/.*)*)"
notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
file_name_regex = r"(?P<name>[^/]+)"
file_path_regex = "%s/%s" % (path_regex, file_name_regex)
#-----------------------------------------------------------------------------
# URL to handler mappings

@ -15,6 +15,9 @@ try:
except ImportError:
from Cookie import SimpleCookie # Py 2
import logging
import tornado
from tornado import ioloop
from tornado import web
from tornado import websocket
@ -26,29 +29,36 @@ from .handlers import IPythonHandler
class ZMQStreamHandler(websocket.WebSocketHandler):
def same_origin(self):
"""Check to see that origin and host match in the headers."""
# The difference between version 8 and 13 is that in 8 the
# client sends a "Sec-Websocket-Origin" header and in 13 it's
# simply "Origin".
if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
origin_header = self.request.headers.get("Sec-Websocket-Origin")
else:
origin_header = self.request.headers.get("Origin")
def check_origin(self, origin):
"""Check Origin == Host or Access-Control-Allow-Origin.
Tornado >= 4 calls this method automatically, raising 403 if it returns False.
We call it explicitly in `open` on Tornado < 4.
"""
if self.allow_origin == '*':
return True
host = self.request.headers.get("Host")
# If no header is provided, assume we can't verify origin
if(origin_header is None or host is None):
if(origin is None or host is None):
return False
host_origin = "{0}://{1}".format(self.request.protocol, host)
# OK if origin matches host
if origin == host_origin:
return True
# Check CORS headers
if self.allow_origin:
return self.allow_origin == origin
elif self.allow_origin_pat:
return bool(self.allow_origin_pat.match(origin))
else:
# No CORS headers deny the request
return False
parsed_origin = urlparse(origin_header)
origin = parsed_origin.netloc
# Check to see that origin matches host directly, including ports
return origin == host
def clear_cookie(self, *args, **kwargs):
"""meaningless for websockets"""
@ -94,19 +104,41 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
"""
return True
# ping interval for keeping websockets alive (30 seconds)
WS_PING_INTERVAL = 30000
class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
ping_callback = None
def set_default_headers(self):
"""Undo the set_default_headers in IPythonHandler
which doesn't make sense for websockets
"""
pass
def open(self, kernel_id):
self.kernel_id = cast_unicode(kernel_id, 'ascii')
# Check to see that origin matches host directly, including ports
if not self.same_origin():
self.log.warn("Cross Origin WebSocket Attempt.")
raise web.HTTPError(404)
# Tornado 4 already does CORS checking
if tornado.version_info[0] < 4:
if not self.check_origin(self.get_origin()):
self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin())
raise web.HTTPError(403)
self.session = Session(config=self.config)
self.save_on_message = self.on_message
self.on_message = self.on_first_message
self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)
self.ping_callback.start()
def send_ping(self):
"""send a ping to keep the websocket alive"""
if self.stream.closed() and self.ping_callback is not None:
self.ping_callback.stop()
return
self.ping(b'')
def _inject_cookie_message(self, msg):
"""Inject the first message, which is the document cookie,

@ -8,31 +8,65 @@ from subprocess import check_output
pjoin = os.path.join
static_dir = 'static'
components_dir = os.path.join(static_dir, 'components')
components_dir = pjoin(static_dir, 'components')
here = os.path.dirname(__file__)
min_less_version = '1.4.0'
max_less_version = '1.5.0' # exclusive
min_less_version = '1.7.0'
max_less_version = '1.8.0' # exclusive
def css(minify=True, verbose=False):
def _need_css_update():
"""Does less need to run?"""
static_path = pjoin(here, static_dir)
css_targets = [
pjoin(static_path, 'style', '%s.min.css' % name)
for name in ('style', 'ipython')
]
css_maps = [t + '.map' for t in css_targets]
targets = css_targets + css_maps
if not all(os.path.exists(t) for t in targets):
# some generated files don't exist
return True
earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
# check if any .less files are newer than the generated targets
for (dirpath, dirnames, filenames) in os.walk(static_path):
for f in filenames:
if f.endswith('.less'):
path = pjoin(static_path, dirpath, f)
timestamp = os.stat(path).st_mtime
if timestamp > earliest_target:
return True
return False
def css(minify=False, verbose=False, force=False):
"""generate the css from less files"""
minify = _to_bool(minify)
verbose = _to_bool(verbose)
force = _to_bool(force)
# minify implies force because it's not the default behavior
if not force and not minify and not _need_css_update():
print("css up-to-date")
return
for name in ('style', 'ipython'):
source = pjoin('style', "%s.less" % name)
target = pjoin('style', "%s.min.css" % name)
_compile_less(source, target, minify, verbose)
sourcemap = pjoin('style', "%s.min.css.map" % name)
_compile_less(source, target, sourcemap, minify, verbose)
def _to_bool(b):
if not b in ['True', 'False', True, False]:
abort('boolean expected, got: %s' % b)
return (b in ['True', True])
def _compile_less(source, target, minify=True, verbose=False):
def _compile_less(source, target, sourcemap, minify=True, verbose=False):
"""Compile a less file by source and target relative to static_dir"""
minify = _to_bool(minify)
verbose = _to_bool(verbose)
min_flag = '-x' if minify is True else ''
ver_flag = '--verbose' if verbose is True else ''
# pin less to 1.4
# pin less to version number from above
try:
out = check_output(['lessc', '--version'])
except OSError as err:
@ -45,6 +79,7 @@ def _compile_less(source, target, minify=True, verbose=False):
if V(less_version) >= V(max_less_version):
raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
static_path = pjoin(here, static_dir)
with lcd(static_dir):
local('lessc {min_flag} {ver_flag} {source} {target}'.format(**locals()))
local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()))

@ -1,10 +1,18 @@
"""Tornado handlers for nbconvert."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import io
import os
import zipfile
from tornado import web
from ..base.handlers import IPythonHandler, notebook_path_regex
from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
notebook_path_regex, path_regex,
)
from IPython.nbformat.current import to_notebook_json
from IPython.utils.py3compat import cast_bytes
@ -73,7 +81,7 @@ class NbconvertFileHandler(IPythonHandler):
exporter = get_exporter(format, config=self.config, log=self.log)
path = path.strip('/')
model = self.notebook_manager.get_notebook(name=name, path=path)
model = self.contents_manager.get_model(name=name, path=path)
self.set_header('Last-Modified', model['last_modified'])
@ -123,6 +131,7 @@ class NbconvertPostHandler(IPythonHandler):
self.finish(output)
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
@ -134,4 +143,5 @@ default_handlers = [
(r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
NbconvertFileHandler),
(r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
(r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
]

@ -106,7 +106,7 @@ class APITest(NotebookTestBase):
@onlyif_cmds_exist('pandoc')
def test_from_post(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()
r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@ -121,7 +121,7 @@ class APITest(NotebookTestBase):
@onlyif_cmds_exist('pandoc')
def test_from_post_zip(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()
r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)

@ -1,31 +1,17 @@
"""Tornado handlers for the live notebook view.
"""Tornado handlers for the live notebook view."""
Authors:
* Brian Granger
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import os
from tornado import web
HTTPError = web.HTTPError
from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
from ..utils import url_path_join, url_escape
#-----------------------------------------------------------------------------
# Handlers
#-----------------------------------------------------------------------------
from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
notebook_path_regex, path_regex,
)
from ..utils import url_escape
class NotebookHandler(IPythonHandler):
@ -35,17 +21,16 @@ class NotebookHandler(IPythonHandler):
"""get renders the notebook template if a name is given, or
redirects to the '/files/' handler if the name is not given."""
path = path.strip('/')
nbm = self.notebook_manager
cm = self.contents_manager
if name is None:
raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
# a .ipynb filename was given
if not nbm.notebook_exists(name, path):
if not cm.file_exists(name, path):
raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
name = url_escape(name)
path = url_escape(path)
self.write(self.render_template('notebook.html',
project=self.project_dir,
notebook_path=path,
notebook_name=name,
kill_kernel=False,
@ -53,30 +38,6 @@ class NotebookHandler(IPythonHandler):
)
)
class NotebookRedirectHandler(IPythonHandler):
def get(self, path=''):
nbm = self.notebook_manager
if nbm.path_exists(path):
# it's a *directory*, redirect to /tree
url = url_path_join(self.base_url, 'tree', path)
else:
# otherwise, redirect to /files
if '/files/' in path:
# redirect without files/ iff it would 404
# this preserves pre-2.0-style 'files/' links
# FIXME: this is hardcoded based on notebook_path,
# but so is the files handler itself,
# so it should work until both are cleaned up.
parts = path.split('/')
files_path = os.path.join(nbm.notebook_dir, *parts)
if not os.path.exists(files_path):
self.log.warn("Deprecated files/ URL: %s", path)
path = path.replace('/files/', '/', 1)
url = url_path_join(self.base_url, 'files', path)
url = url_escape(url)
self.log.debug("Redirecting %s to %s", self.request.path, url)
self.redirect(url)
#-----------------------------------------------------------------------------
# URL to handler mappings
@ -85,6 +46,6 @@ class NotebookRedirectHandler(IPythonHandler):
default_handlers = [
(r"/notebooks%s" % notebook_path_regex, NotebookHandler),
(r"/notebooks%s" % path_regex, NotebookRedirectHandler),
(r"/notebooks%s" % path_regex, FilesRedirectHandler),
]

@ -6,12 +6,14 @@
from __future__ import print_function
import base64
import errno
import io
import json
import logging
import os
import random
import re
import select
import signal
import socket
@ -53,8 +55,8 @@ from IPython.html import DEFAULT_STATIC_FILES_PATH
from .base.handlers import Template404
from .log import log_request
from .services.kernels.kernelmanager import MappingKernelManager
from .services.notebooks.nbmanager import NotebookManager
from .services.notebooks.filenbmanager import FileNotebookManager
from .services.contents.manager import ContentsManager
from .services.contents.filemanager import FileContentsManager
from .services.clusters.clustermanager import ClusterManager
from .services.sessions.sessionmanager import SessionManager
@ -72,6 +74,7 @@ from IPython.kernel.zmq.session import default_secure, Session
from IPython.nbformat.sign import NotebookNotary
from IPython.utils.importstring import import_item
from IPython.utils import submodule
from IPython.utils.process import check_pid
from IPython.utils.traitlets import (
Dict, Unicode, Integer, List, Bool, Bytes, Instance,
DottedObjectName, TraitError,
@ -118,19 +121,19 @@ def load_handlers(name):
class NotebookWebApplication(web.Application):
def __init__(self, ipython_app, kernel_manager, notebook_manager,
def __init__(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager, log,
base_url, settings_overrides, jinja_env_options):
settings = self.init_settings(
ipython_app, kernel_manager, notebook_manager, cluster_manager,
ipython_app, kernel_manager, contents_manager, cluster_manager,
session_manager, kernel_spec_manager, log, base_url,
settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
def init_settings(self, ipython_app, kernel_manager, notebook_manager,
def init_settings(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager,
log, base_url, settings_overrides,
jinja_env_options=None):
@ -162,13 +165,14 @@ class NotebookWebApplication(web.Application):
# managers
kernel_manager=kernel_manager,
notebook_manager=notebook_manager,
contents_manager=contents_manager,
cluster_manager=cluster_manager,
session_manager=session_manager,
kernel_spec_manager=kernel_spec_manager,
# IPython stuff
nbextensions_path = ipython_app.nbextensions_path,
websocket_url=ipython_app.websocket_url,
mathjax_url=ipython_app.mathjax_url,
config=ipython_app.config,
jinja2_env=env,
@ -189,18 +193,20 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('nbconvert.handlers'))
handlers.extend(load_handlers('kernelspecs.handlers'))
handlers.extend(load_handlers('services.kernels.handlers'))
handlers.extend(load_handlers('services.notebooks.handlers'))
handlers.extend(load_handlers('services.contents.handlers'))
handlers.extend(load_handlers('services.clusters.handlers'))
handlers.extend(load_handlers('services.sessions.handlers'))
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
# FIXME: /files/ should be handled by the Contents service when it exists
nbm = settings['notebook_manager']
if hasattr(nbm, 'notebook_dir'):
handlers.extend([
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
cm = settings['contents_manager']
if hasattr(cm, 'root_dir'):
handlers.append(
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
)
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
])
)
# prepend base_url onto the patterns that we match
new_handlers = []
for handler in handlers:
@ -260,9 +266,9 @@ flags['no-mathjax']=(
)
# Add notebook manager flags
flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
'Auto-save a .py script everytime the .ipynb notebook is saved',
'Do not auto-save .py scripts for every notebook'))
flags.update(boolean_flag('script', 'FileContentsManager.save_script',
'DEPRECATED, IGNORED',
'DEPRECATED, IGNORED'))
aliases = dict(base_aliases)
@ -298,7 +304,7 @@ class NotebookApp(BaseIPythonApplication):
classes = [
KernelManager, ProfileDir, Session, MappingKernelManager,
NotebookManager, FileNotebookManager, NotebookNotary,
ContentsManager, FileContentsManager, NotebookNotary,
]
flags = Dict(flags)
aliases = Dict(aliases)
@ -333,8 +339,34 @@ class NotebookApp(BaseIPythonApplication):
self.file_to_run = base
self.notebook_dir = path
# Network related information.
# Network related information
allow_origin = Unicode('', config=True,
help="""Set the Access-Control-Allow-Origin header
Use '*' to allow any origin to access your server.
Takes precedence over allow_origin_pat.
"""
)
allow_origin_pat = Unicode('', config=True,
help="""Use a regular expression for the Access-Control-Allow-Origin header
Requests from an origin matching the expression will get replies with:
Access-Control-Allow-Origin: origin
where `origin` is the origin of the request.
Ignored if allow_origin is set.
"""
)
allow_credentials = Bool(False, config=True,
help="Set the Access-Control-Allow-Credentials: true header"
)
ip = Unicode('localhost', config=True,
help="The IP address the notebook server will listen on."
)
@ -357,6 +389,14 @@ class NotebookApp(BaseIPythonApplication):
help="""The full path to a private key file for usage with SSL/TLS."""
)
cookie_secret_file = Unicode(config=True,
help="""The file where the cookie secret is stored."""
)
def _cookie_secret_file_default(self):
if self.profile_dir is None:
return ''
return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
cookie_secret = Bytes(b'', config=True,
help="""The random bytes used to secure cookies.
By default this is a new random number every time you start the Notebook.
@ -367,7 +407,26 @@ class NotebookApp(BaseIPythonApplication):
"""
)
def _cookie_secret_default(self):
return os.urandom(1024)
if os.path.exists(self.cookie_secret_file):
with io.open(self.cookie_secret_file, 'rb') as f:
return f.read()
else:
secret = base64.encodestring(os.urandom(1024))
self._write_cookie_secret_file(secret)
return secret
def _write_cookie_secret_file(self, secret):
"""write my secret to my secret_file"""
self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
with io.open(self.cookie_secret_file, 'wb') as f:
f.write(secret)
try:
os.chmod(self.cookie_secret_file, 0o600)
except OSError:
self.log.warn(
"Could not set permissions on %s",
self.cookie_secret_file
)
password = Unicode(u'', config=True,
help="""Hashed password to use for web authentication.
@ -456,6 +515,13 @@ class NotebookApp(BaseIPythonApplication):
def _nbextensions_path_default(self):
return [os.path.join(get_ipython_dir(), 'nbextensions')]
websocket_url = Unicode("", config=True,
help="""The base URL for websockets,
if it differs from the HTTP server (hint: it almost certainly doesn't).
Should be in the form of an HTTP origin: ws[s]://hostname[:port]
"""
)
mathjax_url = Unicode("", config=True,
help="""The url for MathJax.js."""
)
@ -482,13 +548,7 @@ class NotebookApp(BaseIPythonApplication):
return url
# no local mathjax, serve from CDN
if self.certfile:
# HTTPS: load from Rackspace CDN, because SSL certificate requires it
host = u"https://c328740.ssl.cf1.rackcdn.com"
else:
host = u"http://cdn.mathjax.org"
url = host + u"/mathjax/latest/MathJax.js"
url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
self.log.info("Using MathJax from CDN: %s", url)
return url
@ -499,7 +559,7 @@ class NotebookApp(BaseIPythonApplication):
else:
self.log.info("Using MathJax: %s", new)
notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
config=True,
help='The notebook manager class to use.'
)
@ -563,7 +623,7 @@ class NotebookApp(BaseIPythonApplication):
raise TraitError("No such notebook dir: %r" % new)
# setting App.notebook_dir implies setting notebook and kernel dirs as well
self.config.FileNotebookManager.notebook_dir = new
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new
@ -589,11 +649,8 @@ class NotebookApp(BaseIPythonApplication):
def init_kernel_argv(self):
"""construct the kernel arguments"""
self.kernel_argv = []
# Kernel should inherit default config file from frontend
self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
# Kernel should get *absolute* path to profile directory
self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
self.kernel_argv = ["--profile-dir", self.profile_dir.location]
def init_configurables(self):
# force Session default to be secure
@ -603,10 +660,12 @@ class NotebookApp(BaseIPythonApplication):
parent=self, log=self.log, kernel_argv=self.kernel_argv,
connection_dir = self.profile_dir.security_dir,
)
kls = import_item(self.notebook_manager_class)
self.notebook_manager = kls(parent=self, log=self.log)
kls = import_item(self.contents_manager_class)
self.contents_manager = kls(parent=self, log=self.log)
kls = import_item(self.session_manager_class)
self.session_manager = kls(parent=self, log=self.log)
self.session_manager = kls(parent=self, log=self.log,
kernel_manager=self.kernel_manager,
contents_manager=self.contents_manager)
kls = import_item(self.cluster_manager_class)
self.cluster_manager = kls(parent=self, log=self.log)
self.cluster_manager.update_profiles()
@ -625,8 +684,13 @@ class NotebookApp(BaseIPythonApplication):
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
self.webapp_settings['allow_origin'] = self.allow_origin
if self.allow_origin_pat:
self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
self.webapp_settings['allow_credentials'] = self.allow_credentials
self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.notebook_manager,
self, self.kernel_manager, self.contents_manager,
self.cluster_manager, self.session_manager, self.kernel_spec_manager,
self.log, self.base_url, self.webapp_settings,
self.jinja_environment_options
@ -717,8 +781,6 @@ class NotebookApp(BaseIPythonApplication):
This doesn't work on Windows.
"""
# FIXME: remove this delay when pyzmq dependency is >= 2.1.11
time.sleep(0.1)
info = self.log.info
info('interrupted')
print(self.notebook_info())
@ -778,7 +840,7 @@ class NotebookApp(BaseIPythonApplication):
def notebook_info(self):
"Return the current working directory and the server url information"
info = self.notebook_manager.info_string() + "\n"
info = self.contents_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The IPython Notebook is running at: %s" % self.display_url
@ -790,6 +852,7 @@ class NotebookApp(BaseIPythonApplication):
'secure': bool(self.certfile),
'base_url': self.base_url,
'notebook_dir': os.path.abspath(self.notebook_dir),
'pid': os.getpid()
}
def write_server_info_file(self):
@ -863,8 +926,17 @@ def list_running_servers(profile='default'):
for file in os.listdir(pd.security_dir):
if file.startswith('nbserver-'):
with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
yield json.load(f)
info = json.load(f)
# Simple check whether that process is really still running
if check_pid(info['pid']):
yield info
else:
# If the process has died, try to delete its info file
try:
os.unlink(file)
except OSError:
pass # TODO: This should warn or log or something
#-----------------------------------------------------------------------------
# Main entry point
#-----------------------------------------------------------------------------

@ -21,7 +21,6 @@ from zmq.eventloop import ioloop
from IPython.config.configurable import LoggingConfigurable
from IPython.utils.traitlets import Dict, Instance, CFloat
from IPython.parallel.apps.ipclusterapp import IPClusterStart
from IPython.core.profileapp import list_profiles_in
from IPython.core.profiledir import ProfileDir
from IPython.utils import py3compat
@ -33,17 +32,6 @@ from IPython.utils.path import get_ipython_dir
#-----------------------------------------------------------------------------
class DummyIPClusterStart(IPClusterStart):
"""Dummy subclass to skip init steps that conflict with global app.
Instantiating and initializing this class should result in fully configured
launchers, but no other side effects or state.
"""
def init_signal(self):
pass
def reinit_logging(self):
pass
class ClusterManager(LoggingConfigurable):
@ -59,6 +47,20 @@ class ClusterManager(LoggingConfigurable):
return IOLoop.instance()
def build_launchers(self, profile_dir):
from IPython.parallel.apps.ipclusterapp import IPClusterStart
class DummyIPClusterStart(IPClusterStart):
"""Dummy subclass to skip init steps that conflict with global app.
Instantiating and initializing this class should result in fully configured
launchers, but no other side effects or state.
"""
def init_signal(self):
pass
def reinit_logging(self):
pass
starter = DummyIPClusterStart(log=self.log)
starter.initialize(['--profile-dir', profile_dir])
cl = starter.controller_launcher

@ -0,0 +1,531 @@
"""A contents manager that uses the local file system for storage."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import base64
import io
import os
import glob
import shutil
from tornado import web
from .manager import ContentsManager
from IPython.nbformat import current
from IPython.utils.path import ensure_dir_exists
from IPython.utils.traitlets import Unicode, Bool, TraitError
from IPython.utils.py3compat import getcwd
from IPython.utils import tz
from IPython.html.utils import is_hidden, to_os_path, url_path_join
class FileContentsManager(ContentsManager):
root_dir = Unicode(getcwd(), config=True)
save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
def _save_script_changed(self):
self.log.warn("""
Automatically saving notebooks as scripts has been removed.
Use `ipython nbconvert --to python [notebook]` instead.
""")
def _root_dir_changed(self, name, old, new):
"""Do a bit of validation of the root_dir."""
if not os.path.isabs(new):
# If we receive a non-absolute path, make it absolute.
self.root_dir = os.path.abspath(new)
return
if not os.path.isdir(new):
raise TraitError("%r is not a directory" % new)
checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
help="""The directory name in which to keep file checkpoints
This is a path relative to the file's own directory.
By default, it is .ipynb_checkpoints
"""
)
def _copy(self, src, dest):
"""copy src to dest
like shutil.copy2, but log errors in copystat
"""
shutil.copyfile(src, dest)
try:
shutil.copystat(src, dest)
except OSError as e:
self.log.debug("copystat on %s failed", dest, exc_info=True)
def _get_os_path(self, name=None, path=''):
"""Given a filename and API path, return its file system
path.
Parameters
----------
name : string
A filename
path : string
The relative API path to the named file.
Returns
-------
path : string
API path to be evaluated relative to root_dir.
"""
if name is not None:
path = url_path_join(path, name)
return to_os_path(path, self.root_dir)
def path_exists(self, path):
"""Does the API-style path refer to an extant directory?
API-style wrapper for os.path.isdir
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to root_dir).
Returns
-------
exists : bool
Whether the path is indeed a directory.
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return os.path.isdir(os_path)
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to root_dir).
Returns
-------
exists : bool
Whether the path is hidden.
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return is_hidden(os_path, self.root_dir)
def file_exists(self, name, path=''):
"""Returns True if the file exists, else returns False.
API-style wrapper for os.path.isfile
Parameters
----------
name : string
The name of the file you are checking.
path : string
The relative path to the file's directory (with '/' as separator)
Returns
-------
exists : bool
Whether the file exists.
"""
path = path.strip('/')
nbpath = self._get_os_path(name, path=path)
return os.path.isfile(nbpath)
def exists(self, name=None, path=''):
"""Returns True if the path [and name] exists, else returns False.
API-style wrapper for os.path.exists
Parameters
----------
name : string
The name of the file you are checking.
path : string
The relative path to the file's directory (with '/' as separator)
Returns
-------
exists : bool
Whether the target exists.
"""
path = path.strip('/')
os_path = self._get_os_path(name, path=path)
return os.path.exists(os_path)
def _base_model(self, name, path=''):
"""Build the common base of a contents model"""
os_path = self._get_os_path(name, path)
info = os.stat(os_path)
last_modified = tz.utcfromtimestamp(info.st_mtime)
created = tz.utcfromtimestamp(info.st_ctime)
# Create the base model.
model = {}
model['name'] = name
model['path'] = path
model['last_modified'] = last_modified
model['created'] = created
model['content'] = None
model['format'] = None
return model
def _dir_model(self, name, path='', content=True):
"""Build a model for a directory
if content is requested, will include a listing of the directory
"""
os_path = self._get_os_path(name, path)
four_o_four = u'directory does not exist: %r' % os_path
if not os.path.isdir(os_path):
raise web.HTTPError(404, four_o_four)
elif is_hidden(os_path, self.root_dir):
self.log.info("Refusing to serve hidden directory %r, via 404 Error",
os_path
)
raise web.HTTPError(404, four_o_four)
if name is None:
if '/' in path:
path, name = path.rsplit('/', 1)
else:
name = ''
model = self._base_model(name, path)
model['type'] = 'directory'
dir_path = u'{}/{}'.format(path, name)
if content:
model['content'] = contents = []
for os_path in glob.glob(self._get_os_path('*', dir_path)):
name = os.path.basename(os_path)
if self.should_list(name) and not is_hidden(os_path, self.root_dir):
contents.append(self.get_model(name=name, path=dir_path, content=False))
model['format'] = 'json'
return model
def _file_model(self, name, path='', content=True):
"""Build a model for a file
if content is requested, include the file contents.
UTF-8 text files will be unicode, binary files will be base64-encoded.
"""
model = self._base_model(name, path)
model['type'] = 'file'
if content:
os_path = self._get_os_path(name, path)
with io.open(os_path, 'rb') as f:
bcontent = f.read()
try:
model['content'] = bcontent.decode('utf8')
except UnicodeError as e:
model['content'] = base64.encodestring(bcontent).decode('ascii')
model['format'] = 'base64'
else:
model['format'] = 'text'
return model
def _notebook_model(self, name, path='', content=True):
"""Build a notebook model
if content is requested, the notebook content will be populated
as a JSON structure (not double-serialized)
"""
model = self._base_model(name, path)
model['type'] = 'notebook'
if content:
os_path = self._get_os_path(name, path)
with io.open(os_path, 'r', encoding='utf-8') as f:
try:
nb = current.read(f, u'json')
except Exception as e:
raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
self.mark_trusted_cells(nb, name, path)
model['content'] = nb
model['format'] = 'json'
return model
def get_model(self, name, path='', content=True):
""" Takes a path and name for an entity and returns its model
Parameters
----------
name : str
the name of the target
path : str
the API path that describes the relative path for the target
Returns
-------
model : dict
the contents model. If content=True, returns the contents
of the file or directory as well.
"""
path = path.strip('/')
if not self.exists(name=name, path=path):
raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name))
os_path = self._get_os_path(name, path)
if os.path.isdir(os_path):
model = self._dir_model(name, path, content)
elif name.endswith('.ipynb'):
model = self._notebook_model(name, path, content)
else:
model = self._file_model(name, path, content)
return model
def _save_notebook(self, os_path, model, name='', path=''):
"""save a notebook file"""
# Save the notebook file
nb = current.to_notebook_json(model['content'])
self.check_and_sign(nb, name, path)
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''
with io.open(os_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'json')
def _save_file(self, os_path, model, name='', path=''):
"""save a non-notebook file"""
fmt = model.get('format', None)
if fmt not in {'text', 'base64'}:
raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
try:
content = model['content']
if fmt == 'text':
bcontent = content.encode('utf8')
else:
b64_bytes = content.encode('ascii')
bcontent = base64.decodestring(b64_bytes)
except Exception as e:
raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
with io.open(os_path, 'wb') as f:
f.write(bcontent)
def _save_directory(self, os_path, model, name='', path=''):
"""create a directory"""
if is_hidden(os_path, self.root_dir):
raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
if not os.path.exists(os_path):
os.mkdir(os_path)
elif not os.path.isdir(os_path):
raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
else:
self.log.debug("Directory %r already exists", os_path)
def save(self, model, name='', path=''):
"""Save the file model and return the model with no content."""
path = path.strip('/')
if 'type' not in model:
raise web.HTTPError(400, u'No file type provided')
if 'content' not in model and model['type'] != 'directory':
raise web.HTTPError(400, u'No file content provided')
# One checkpoint should always exist
if self.file_exists(name, path) and not self.list_checkpoints(name, path):
self.create_checkpoint(name, path)
new_path = model.get('path', path).strip('/')
new_name = model.get('name', name)
if path != new_path or name != new_name:
self.rename(name, path, new_name, new_path)
os_path = self._get_os_path(new_name, new_path)
self.log.debug("Saving %s", os_path)
try:
if model['type'] == 'notebook':
self._save_notebook(os_path, model, new_name, new_path)
elif model['type'] == 'file':
self._save_file(os_path, model, new_name, new_path)
elif model['type'] == 'directory':
self._save_directory(os_path, model, new_name, new_path)
else:
raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
except web.HTTPError:
raise
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
model = self.get_model(new_name, new_path, content=False)
return model
def update(self, model, name, path=''):
"""Update the file's path and/or name
For use in PATCH requests, to enable renaming a file without
re-uploading its contents. Only used for renaming at the moment.
"""
path = path.strip('/')
new_name = model.get('name', name)
new_path = model.get('path', path).strip('/')
if path != new_path or name != new_name:
self.rename(name, path, new_name, new_path)
model = self.get_model(new_name, new_path, content=False)
return model
def delete(self, name, path=''):
"""Delete file by name and path."""
path = path.strip('/')
os_path = self._get_os_path(name, path)
rm = os.unlink
if os.path.isdir(os_path):
listing = os.listdir(os_path)
# don't delete non-empty directories (checkpoints dir doesn't count)
if listing and listing != [self.checkpoint_dir]:
raise web.HTTPError(400, u'Directory %s not empty' % os_path)
elif not os.path.isfile(os_path):
raise web.HTTPError(404, u'File does not exist: %s' % os_path)
# clear checkpoints
for checkpoint in self.list_checkpoints(name, path):
checkpoint_id = checkpoint['id']
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if os.path.isfile(cp_path):
self.log.debug("Unlinking checkpoint %s", cp_path)
os.unlink(cp_path)
if os.path.isdir(os_path):
self.log.debug("Removing directory %s", os_path)
shutil.rmtree(os_path)
else:
self.log.debug("Unlinking file %s", os_path)
rm(os_path)
def rename(self, old_name, old_path, new_name, new_path):
"""Rename a file."""
old_path = old_path.strip('/')
new_path = new_path.strip('/')
if new_name == old_name and new_path == old_path:
return
new_os_path = self._get_os_path(new_name, new_path)
old_os_path = self._get_os_path(old_name, old_path)
# Should we proceed with the move?
if os.path.isfile(new_os_path):
raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path)
# Move the file
try:
shutil.move(old_os_path, new_os_path)
except Exception as e:
raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e))
# Move the checkpoints
old_checkpoints = self.list_checkpoints(old_name, old_path)
for cp in old_checkpoints:
checkpoint_id = cp['id']
old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
if os.path.isfile(old_cp_path):
self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
shutil.move(old_cp_path, new_cp_path)
# Checkpoint-related utilities
def get_checkpoint_path(self, checkpoint_id, name, path=''):
"""find the path to a checkpoint"""
path = path.strip('/')
basename, ext = os.path.splitext(name)
filename = u"{name}-{checkpoint_id}{ext}".format(
name=basename,
checkpoint_id=checkpoint_id,
ext=ext,
)
os_path = self._get_os_path(path=path)
cp_dir = os.path.join(os_path, self.checkpoint_dir)
ensure_dir_exists(cp_dir)
cp_path = os.path.join(cp_dir, filename)
return cp_path
def get_checkpoint_model(self, checkpoint_id, name, path=''):
"""construct the info dict for a given checkpoint"""
path = path.strip('/')
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
stats = os.stat(cp_path)
last_modified = tz.utcfromtimestamp(stats.st_mtime)
info = dict(
id = checkpoint_id,
last_modified = last_modified,
)
return info
# public checkpoint API
def create_checkpoint(self, name, path=''):
"""Create a checkpoint from the current state of a file"""
path = path.strip('/')
src_path = self._get_os_path(name, path)
# only the one checkpoint ID:
checkpoint_id = u"checkpoint"
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
self.log.debug("creating checkpoint for %s", name)
self._copy(src_path, cp_path)
# return the checkpoint info
return self.get_checkpoint_model(checkpoint_id, name, path)
def list_checkpoints(self, name, path=''):
"""list the checkpoints for a given file
This contents manager currently only supports one checkpoint per file.
"""
path = path.strip('/')
checkpoint_id = "checkpoint"
os_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.exists(os_path):
return []
else:
return [self.get_checkpoint_model(checkpoint_id, name, path)]
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""restore a file to a checkpointed state"""
path = path.strip('/')
self.log.info("restoring %s from checkpoint %s", name, checkpoint_id)
nb_path = self._get_os_path(name, path)
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.isfile(cp_path):
self.log.debug("checkpoint file does not exist: %s", cp_path)
raise web.HTTPError(404,
u'checkpoint does not exist: %s-%s' % (name, checkpoint_id)
)
# ensure notebook is readable (never restore from an unreadable notebook)
if cp_path.endswith('.ipynb'):
with io.open(cp_path, 'r', encoding='utf-8') as f:
current.read(f, u'json')
self._copy(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path)
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a file's checkpoint"""
path = path.strip('/')
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.isfile(cp_path):
raise web.HTTPError(404,
u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
)
self.log.debug("unlinking %s", cp_path)
os.unlink(cp_path)
def info_string(self):
return "Serving notebooks from local directory: %s" % self.root_dir
def get_kernel_path(self, name, path='', model=None):
"""Return the initial working dir a kernel associated with a given notebook"""
return os.path.join(self.root_dir, path)

@ -0,0 +1,286 @@
"""Tornado handlers for the contents web service."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
from tornado import web
from IPython.html.utils import url_path_join, url_escape
from IPython.utils.jsonutil import date_default
from IPython.html.base.handlers import (IPythonHandler, json_errors,
file_path_regex, path_regex,
file_name_regex)
def sort_key(model):
"""key function for case-insensitive sort by name and type"""
iname = model['name'].lower()
type_key = {
'directory' : '0',
'notebook' : '1',
'file' : '2',
}.get(model['type'], '9')
return u'%s%s' % (type_key, iname)
class ContentsHandler(IPythonHandler):
SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
def location_url(self, name, path):
"""Return the full URL location of a file.
Parameters
----------
name : unicode
The base name of the file, such as "foo.ipynb".
path : unicode
The API path of the file, such as "foo/bar".
"""
return url_escape(url_path_join(
self.base_url, 'api', 'contents', path, name
))
def _finish_model(self, model, location=True):
"""Finish a JSON request with a model, setting relevant headers, etc."""
if location:
location = self.location_url(model['name'], model['path'])
self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified'])
self.finish(json.dumps(model, default=date_default))
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""Return a model for a file or directory.
A directory model contains a list of models (without content)
of the files and directories it contains.
"""
path = path or ''
model = self.contents_manager.get_model(name=name, path=path)
if model['type'] == 'directory':
# group listing by type, then by name (case-insensitive)
# FIXME: sorting should be done in the frontends
model['content'].sort(key=sort_key)
self._finish_model(model, location=False)
@web.authenticated
@json_errors
def patch(self, path='', name=None):
"""PATCH renames a notebook without re-uploading content."""
cm = self.contents_manager
if name is None:
raise web.HTTPError(400, u'Filename missing')
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, u'JSON body missing')
model = cm.update(model, name, path)
self._finish_model(model)
def _copy(self, copy_from, path, copy_to=None):
"""Copy a file, optionally specifying the new name.
"""
self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
copy_from=copy_from,
path=path,
copy_to=copy_to or '',
))
model = self.contents_manager.copy(copy_from, copy_to, path)
self.set_status(201)
self._finish_model(model)
def _upload(self, model, path, name=None):
"""Handle upload of a new file
If name specified, create it in path/name,
otherwise create a new untitled file in path.
"""
self.log.info(u"Uploading file to %s/%s", path, name or '')
if name:
model['name'] = name
model = self.contents_manager.create_file(model, path)
self.set_status(201)
self._finish_model(model)
def _create_empty_file(self, path, name=None, ext='.ipynb'):
"""Create an empty file in path
If name specified, create it in path/name.
"""
self.log.info(u"Creating new file in %s/%s", path, name or '')
model = {}
if name:
model['name'] = name
model = self.contents_manager.create_file(model, path=path, ext=ext)
self.set_status(201)
self._finish_model(model)
def _save(self, model, path, name):
"""Save an existing file."""
self.log.info(u"Saving file at %s/%s", path, name)
model = self.contents_manager.save(model, name, path)
if model['path'] != path.strip('/') or model['name'] != name:
# a rename happened, set Location header
location = True
else:
location = False
self._finish_model(model, location)
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""Create a new file or directory in the specified path.
POST creates new files or directories. The server always decides on the name.
POST /api/contents/path
New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty.
POST /api/contents/path
with body {"copy_from" : "OtherNotebook.ipynb"}
New copy of OtherNotebook in path
"""
if name is not None:
path = u'{}/{}'.format(path, name)
cm = self.contents_manager
if cm.file_exists(path):
raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
if not cm.path_exists(path):
raise web.HTTPError(404, "No such directory: %s" % path)
model = self.get_json_body()
if model is not None:
copy_from = model.get('copy_from')
ext = model.get('ext', '.ipynb')
if model.get('content') is not None:
if copy_from:
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._upload(model, path)
elif copy_from:
self._copy(copy_from, path)
else:
self._create_empty_file(path, ext=ext)
else:
self._create_empty_file(path)
@web.authenticated
@json_errors
def put(self, path='', name=None):
"""Saves the file in the location specified by name and path.
PUT is very similar to POST, but the requester specifies the name,
whereas with POST, the server picks the name.
PUT /api/contents/path/Name.ipynb
Save notebook at ``path/Name.ipynb``. Notebook structure is specified
in `content` key of JSON request body. If content is not specified,
create a new empty notebook.
PUT /api/contents/path/Name.ipynb
with JSON body::
{
"copy_from" : "[path/to/]OtherNotebook.ipynb"
}
Copy OtherNotebook to Name
"""
if name is None:
raise web.HTTPError(400, "name must be specified with PUT.")
model = self.get_json_body()
if model:
copy_from = model.get('copy_from')
if copy_from:
if model.get('content'):
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._copy(copy_from, path, name)
elif self.contents_manager.file_exists(name, path):
self._save(model, path, name)
else:
self._upload(model, path, name)
else:
self._create_empty_file(path, name)
@web.authenticated
@json_errors
def delete(self, path='', name=None):
"""delete a file in the given path"""
cm = self.contents_manager
self.log.warn('delete %s:%s', path, name)
cm.delete(name, path)
self.set_status(204)
self.finish()
class CheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""get lists checkpoints for a file"""
cm = self.contents_manager
checkpoints = cm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default)
self.finish(data)
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""post creates a new checkpoint"""
cm = self.contents_manager
checkpoint = cm.create_checkpoint(name, path)
data = json.dumps(checkpoint, default=date_default)
location = url_path_join(self.base_url, 'api/contents',
path, name, 'checkpoints', checkpoint['id'])
self.set_header('Location', url_escape(location))
self.set_status(201)
self.finish(data)
class ModifyCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated
@json_errors
def post(self, path, name, checkpoint_id):
"""post restores a file from a checkpoint"""
cm = self.contents_manager
cm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@web.authenticated
@json_errors
def delete(self, path, name, checkpoint_id):
"""delete clears a checkpoint for a given file"""
cm = self.contents_manager
cm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
default_handlers = [
(r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
(r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
ModifyCheckpointsHandler),
(r"/api/contents%s" % file_path_regex, ContentsHandler),
(r"/api/contents%s" % path_regex, ContentsHandler),
]

@ -0,0 +1,333 @@
"""A base class for contents managers."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from fnmatch import fnmatch
import itertools
import os
from tornado.web import HTTPError
from IPython.config.configurable import LoggingConfigurable
from IPython.nbformat import current, sign
from IPython.utils.traitlets import Instance, Unicode, List
class ContentsManager(LoggingConfigurable):
"""Base class for serving files and directories.
This serves any text or binary file,
as well as directories,
with special handling for JSON notebook documents.
Most APIs take a path argument,
which is always an API-style unicode path,
and always refers to a directory.
- unicode, not url-escaped
- '/'-separated
- leading and trailing '/' will be stripped
- if unspecified, path defaults to '',
indicating the root path.
name is also unicode, and refers to a specfic target:
- unicode, not url-escaped
- must not contain '/'
- It refers to an individual filename
- It may refer to a directory name,
in the case of listing or creating directories.
"""
notary = Instance(sign.NotebookNotary)
def _notary_default(self):
return sign.NotebookNotary(parent=self)
hide_globs = List(Unicode, [
u'__pycache__', '*.pyc', '*.pyo',
'.DS_Store', '*.so', '*.dylib', '*~',
], config=True, help="""
Glob patterns to hide in file and directory listings.
""")
untitled_notebook = Unicode("Untitled", config=True,
help="The base name used when creating untitled notebooks."
)
untitled_file = Unicode("untitled", config=True,
help="The base name used when creating untitled files."
)
untitled_directory = Unicode("Untitled Folder", config=True,
help="The base name used when creating untitled directories."
)
# ContentsManager API part 1: methods that must be
# implemented in subclasses.
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
Like os.path.isdir
Override this method in subclasses.
Parameters
----------
path : string
The path to check
Returns
-------
exists : bool
Whether the path does indeed exist.
"""
raise NotImplementedError
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to root dir).
Returns
-------
hidden : bool
Whether the path is hidden.
"""
raise NotImplementedError
def file_exists(self, name, path=''):
"""Does a file exist at the given name and path?
Like os.path.isfile
Override this method in subclasses.
Parameters
----------
name : string
The name of the file you are checking.
path : string
The relative path to the file's directory (with '/' as separator)
Returns
-------
exists : bool
Whether the file exists.
"""
raise NotImplementedError('must be implemented in a subclass')
def exists(self, name, path=''):
"""Does a file or directory exist at the given name and path?
Like os.path.exists
Parameters
----------
name : string
The name of the file you are checking.
path : string
The relative path to the file's directory (with '/' as separator)
Returns
-------
exists : bool
Whether the target exists.
"""
return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name))
def get_model(self, name, path='', content=True):
"""Get the model of a file or directory with or without content."""
raise NotImplementedError('must be implemented in a subclass')
def save(self, model, name, path=''):
"""Save the file or directory and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
def update(self, model, name, path=''):
"""Update the file or directory and return the model with no content.
For use in PATCH requests, to enable renaming a file without
re-uploading its contents. Only used for renaming at the moment.
"""
raise NotImplementedError('must be implemented in a subclass')
def delete(self, name, path=''):
"""Delete file or directory by name and path."""
raise NotImplementedError('must be implemented in a subclass')
def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a file
Returns a checkpoint_id for the new checkpoint.
"""
raise NotImplementedError("must be implemented in a subclass")
def list_checkpoints(self, name, path=''):
"""Return a list of checkpoints for a given file"""
return []
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a file from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass")
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a checkpoint for a file"""
raise NotImplementedError("must be implemented in a subclass")
# ContentsManager API part 2: methods that have useable default
# implementations, but can be overridden in subclasses.
def info_string(self):
return "Serving contents"
def get_kernel_path(self, name, path='', model=None):
""" Return the path to start kernel in """
return path
def increment_filename(self, filename, path=''):
"""Increment a filename until it is unique.
Parameters
----------
filename : unicode
The name of a file, including extension
path : unicode
The API path of the target's directory
Returns
-------
name : unicode
A filename that is unique, based on the input filename.
"""
path = path.strip('/')
basename, ext = os.path.splitext(filename)
for i in itertools.count():
name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
ext=ext)
if not self.file_exists(name, path):
break
return name
def create_file(self, model=None, path='', ext='.ipynb'):
"""Create a new file or directory and return its model with no content."""
path = path.strip('/')
if model is None:
model = {}
if 'content' not in model and model.get('type', None) != 'directory':
if ext == '.ipynb':
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
model['type'] = 'notebook'
model['format'] = 'json'
else:
model['content'] = ''
model['type'] = 'file'
model['format'] = 'text'
if 'name' not in model:
if model['type'] == 'directory':
untitled = self.untitled_directory
elif model['type'] == 'notebook':
untitled = self.untitled_notebook
elif model['type'] == 'file':
untitled = self.untitled_file
else:
raise HTTPError(400, "Unexpected model type: %r" % model['type'])
model['name'] = self.increment_filename(untitled + ext, path)
model['path'] = path
model = self.save(model, model['name'], model['path'])
return model
def copy(self, from_name, to_name=None, path=''):
"""Copy an existing file and return its new model.
If to_name not specified, increment `from_name-Copy#.ext`.
copy_from can be a full path to a file,
or just a base name. If a base name, `path` is used.
"""
path = path.strip('/')
if '/' in from_name:
from_path, from_name = from_name.rsplit('/', 1)
else:
from_path = path
model = self.get_model(from_name, from_path)
if model['type'] == 'directory':
raise HTTPError(400, "Can't copy directories")
if not to_name:
base, ext = os.path.splitext(from_name)
copy_name = u'{0}-Copy{1}'.format(base, ext)
to_name = self.increment_filename(copy_name, path)
model['name'] = to_name
model['path'] = path
model = self.save(model, to_name, path)
return model
def log_info(self):
self.log.info(self.info_string())
def trust_notebook(self, name, path=''):
"""Explicitly trust a notebook
Parameters
----------
name : string
The filename of the notebook
path : string
The notebook's directory
"""
model = self.get_model(name, path)
nb = model['content']
self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True)
self.save(model, name, path)
def check_and_sign(self, nb, name='', path=''):
"""Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks.
Parameters
----------
nb : dict
The notebook object (in nbformat.current format)
name : string
The filename of the notebook (for logging)
path : string
The notebook's directory (for logging)
"""
if self.notary.check_cells(nb):
self.notary.sign(nb)
else:
self.log.warn("Saving untrusted notebook %s/%s", path, name)
def mark_trusted_cells(self, nb, name='', path=''):
"""Mark cells as trusted if the notebook signature matches.
Called as a part of loading notebooks.
Parameters
----------
nb : dict
The notebook object (in nbformat.current format)
name : string
The filename of the notebook (for logging)
path : string
The notebook's directory (for logging)
"""
trusted = self.notary.check_signature(nb)
if not trusted:
self.log.warn("Notebook %s/%s is not trusted", path, name)
self.notary.mark_cells(nb, trusted)
def should_list(self, name):
"""Should this file/directory name be displayed in a listing?"""
return not any(fnmatch(name, glob) for glob in self.hide_globs)

@ -1,6 +1,7 @@
# coding: utf-8
"""Test the notebooks webservice API."""
"""Test the contents webservice API."""
import base64
import io
import json
import os
@ -21,23 +22,21 @@ from IPython.utils import py3compat
from IPython.utils.data import uniq_stable
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service.
def notebooks_only(nb_list):
return [nb for nb in nb_list if nb['type']=='notebook']
def notebooks_only(dir_model):
return [nb for nb in dir_model['content'] if nb['type']=='notebook']
def dirs_only(nb_list):
return [x for x in nb_list if x['type']=='directory']
def dirs_only(dir_model):
return [x for x in dir_model['content'] if x['type']=='directory']
class NBAPI(object):
"""Wrapper for notebook API calls."""
class API(object):
"""Wrapper for contents API calls."""
def __init__(self, base_url):
self.base_url = base_url
def _req(self, verb, path, body=None):
response = requests.request(verb,
url_path_join(self.base_url, 'api/notebooks', path),
url_path_join(self.base_url, 'api/contents', path),
data=body,
)
response.raise_for_status()
@ -49,8 +48,11 @@ class NBAPI(object):
def read(self, name, path='/'):
return self._req('GET', url_path_join(path, name))
def create_untitled(self, path='/'):
return self._req('POST', path)
def create_untitled(self, path='/', ext=None):
body = None
if ext:
body = json.dumps({'ext': ext})
return self._req('POST', path, body)
def upload_untitled(self, body, path='/'):
return self._req('POST', path, body)
@ -65,6 +67,9 @@ class NBAPI(object):
def upload(self, name, body, path='/'):
return self._req('PUT', url_path_join(path, name), body)
def mkdir(self, name, path='/'):
return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'}))
def copy(self, copy_from, copy_to, path='/'):
body = json.dumps({'copy_from':copy_from})
return self._req('PUT', url_path_join(path, copy_to), body)
@ -112,8 +117,20 @@ class APITest(NotebookTestBase):
del dirs[0] # remove ''
top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
@staticmethod
def _blob_for_name(name):
return name.encode('utf-8') + b'\xFF'
@staticmethod
def _txt_for_name(name):
return u'%s text file' % name
def setUp(self):
nbdir = self.notebook_dir.name
self.blob = os.urandom(100)
self.b64_blob = base64.encodestring(self.blob).decode('ascii')
for d in (self.dirs + self.hidden_dirs):
d.replace('/', os.sep)
@ -122,12 +139,22 @@ class APITest(NotebookTestBase):
for d, name in self.dirs_nbs:
d = d.replace('/', os.sep)
# create a notebook
with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
encoding='utf-8') as f:
nb = new_notebook(name=name)
write(nb, f, format='ipynb')
self.nb_api = NBAPI(self.base_url())
# create a text file
with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
encoding='utf-8') as f:
f.write(self._txt_for_name(name))
# create a binary file
with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f:
f.write(self._blob_for_name(name))
self.api = API(self.base_url())
def tearDown(self):
nbdir = self.notebook_dir.name
@ -139,175 +166,287 @@ class APITest(NotebookTestBase):
os.unlink(pjoin(nbdir, 'inroot.ipynb'))
def test_list_notebooks(self):
nbs = notebooks_only(self.nb_api.list().json())
nbs = notebooks_only(self.api.list().json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
nbs = notebooks_only(self.nb_api.list(u'/unicodé/').json())
nbs = notebooks_only(self.api.list(u'/unicodé/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
self.assertEqual(nbs[0]['path'], u'unicodé')
nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
nbs = notebooks_only(self.api.list('/foo/bar/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'baz.ipynb')
self.assertEqual(nbs[0]['path'], 'foo/bar')
nbs = notebooks_only(self.nb_api.list('foo').json())
nbs = notebooks_only(self.api.list('foo').json())
self.assertEqual(len(nbs), 4)
nbnames = { normalize('NFC', n['name']) for n in nbs }
expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
expected = { normalize('NFC', name) for name in expected }
self.assertEqual(nbnames, expected)
nbs = notebooks_only(self.nb_api.list('ordering').json())
nbs = notebooks_only(self.api.list('ordering').json())
nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
self.assertEqual(nbnames, expected)
def test_list_dirs(self):
dirs = dirs_only(self.nb_api.list().json())
dirs = dirs_only(self.api.list().json())
dir_names = {normalize('NFC', d['name']) for d in dirs}
self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
def test_list_nonexistant_dir(self):
with assert_http_error(404):
self.nb_api.list('nonexistant')
self.api.list('nonexistant')
def test_get_contents(self):
def test_get_nb_contents(self):
for d, name in self.dirs_nbs:
nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
nb = self.api.read('%s.ipynb' % name, d+'/').json()
self.assertEqual(nb['name'], u'%s.ipynb' % name)
self.assertEqual(nb['type'], 'notebook')
self.assertIn('content', nb)
self.assertEqual(nb['format'], 'json')
self.assertIn('content', nb)
self.assertIn('metadata', nb['content'])
self.assertIsInstance(nb['content']['metadata'], dict)
def test_get_contents_no_such_file(self):
# Name that doesn't exist - should be a 404
with assert_http_error(404):
self.api.read('q.ipynb', 'foo')
def test_get_text_file_contents(self):
for d, name in self.dirs_nbs:
model = self.api.read(u'%s.txt' % name, d+'/').json()
self.assertEqual(model['name'], u'%s.txt' % name)
self.assertIn('content', model)
self.assertEqual(model['format'], 'text')
self.assertEqual(model['type'], 'file')
self.assertEqual(model['content'], self._txt_for_name(name))
# Name that doesn't exist - should be a 404
with assert_http_error(404):
self.api.read('q.txt', 'foo')
def test_get_binary_file_contents(self):
for d, name in self.dirs_nbs:
model = self.api.read(u'%s.blob' % name, d+'/').json()
self.assertEqual(model['name'], u'%s.blob' % name)
self.assertIn('content', model)
self.assertEqual(model['format'], 'base64')
self.assertEqual(model['type'], 'file')
b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
self.assertEqual(model['content'], b64_data)
# Name that doesn't exist - should be a 404
with assert_http_error(404):
self.nb_api.read('q.ipynb', 'foo')
self.api.read('q.txt', 'foo')
def _check_nb_created(self, resp, name, path):
def _check_created(self, resp, name, path, type='notebook'):
self.assertEqual(resp.status_code, 201)
location_header = py3compat.str_to_unicode(resp.headers['Location'])
self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
self.assertEqual(resp.json()['name'], name)
assert os.path.isfile(pjoin(
self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
rjson = resp.json()
self.assertEqual(rjson['name'], name)
self.assertEqual(rjson['path'], path)
self.assertEqual(rjson['type'], type)
isright = os.path.isdir if type == 'directory' else os.path.isfile
assert isright(pjoin(
self.notebook_dir.name,
path.replace('/', os.sep),
name,
))
def test_create_untitled(self):
resp = self.nb_api.create_untitled(path=u'å b')
self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
resp = self.api.create_untitled(path=u'å b')
self._check_created(resp, 'Untitled0.ipynb', u'å b')
# Second time
resp = self.nb_api.create_untitled(path=u'å b')
self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
resp = self.api.create_untitled(path=u'å b')
self._check_created(resp, 'Untitled1.ipynb', u'å b')
# And two directories down
resp = self.nb_api.create_untitled(path='foo/bar')
self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
resp = self.api.create_untitled(path='foo/bar')
self._check_created(resp, 'Untitled0.ipynb', 'foo/bar')
def test_create_untitled_txt(self):
resp = self.api.create_untitled(path='foo/bar', ext='.txt')
self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file')
resp = self.api.read(path='foo/bar', name='untitled0.txt')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'text')
self.assertEqual(model['content'], '')
def test_upload_untitled(self):
nb = new_notebook(name='Upload test')
nbmodel = {'content': nb}
resp = self.nb_api.upload_untitled(path=u'å b',
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload_untitled(path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
self._check_created(resp, 'Untitled0.ipynb', u'å b')
def test_upload(self):
nb = new_notebook(name=u'ignored')
nbmodel = {'content': nb}
resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
self._check_created(resp, u'Upload tést.ipynb', u'å b')
def test_mkdir(self):
resp = self.api.mkdir(u'New ∂ir', path=u'å b')
self._check_created(resp, u'New ∂ir', u'å b', type='directory')
def test_mkdir_hidden_400(self):
with assert_http_error(400):
resp = self.api.mkdir(u'.hidden', path=u'å b')
def test_upload_txt(self):
body = u'ünicode téxt'
model = {
'content' : body,
'format' : 'text',
'type' : 'file',
}
resp = self.api.upload(u'Upload tést.txt', path=u'å b',
body=json.dumps(model))
# check roundtrip
resp = self.api.read(path=u'å b', name=u'Upload tést.txt')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'text')
self.assertEqual(model['content'], body)
def test_upload_b64(self):
body = b'\xFFblob'
b64body = base64.encodestring(body).decode('ascii')
model = {
'content' : b64body,
'format' : 'base64',
'type' : 'file',
}
resp = self.api.upload(u'Upload tést.blob', path=u'å b',
body=json.dumps(model))
# check roundtrip
resp = self.api.read(path=u'å b', name=u'Upload tést.blob')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'base64')
decoded = base64.decodestring(model['content'].encode('ascii'))
self.assertEqual(decoded, body)
def test_upload_v2(self):
nb = v2.new_notebook()
ws = v2.new_worksheet()
nb.worksheets.append(ws)
ws.cells.append(v2.new_code_cell(input='print("hi")'))
nbmodel = {'content': nb}
resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
nbmodel = {'content': nb, 'type': 'notebook'}
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
self._check_created(resp, u'Upload tést.ipynb', u'å b')
resp = self.api.read(u'Upload tést.ipynb', u'å b')
data = resp.json()
self.assertEqual(data['content']['nbformat'], current.nbformat)
self.assertEqual(data['content']['orig_nbformat'], 2)
def test_copy_untitled(self):
resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
self._check_created(resp, u'ç d-Copy0.ipynb', u'å b')
def test_copy(self):
resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
self._check_created(resp, u'cøpy.ipynb', u'å b')
def test_copy_path(self):
resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b')
self._check_created(resp, u'cøpyfoo.ipynb', u'å b')
def test_copy_dir_400(self):
# can't copy directories
with assert_http_error(400):
resp = self.api.copy(u'å b', u'å c')
def test_delete(self):
for d, name in self.dirs_nbs:
resp = self.nb_api.delete('%s.ipynb' % name, d)
resp = self.api.delete('%s.ipynb' % name, d)
self.assertEqual(resp.status_code, 204)
for d in self.dirs + ['/']:
nbs = notebooks_only(self.nb_api.list(d).json())
nbs = notebooks_only(self.api.list(d).json())
self.assertEqual(len(nbs), 0)
def test_delete_dirs(self):
# depth-first delete everything, so we don't try to delete empty directories
for name in sorted(self.dirs + ['/'], key=len, reverse=True):
listing = self.api.list(name).json()['content']
for model in listing:
self.api.delete(model['name'], model['path'])
listing = self.api.list('/').json()['content']
self.assertEqual(listing, [])
def test_delete_non_empty_dir(self):
"""delete non-empty dir raises 400"""
with assert_http_error(400):
self.api.delete(u'å b')
def test_rename(self):
resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
self.assertEqual(resp.json()['name'], 'z.ipynb')
assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
nbs = notebooks_only(self.nb_api.list('foo').json())
nbs = notebooks_only(self.api.list('foo').json())
nbnames = set(n['name'] for n in nbs)
self.assertIn('z.ipynb', nbnames)
self.assertNotIn('a.ipynb', nbnames)
def test_rename_existing(self):
with assert_http_error(409):
self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
self.api.rename('a.ipynb', 'foo', 'b.ipynb')
def test_save(self):
resp = self.nb_api.read('a.ipynb', 'foo')
resp = self.api.read('a.ipynb', 'foo')
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
ws = new_worksheet()
nb.worksheets = [ws]
ws.cells.append(new_heading_cell(u'Created by test ³'))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
with io.open(nbfile, 'r', encoding='utf-8') as f:
newnb = read(f, format='ipynb')
self.assertEqual(newnb.worksheets[0].cells[0].source,
u'Created by test ³')
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
newnb = to_notebook_json(nbcontent)
self.assertEqual(newnb.worksheets[0].cells[0].source,
u'Created by test ³')
# Save and rename
nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
saved = resp.json()
self.assertEqual(saved['name'], 'a2.ipynb')
self.assertEqual(saved['path'], 'foo/bar')
assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
with assert_http_error(404):
self.nb_api.read('a.ipynb', 'foo')
self.api.read('a.ipynb', 'foo')
def test_checkpoints(self):
resp = self.nb_api.read('a.ipynb', 'foo')
r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
resp = self.api.read('a.ipynb', 'foo')
r = self.api.new_checkpoint('a.ipynb', 'foo')
self.assertEqual(r.status_code, 201)
cp1 = r.json()
self.assertEqual(set(cp1), {'id', 'last_modified'})
@ -321,27 +460,26 @@ class APITest(NotebookTestBase):
hcell = new_heading_cell('Created by test')
ws.cells.append(hcell)
# Save
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
# List checkpoints
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, [cp1])
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
# Restore cp1
r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets, [])
# Delete cp1
r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, [])

@ -15,74 +15,74 @@ from IPython.utils.tempdir import TemporaryDirectory
from IPython.utils.traitlets import TraitError
from IPython.html.utils import url_path_join
from ..filenbmanager import FileNotebookManager
from ..nbmanager import NotebookManager
from ..filemanager import FileContentsManager
from ..manager import ContentsManager
class TestFileNotebookManager(TestCase):
class TestFileContentsManager(TestCase):
def test_nb_dir(self):
def test_root_dir(self):
with TemporaryDirectory() as td:
fm = FileNotebookManager(notebook_dir=td)
self.assertEqual(fm.notebook_dir, td)
fm = FileContentsManager(root_dir=td)
self.assertEqual(fm.root_dir, td)
def test_missing_nb_dir(self):
def test_missing_root_dir(self):
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
self.assertRaises(TraitError, FileContentsManager, root_dir=root)
def test_invalid_nb_dir(self):
def test_invalid_root_dir(self):
with NamedTemporaryFile() as tf:
self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
def test_get_os_path(self):
# full filesystem path should be returned with correct operating system
# separators.
with TemporaryDirectory() as td:
nbdir = td
fm = FileNotebookManager(notebook_dir=nbdir)
root = td
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
fs_path = os.path.join(fm.root_dir, *rel_path_list)
self.assertEqual(path, fs_path)
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
self.assertEqual(path, fs_path)
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb', '////')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
self.assertEqual(path, fs_path)
def test_checkpoint_subdir(self):
subd = u'sub ∂ir'
cp_name = 'test-cp.ipynb'
with TemporaryDirectory() as td:
nbdir = td
root = td
os.mkdir(os.path.join(td, subd))
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
self.assertNotEqual(cp_dir, cp_subdir)
self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
class TestContentsManager(TestCase):
class TestNotebookManager(TestCase):
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name
self.notebook_manager = FileNotebookManager(
notebook_dir=self.td,
self.contents_manager = FileContentsManager(
root_dir=self.td,
log=logging.getLogger()
)
def tearDown(self):
self._temp_dir.cleanup()
def make_dir(self, abs_path, rel_path):
"""make subdirectory, rel_path is the relative path
to that directory from the location where the server started"""
@ -91,31 +91,31 @@ class TestNotebookManager(TestCase):
os.makedirs(os_path)
except OSError:
print("Directory already exists: %r" % os_path)
def add_code_cell(self, nb):
output = current.new_output("display_data", output_javascript="alert('hi');")
cell = current.new_code_cell("print('hi')", outputs=[output])
if not nb.worksheets:
nb.worksheets.append(current.new_worksheet())
nb.worksheets[0].cells.append(cell)
def new_notebook(self):
nbm = self.notebook_manager
model = nbm.create_notebook()
cm = self.contents_manager
model = cm.create_file()
name = model['name']
path = model['path']
full_model = nbm.get_notebook(name, path)
full_model = cm.get_model(name, path)
nb = full_model['content']
self.add_code_cell(nb)
nbm.save_notebook(full_model, name, path)
cm.save(full_model, name, path)
return nb, name, path
def test_create_notebook(self):
nm = self.notebook_manager
def test_create_file(self):
cm = self.contents_manager
# Test in root directory
model = nm.create_notebook()
model = cm.create_file()
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -124,23 +124,23 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_file(None, sub_dir)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'Untitled0.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
def test_get_notebook(self):
nm = self.notebook_manager
def test_get(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
# Check that we 'get' on the notebook we just created
model2 = nm.get_notebook(name, path)
model2 = cm.get_model(name, path)
assert isinstance(model2, dict)
self.assertIn('name', model2)
self.assertIn('path', model2)
@ -149,66 +149,66 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
model2 = nm.get_notebook(name, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_file(None, sub_dir)
model2 = cm.get_model(name, sub_dir)
assert isinstance(model2, dict)
self.assertIn('name', model2)
self.assertIn('path', model2)
self.assertIn('content', model2)
self.assertEqual(model2['name'], 'Untitled0.ipynb')
self.assertEqual(model2['path'], sub_dir.strip('/'))
def test_update_notebook(self):
nm = self.notebook_manager
def test_update(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
# Change the name in the model for rename
model['name'] = 'test.ipynb'
model = nm.update_notebook(model, name, path)
model = cm.update(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'test.ipynb')
# Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path)
self.assertRaises(HTTPError, cm.get_model, name, path)
# Test in sub-directory
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_file(None, sub_dir)
name = model['name']
path = model['path']
# Change the name in the model for rename
model['name'] = 'test_in_sub.ipynb'
model = nm.update_notebook(model, name, path)
model = cm.update(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'test_in_sub.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
# Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path)
self.assertRaises(HTTPError, cm.get_model, name, path)
def test_save_notebook(self):
nm = self.notebook_manager
def test_save(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
# Get the model with 'content'
full_model = nm.get_notebook(name, path)
full_model = cm.get_model(name, path)
# Save the notebook
model = nm.save_notebook(full_model, name, path)
model = cm.save(full_model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -218,103 +218,84 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_file(None, sub_dir)
name = model['name']
path = model['path']
model = nm.get_notebook(name, path)
model = cm.get_model(name, path)
# Change the name in the model for rename
model = nm.save_notebook(model, name, path)
model = cm.save(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'Untitled0.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
def test_save_notebook_with_script(self):
nm = self.notebook_manager
# Create a notebook
model = nm.create_notebook()
nm.save_script = True
model = nm.create_notebook()
name = model['name']
path = model['path']
# Get the model with 'content'
full_model = nm.get_notebook(name, path)
# Save the notebook
model = nm.save_notebook(full_model, name, path)
# Check that the script was created
py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
assert os.path.exists(py_path), py_path
def test_delete_notebook(self):
nm = self.notebook_manager
def test_delete(self):
cm = self.contents_manager
# Create a notebook
nb, name, path = self.new_notebook()
# Delete the notebook
nm.delete_notebook(name, path)
cm.delete(name, path)
# Check that a 'get' on the deleted notebook raises and error
self.assertRaises(HTTPError, nm.get_notebook, name, path)
def test_copy_notebook(self):
nm = self.notebook_manager
self.assertRaises(HTTPError, cm.get_model, name, path)
def test_copy(self):
cm = self.contents_manager
path = u'å b'
name = u'nb √.ipynb'
os.mkdir(os.path.join(nm.notebook_dir, path))
orig = nm.create_notebook({'name' : name}, path=path)
os.mkdir(os.path.join(cm.root_dir, path))
orig = cm.create_file({'name' : name}, path=path)
# copy with unspecified name
copy = nm.copy_notebook(name, path=path)
copy = cm.copy(name, path=path)
self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
# copy with specified name
copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
copy2 = cm.copy(name, u'copy 2.ipynb', path=path)
self.assertEqual(copy2['name'], u'copy 2.ipynb')
def test_trust_notebook(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
untrusted = nbm.get_notebook(name, path)['content']
assert not nbm.notary.check_cells(untrusted)
untrusted = cm.get_model(name, path)['content']
assert not cm.notary.check_cells(untrusted)
# print(untrusted)
nbm.trust_notebook(name, path)
trusted = nbm.get_notebook(name, path)['content']
cm.trust_notebook(name, path)
trusted = cm.get_model(name, path)['content']
# print(trusted)
assert nbm.notary.check_cells(trusted)
assert cm.notary.check_cells(trusted)
def test_mark_trusted_cells(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
cm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert not cell.trusted
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
cm.trust_notebook(name, path)
nb = cm.get_model(name, path)['content']
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert cell.trusted
def test_check_and_sign(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert not nbm.notary.check_signature(nb)
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert nbm.notary.check_signature(nb)
cm.mark_trusted_cells(nb, name, path)
cm.check_and_sign(nb, name, path)
assert not cm.notary.check_signature(nb)
cm.trust_notebook(name, path)
nb = cm.get_model(name, path)['content']
cm.mark_trusted_cells(nb, name, path)
cm.check_and_sign(nb, name, path)
assert cm.notary.check_signature(nb)

@ -27,8 +27,16 @@ class MainKernelHandler(IPythonHandler):
@web.authenticated
@json_errors
def post(self):
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, "No JSON data provided")
try:
name = model['name']
except KeyError:
raise web.HTTPError(400, "Missing field in JSON data: name")
km = self.kernel_manager
kernel_id = km.start_kernel()
kernel_id = km.start_kernel(kernel_name=name)
model = km.kernel_model(kernel_id)
location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
self.set_header('Location', url_escape(location))
@ -76,6 +84,9 @@ class KernelActionHandler(IPythonHandler):
class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
def create_stream(self):
km = self.kernel_manager
meth = getattr(km, 'connect_%s' % self.channel)
@ -137,6 +148,12 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
self.zmq_stream.on_recv(self._on_zmq_reply)
def on_message(self, msg):
if self.zmq_stream is None:
return
elif self.zmq_stream.closed():
self.log.info("%s closed, closing websocket.", self)
self.close()
return
msg = json.loads(msg)
self.session.send(self.zmq_stream, msg)

@ -72,8 +72,8 @@ class MappingKernelManager(MultiKernelManager):
os_path = os.path.dirname(os_path)
return os_path
def start_kernel(self, kernel_id=None, path=None, **kwargs):
"""Start a kernel for a session an return its kernel_id.
def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
"""Start a kernel for a session and return its kernel_id.
Parameters
----------
@ -84,12 +84,16 @@ class MappingKernelManager(MultiKernelManager):
path : API path
The API path (unicode, '/' delimited) for the cwd.
Will be transformed to an OS path relative to root_dir.
kernel_name : str
The name identifying which kernel spec to launch. This is ignored if
an existing kernel is returned, but it may be checked in the future.
"""
if kernel_id is None:
kwargs['extra_arguments'] = self.kernel_argv
if path is not None:
kwargs['cwd'] = self.cwd_for_path(path)
kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
kernel_id = super(MappingKernelManager, self).start_kernel(
kernel_name=kernel_name, **kwargs)
self.log.info("Kernel started: %s" % kernel_id)
self.log.debug("Kernel args: %r" % kwargs)
# register callback for failed auto-restart
@ -111,7 +115,8 @@ class MappingKernelManager(MultiKernelManager):
"""Return a dictionary of kernel information described in the
JSON standard model."""
self._check_kernel_id(kernel_id)
model = {"id":kernel_id}
model = {"id":kernel_id,
"name": self._kernels[kernel_id].kernel_name}
return model
def list_kernels(self):

@ -1,6 +1,6 @@
"""Test the kernels service API."""
import json
import requests
from IPython.html.utils import url_path_join
@ -30,8 +30,9 @@ class KernelAPI(object):
def get(self, id):
return self._req('GET', id)
def start(self):
return self._req('POST', '')
def start(self, name='python'):
body = json.dumps({'name': name})
return self._req('POST', '', body)
def shutdown(self, id):
return self._req('DELETE', id)
@ -64,11 +65,14 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.status_code, 201)
self.assertIsInstance(kern1, dict)
self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
# GET request
r = self.kern_api.list()
self.assertEqual(r.status_code, 200)
assert isinstance(r.json(), list)
self.assertEqual(r.json()[0]['id'], kern1['id'])
self.assertEqual(r.json()[0]['name'], kern1['name'])
# create another kernel and check that they both are added to the
# list of kernels from a GET request
@ -89,6 +93,7 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
rekern = r.json()
self.assertEqual(rekern['id'], kern2['id'])
self.assertEqual(rekern['name'], kern2['name'])
def test_kernel_handler(self):
# GET kernel with given id

@ -7,6 +7,8 @@ from tornado import web
from ...base.handlers import IPythonHandler, json_errors
from IPython.kernel.kernelspec import _pythonfirst
class MainKernelSpecHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET',)
@ -16,7 +18,7 @@ class MainKernelSpecHandler(IPythonHandler):
def get(self):
ksm = self.kernel_spec_manager
results = []
for kernel_name in ksm.find_kernel_specs():
for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
d = ksm.get_kernel_spec(kernel_name).to_dict()
d['name'] = kernel_name
results.append(d)

@ -1,470 +0,0 @@
"""A notebook manager that uses the local file system for storage."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import io
import os
import glob
import shutil
from tornado import web
from .nbmanager import NotebookManager
from IPython.nbformat import current
from IPython.utils.path import ensure_dir_exists
from IPython.utils.traitlets import Unicode, Bool, TraitError
from IPython.utils.py3compat import getcwd
from IPython.utils import tz
from IPython.html.utils import is_hidden, to_os_path
def sort_key(item):
"""Case-insensitive sorting."""
return item['name'].lower()
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
class FileNotebookManager(NotebookManager):
save_script = Bool(False, config=True,
help="""Automatically create a Python script when saving the notebook.
For easier use of import, %run and %load across notebooks, a
<notebook-name>.py script will be created next to any
<notebook-name>.ipynb on each save. This can also be set with the
short `--script` flag.
"""
)
notebook_dir = Unicode(getcwd(), config=True)
def _notebook_dir_changed(self, name, old, new):
"""Do a bit of validation of the notebook dir."""
if not os.path.isabs(new):
# If we receive a non-absolute path, make it absolute.
self.notebook_dir = os.path.abspath(new)
return
if not os.path.exists(new) or not os.path.isdir(new):
raise TraitError("notebook dir %r is not a directory" % new)
checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
help="""The directory name in which to keep notebook checkpoints
This is a path relative to the notebook's own directory.
By default, it is .ipynb_checkpoints
"""
)
def _copy(self, src, dest):
"""copy src to dest
like shutil.copy2, but log errors in copystat
"""
shutil.copyfile(src, dest)
try:
shutil.copystat(src, dest)
except OSError as e:
self.log.debug("copystat on %s failed", dest, exc_info=True)
def get_notebook_names(self, path=''):
"""List all notebook names in the notebook dir and path."""
path = path.strip('/')
if not os.path.isdir(self._get_os_path(path=path)):
raise web.HTTPError(404, 'Directory not found: ' + path)
names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
names = [os.path.basename(name)
for name in names]
return names
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
Returns
-------
exists : bool
Whether the path is indeed a directory.
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return os.path.isdir(os_path)
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
Returns
-------
exists : bool
Whether the path is hidden.
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return is_hidden(os_path, self.notebook_dir)
def _get_os_path(self, name=None, path=''):
"""Given a notebook name and a URL path, return its file system
path.
Parameters
----------
name : string
The name of a notebook file with the .ipynb extension
path : string
The relative URL path (with '/' as separator) to the named
notebook.
Returns
-------
path : string
A file system path that combines notebook_dir (location where
server started), the relative path, and the filename with the
current operating system's url.
"""
if name is not None:
path = path + '/' + name
return to_os_path(path, self.notebook_dir)
def notebook_exists(self, name, path=''):
"""Returns a True if the notebook exists. Else, returns False.
Parameters
----------
name : string
The name of the notebook you are checking.
path : string
The relative path to the notebook (with '/' as separator)
Returns
-------
bool
"""
path = path.strip('/')
nbpath = self._get_os_path(name, path=path)
return os.path.isfile(nbpath)
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service.
def list_dirs(self, path):
"""List the directories for a given API style path."""
path = path.strip('/')
os_path = self._get_os_path('', path)
if not os.path.isdir(os_path):
raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
elif is_hidden(os_path, self.notebook_dir):
self.log.info("Refusing to serve hidden directory, via 404 Error")
raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
dir_names = os.listdir(os_path)
dirs = []
for name in dir_names:
os_path = self._get_os_path(name, path)
if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
and self.should_list(name):
try:
model = self.get_dir_model(name, path)
except IOError:
pass
dirs.append(model)
dirs = sorted(dirs, key=sort_key)
return dirs
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service.
def get_dir_model(self, name, path=''):
"""Get the directory model given a directory name and its API style path"""
path = path.strip('/')
os_path = self._get_os_path(name, path)
if not os.path.isdir(os_path):
raise IOError('directory does not exist: %r' % os_path)
info = os.stat(os_path)
last_modified = tz.utcfromtimestamp(info.st_mtime)
created = tz.utcfromtimestamp(info.st_ctime)
# Create the notebook model.
model ={}
model['name'] = name
model['path'] = path
model['last_modified'] = last_modified
model['created'] = created
model['type'] = 'directory'
return model
def list_notebooks(self, path):
"""Returns a list of dictionaries that are the standard model
for all notebooks in the relative 'path'.
Parameters
----------
path : str
the URL path that describes the relative path for the
listed notebooks
Returns
-------
notebooks : list of dicts
a list of the notebook models without 'content'
"""
path = path.strip('/')
notebook_names = self.get_notebook_names(path)
notebooks = [self.get_notebook(name, path, content=False)
for name in notebook_names if self.should_list(name)]
notebooks = sorted(notebooks, key=sort_key)
return notebooks
def get_notebook(self, name, path='', content=True):
""" Takes a path and name for a notebook and returns its model
Parameters
----------
name : str
the name of the notebook
path : str
the URL path that describes the relative path for
the notebook
Returns
-------
model : dict
the notebook model. If contents=True, returns the 'contents'
dict in the model as well.
"""
path = path.strip('/')
if not self.notebook_exists(name=name, path=path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
os_path = self._get_os_path(name, path)
info = os.stat(os_path)
last_modified = tz.utcfromtimestamp(info.st_mtime)
created = tz.utcfromtimestamp(info.st_ctime)
# Create the notebook model.
model ={}
model['name'] = name
model['path'] = path
model['last_modified'] = last_modified
model['created'] = created
model['type'] = 'notebook'
if content:
with io.open(os_path, 'r', encoding='utf-8') as f:
try:
nb = current.read(f, u'json')
except Exception as e:
raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
self.mark_trusted_cells(nb, name, path)
model['content'] = nb
return model
def save_notebook(self, model, name='', path=''):
"""Save the notebook model and return the model with no content."""
path = path.strip('/')
if 'content' not in model:
raise web.HTTPError(400, u'No notebook JSON data provided')
# One checkpoint should always exist
if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
self.create_checkpoint(name, path)
new_path = model.get('path', path).strip('/')
new_name = model.get('name', name)
if path != new_path or name != new_name:
self.rename_notebook(name, path, new_name, new_path)
# Save the notebook file
os_path = self._get_os_path(new_name, new_path)
nb = current.to_notebook_json(model['content'])
self.check_and_sign(nb, new_name, new_path)
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''
try:
self.log.debug("Autosaving notebook %s", os_path)
with io.open(os_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'json')
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
# Save .py script as well
if self.save_script:
py_path = os.path.splitext(os_path)[0] + '.py'
self.log.debug("Writing script %s", py_path)
try:
with io.open(py_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'py')
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
model = self.get_notebook(new_name, new_path, content=False)
return model
def update_notebook(self, model, name, path=''):
"""Update the notebook's path and/or name"""
path = path.strip('/')
new_name = model.get('name', name)
new_path = model.get('path', path).strip('/')
if path != new_path or name != new_name:
self.rename_notebook(name, path, new_name, new_path)
model = self.get_notebook(new_name, new_path, content=False)
return model
def delete_notebook(self, name, path=''):
"""Delete notebook by name and path."""
path = path.strip('/')
os_path = self._get_os_path(name, path)
if not os.path.isfile(os_path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
# clear checkpoints
for checkpoint in self.list_checkpoints(name, path):
checkpoint_id = checkpoint['id']
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if os.path.isfile(cp_path):
self.log.debug("Unlinking checkpoint %s", cp_path)
os.unlink(cp_path)
self.log.debug("Unlinking notebook %s", os_path)
os.unlink(os_path)
def rename_notebook(self, old_name, old_path, new_name, new_path):
"""Rename a notebook."""
old_path = old_path.strip('/')
new_path = new_path.strip('/')
if new_name == old_name and new_path == old_path:
return
new_os_path = self._get_os_path(new_name, new_path)
old_os_path = self._get_os_path(old_name, old_path)
# Should we proceed with the move?
if os.path.isfile(new_os_path):
raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
if self.save_script:
old_py_path = os.path.splitext(old_os_path)[0] + '.py'
new_py_path = os.path.splitext(new_os_path)[0] + '.py'
if os.path.isfile(new_py_path):
raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
# Move the notebook file
try:
shutil.move(old_os_path, new_os_path)
except Exception as e:
raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
# Move the checkpoints
old_checkpoints = self.list_checkpoints(old_name, old_path)
for cp in old_checkpoints:
checkpoint_id = cp['id']
old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
if os.path.isfile(old_cp_path):
self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
shutil.move(old_cp_path, new_cp_path)
# Move the .py script
if self.save_script:
shutil.move(old_py_path, new_py_path)
# Checkpoint-related utilities
def get_checkpoint_path(self, checkpoint_id, name, path=''):
"""find the path to a checkpoint"""
path = path.strip('/')
basename, _ = os.path.splitext(name)
filename = u"{name}-{checkpoint_id}{ext}".format(
name=basename,
checkpoint_id=checkpoint_id,
ext=self.filename_ext,
)
os_path = self._get_os_path(path=path)
cp_dir = os.path.join(os_path, self.checkpoint_dir)
ensure_dir_exists(cp_dir)
cp_path = os.path.join(cp_dir, filename)
return cp_path
def get_checkpoint_model(self, checkpoint_id, name, path=''):
"""construct the info dict for a given checkpoint"""
path = path.strip('/')
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
stats = os.stat(cp_path)
last_modified = tz.utcfromtimestamp(stats.st_mtime)
info = dict(
id = checkpoint_id,
last_modified = last_modified,
)
return info
# public checkpoint API
def create_checkpoint(self, name, path=''):
"""Create a checkpoint from the current state of a notebook"""
path = path.strip('/')
nb_path = self._get_os_path(name, path)
# only the one checkpoint ID:
checkpoint_id = u"checkpoint"
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
self.log.debug("creating checkpoint for notebook %s", name)
self._copy(nb_path, cp_path)
# return the checkpoint info
return self.get_checkpoint_model(checkpoint_id, name, path)
def list_checkpoints(self, name, path=''):
"""list the checkpoints for a given notebook
This notebook manager currently only supports one checkpoint per notebook.
"""
path = path.strip('/')
checkpoint_id = "checkpoint"
os_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.exists(os_path):
return []
else:
return [self.get_checkpoint_model(checkpoint_id, name, path)]
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""restore a notebook to a checkpointed state"""
path = path.strip('/')
self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
nb_path = self._get_os_path(name, path)
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.isfile(cp_path):
self.log.debug("checkpoint file does not exist: %s", cp_path)
raise web.HTTPError(404,
u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
)
# ensure notebook is readable (never restore from an unreadable notebook)
with io.open(cp_path, 'r', encoding='utf-8') as f:
current.read(f, u'json')
self._copy(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path)
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a notebook's checkpoint"""
path = path.strip('/')
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.isfile(cp_path):
raise web.HTTPError(404,
u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
)
self.log.debug("unlinking %s", cp_path)
os.unlink(cp_path)
def info_string(self):
return "Serving notebooks from local directory: %s" % self.notebook_dir
def get_kernel_path(self, name, path='', model=None):
""" Return the path to start kernel in """
return os.path.join(self.notebook_dir, path)

@ -1,288 +0,0 @@
"""Tornado handlers for the notebooks web service.
Authors:
* Brian Granger
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
import json
from tornado import web
from IPython.html.utils import url_path_join, url_escape
from IPython.utils.jsonutil import date_default
from IPython.html.base.handlers import (IPythonHandler, json_errors,
notebook_path_regex, path_regex,
notebook_name_regex)
#-----------------------------------------------------------------------------
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookHandler(IPythonHandler):
SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
def notebook_location(self, name, path=''):
"""Return the full URL location of a notebook based.
Parameters
----------
name : unicode
The base name of the notebook, such as "foo.ipynb".
path : unicode
The URL path of the notebook.
"""
return url_escape(url_path_join(
self.base_url, 'api', 'notebooks', path, name
))
def _finish_model(self, model, location=True):
"""Finish a JSON request with a model, setting relevant headers, etc."""
if location:
location = self.notebook_location(model['name'], model['path'])
self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified'])
self.finish(json.dumps(model, default=date_default))
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""Return a Notebook or list of notebooks.
* GET with path and no notebook name lists notebooks in a directory
* GET with path and notebook name returns notebook JSON
"""
nbm = self.notebook_manager
# Check to see if a notebook name was given
if name is None:
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service. This should only handle notebooks
# and not directories.
dirs = nbm.list_dirs(path)
notebooks = []
index = []
for nb in nbm.list_notebooks(path):
if nb['name'].lower() == 'index.ipynb':
index.append(nb)
else:
notebooks.append(nb)
notebooks = index + dirs + notebooks
self.finish(json.dumps(notebooks, default=date_default))
return
# get and return notebook representation
model = nbm.get_notebook(name, path)
self._finish_model(model, location=False)
@web.authenticated
@json_errors
def patch(self, path='', name=None):
"""PATCH renames a notebook without re-uploading content."""
nbm = self.notebook_manager
if name is None:
raise web.HTTPError(400, u'Notebook name missing')
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, u'JSON body missing')
model = nbm.update_notebook(model, name, path)
self._finish_model(model)
def _copy_notebook(self, copy_from, path, copy_to=None):
"""Copy a notebook in path, optionally specifying the new name.
Only support copying within the same directory.
"""
self.log.info(u"Copying notebook from %s/%s to %s/%s",
path, copy_from,
path, copy_to or '',
)
model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
self.set_status(201)
self._finish_model(model)
def _upload_notebook(self, model, path, name=None):
"""Upload a notebook
If name specified, create it in path/name.
"""
self.log.info(u"Uploading notebook to %s/%s", path, name or '')
if name:
model['name'] = name
model = self.notebook_manager.create_notebook(model, path)
self.set_status(201)
self._finish_model(model)
def _create_empty_notebook(self, path, name=None):
"""Create an empty notebook in path
If name specified, create it in path/name.
"""
self.log.info(u"Creating new notebook in %s/%s", path, name or '')
model = {}
if name:
model['name'] = name
model = self.notebook_manager.create_notebook(model, path=path)
self.set_status(201)
self._finish_model(model)
def _save_notebook(self, model, path, name):
"""Save an existing notebook."""
self.log.info(u"Saving notebook at %s/%s", path, name)
model = self.notebook_manager.save_notebook(model, name, path)
if model['path'] != path.strip('/') or model['name'] != name:
# a rename happened, set Location header
location = True
else:
location = False
self._finish_model(model, location)
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""Create a new notebook in the specified path.
POST creates new notebooks. The server always decides on the notebook name.
POST /api/notebooks/path
New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty.
POST /api/notebooks/path?copy=OtherNotebook.ipynb
New copy of OtherNotebook in path
"""
if name is not None:
raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
model = self.get_json_body()
if model is not None:
copy_from = model.get('copy_from')
if copy_from:
if model.get('content'):
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._copy_notebook(copy_from, path)
else:
self._upload_notebook(model, path)
else:
self._create_empty_notebook(path)
@web.authenticated
@json_errors
def put(self, path='', name=None):
"""Saves the notebook in the location specified by name and path.
PUT is very similar to POST, but the requester specifies the name,
whereas with POST, the server picks the name.
PUT /api/notebooks/path/Name.ipynb
Save notebook at ``path/Name.ipynb``. Notebook structure is specified
in `content` key of JSON request body. If content is not specified,
create a new empty notebook.
PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
Copy OtherNotebook to Name
"""
if name is None:
raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
model = self.get_json_body()
if model:
copy_from = model.get('copy_from')
if copy_from:
if model.get('content'):
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._copy_notebook(copy_from, path, name)
elif self.notebook_manager.notebook_exists(name, path):
self._save_notebook(model, path, name)
else:
self._upload_notebook(model, path, name)
else:
self._create_empty_notebook(path, name)
@web.authenticated
@json_errors
def delete(self, path='', name=None):
"""delete the notebook in the given notebook path"""
nbm = self.notebook_manager
nbm.delete_notebook(name, path)
self.set_status(204)
self.finish()
class NotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""get lists checkpoints for a notebook"""
nbm = self.notebook_manager
checkpoints = nbm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default)
self.finish(data)
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""post creates a new checkpoint"""
nbm = self.notebook_manager
checkpoint = nbm.create_checkpoint(name, path)
data = json.dumps(checkpoint, default=date_default)
location = url_path_join(self.base_url, 'api/notebooks',
path, name, 'checkpoints', checkpoint['id'])
self.set_header('Location', url_escape(location))
self.set_status(201)
self.finish(data)
class ModifyNotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated
@json_errors
def post(self, path, name, checkpoint_id):
"""post restores a notebook from a checkpoint"""
nbm = self.notebook_manager
nbm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@web.authenticated
@json_errors
def delete(self, path, name, checkpoint_id):
"""delete clears a checkpoint for a given notebook"""
nbm = self.notebook_manager
nbm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
default_handlers = [
(r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
(r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
(r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
(r"/api/notebooks%s" % path_regex, NotebookHandler),
]

@ -1,287 +0,0 @@
"""A base class notebook manager.
Authors:
* Brian Granger
* Zach Sailer
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
from fnmatch import fnmatch
import itertools
import os
from IPython.config.configurable import LoggingConfigurable
from IPython.nbformat import current, sign
from IPython.utils.traitlets import Instance, Unicode, List
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
class NotebookManager(LoggingConfigurable):
filename_ext = Unicode(u'.ipynb')
notary = Instance(sign.NotebookNotary)
def _notary_default(self):
return sign.NotebookNotary(parent=self)
hide_globs = List(Unicode, [u'__pycache__'], config=True, help="""
Glob patterns to hide in file and directory listings.
""")
# NotebookManager API part 1: methods that must be
# implemented in subclasses.
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
Override this method in subclasses.
Parameters
----------
path : string
The path to check
Returns
-------
exists : bool
Whether the path does indeed exist.
"""
raise NotImplementedError
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
Returns
-------
exists : bool
Whether the path is hidden.
"""
raise NotImplementedError
def notebook_exists(self, name, path=''):
"""Returns a True if the notebook exists. Else, returns False.
Parameters
----------
name : string
The name of the notebook you are checking.
path : string
The relative path to the notebook (with '/' as separator)
Returns
-------
bool
"""
raise NotImplementedError('must be implemented in a subclass')
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service.
def list_dirs(self, path):
"""List the directory models for a given API style path."""
raise NotImplementedError('must be implemented in a subclass')
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service.
def get_dir_model(self, name, path=''):
"""Get the directory model given a directory name and its API style path.
The keys in the model should be:
* name
* path
* last_modified
* created
* type='directory'
"""
raise NotImplementedError('must be implemented in a subclass')
def list_notebooks(self, path=''):
"""Return a list of notebook dicts without content.
This returns a list of dicts, each of the form::
dict(notebook_id=notebook,name=name)
This list of dicts should be sorted by name::
data = sorted(data, key=lambda item: item['name'])
"""
raise NotImplementedError('must be implemented in a subclass')
def get_notebook(self, name, path='', content=True):
"""Get the notebook model with or without content."""
raise NotImplementedError('must be implemented in a subclass')
def save_notebook(self, model, name, path=''):
"""Save the notebook and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
def update_notebook(self, model, name, path=''):
"""Update the notebook and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
def delete_notebook(self, name, path=''):
"""Delete notebook by name and path."""
raise NotImplementedError('must be implemented in a subclass')
def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a notebook
Returns a checkpoint_id for the new checkpoint.
"""
raise NotImplementedError("must be implemented in a subclass")
def list_checkpoints(self, name, path=''):
"""Return a list of checkpoints for a given notebook"""
return []
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a notebook from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass")
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a checkpoint for a notebook"""
raise NotImplementedError("must be implemented in a subclass")
def info_string(self):
return "Serving notebooks"
# NotebookManager API part 2: methods that have useable default
# implementations, but can be overridden in subclasses.
def get_kernel_path(self, name, path='', model=None):
""" Return the path to start kernel in """
return path
def increment_filename(self, basename, path=''):
"""Increment a notebook filename without the .ipynb to make it unique.
Parameters
----------
basename : unicode
The name of a notebook without the ``.ipynb`` file extension.
path : unicode
The URL path of the notebooks directory
Returns
-------
name : unicode
A notebook name (with the .ipynb extension) that starts
with basename and does not refer to any existing notebook.
"""
path = path.strip('/')
for i in itertools.count():
name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
ext=self.filename_ext)
if not self.notebook_exists(name, path):
break
return name
def create_notebook(self, model=None, path=''):
"""Create a new notebook and return its model with no content."""
path = path.strip('/')
if model is None:
model = {}
if 'content' not in model:
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
if 'name' not in model:
model['name'] = self.increment_filename('Untitled', path)
model['path'] = path
model = self.save_notebook(model, model['name'], model['path'])
return model
def copy_notebook(self, from_name, to_name=None, path=''):
"""Copy an existing notebook and return its new model.
If to_name not specified, increment `from_name-Copy#.ipynb`.
"""
path = path.strip('/')
model = self.get_notebook(from_name, path)
if not to_name:
base = os.path.splitext(from_name)[0] + '-Copy'
to_name = self.increment_filename(base, path)
model['name'] = to_name
model = self.save_notebook(model, to_name, path)
return model
def log_info(self):
self.log.info(self.info_string())
def trust_notebook(self, name, path=''):
"""Explicitly trust a notebook
Parameters
----------
name : string
The filename of the notebook
path : string
The notebook's directory
"""
model = self.get_notebook(name, path)
nb = model['content']
self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True)
self.save_notebook(model, name, path)
def check_and_sign(self, nb, name, path=''):
"""Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks.
Parameters
----------
nb : dict
The notebook structure
name : string
The filename of the notebook
path : string
The notebook's directory
"""
if self.notary.check_cells(nb):
self.notary.sign(nb)
else:
self.log.warn("Saving untrusted notebook %s/%s", path, name)
def mark_trusted_cells(self, nb, name, path=''):
"""Mark cells as trusted if the notebook signature matches.
Called as a part of loading notebooks.
Parameters
----------
nb : dict
The notebook structure
name : string
The filename of the notebook
path : string
The notebook's directory
"""
trusted = self.notary.check_signature(nb)
if not trusted:
self.log.warn("Notebook %s/%s is not trusted", path, name)
self.notary.mark_cells(nb, trusted)
def should_list(self, name):
"""Should this file/directory name be displayed in a listing?"""
return not any(fnmatch(name, glob) for glob in self.hide_globs)

@ -1,20 +1,7 @@
"""Tornado handlers for the sessions web service.
"""Tornado handlers for the sessions web service."""
Authors:
* Zach Sailer
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2013 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
@ -24,10 +11,6 @@ from ...base.handlers import IPythonHandler, json_errors
from IPython.utils.jsonutil import date_default
from IPython.html.utils import url_path_join, url_escape
#-----------------------------------------------------------------------------
# Session web service handlers
#-----------------------------------------------------------------------------
class SessionRootHandler(IPythonHandler):
@ -45,27 +28,30 @@ class SessionRootHandler(IPythonHandler):
# Creates a new session
#(unless a session already exists for the named nb)
sm = self.session_manager
nbm = self.notebook_manager
cm = self.contents_manager
km = self.kernel_manager
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, "No JSON data provided")
try:
name = model['notebook']['name']
except KeyError:
raise web.HTTPError(400, "Missing field in JSON data: name")
raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
try:
path = model['notebook']['path']
except KeyError:
raise web.HTTPError(400, "Missing field in JSON data: path")
raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
try:
kernel_name = model['kernel']['name']
except KeyError:
raise web.HTTPError(400, "Missing field in JSON data: kernel.name")
# Check to see if session exists
if sm.session_exists(name=name, path=path):
model = sm.get_session(name=name, path=path)
else:
# allow nbm to specify kernels cwd
kernel_path = nbm.get_kernel_path(name=name, path=path)
kernel_id = km.start_kernel(path=kernel_path)
model = sm.create_session(name=name, path=path, kernel_id=kernel_id)
model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
self.set_header('Location', url_escape(location))
self.set_status(201)
@ -108,10 +94,7 @@ class SessionHandler(IPythonHandler):
def delete(self, session_id):
# Deletes the session with given session_id
sm = self.session_manager
km = self.kernel_manager
session = sm.get_session(session_id=session_id)
sm.delete_session(session_id)
km.shutdown_kernel(session['kernel']['id'])
self.set_status(204)
self.finish()

@ -23,12 +23,16 @@ from tornado import web
from IPython.config.configurable import LoggingConfigurable
from IPython.utils.py3compat import unicode_type
from IPython.utils.traitlets import Instance
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
class SessionManager(LoggingConfigurable):
kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
# Session database initialized below
_cursor = None
@ -69,10 +73,15 @@ class SessionManager(LoggingConfigurable):
"Create a uuid for a new session"
return unicode_type(uuid.uuid4())
def create_session(self, name=None, path=None, kernel_id=None):
def create_session(self, name=None, path=None, kernel_name='python'):
"""Creates a session and returns its model"""
session_id = self.new_session_id()
return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id)
# allow nbm to specify kernels cwd
kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
kernel_name=kernel_name)
return self.save_session(session_id, name=name, path=path,
kernel_id=kernel_id)
def save_session(self, session_id, name=None, path=None, kernel_id=None):
"""Saves the items for the session with the given session_id
@ -170,8 +179,7 @@ class SessionManager(LoggingConfigurable):
query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
self.cursor.execute(query, list(kwargs.values()) + [session_id])
@staticmethod
def row_factory(cursor, row):
def row_factory(self, cursor, row):
"""Takes sqlite database session row and turns it into a dictionary"""
row = sqlite3.Row(cursor, row)
model = {
@ -180,9 +188,7 @@ class SessionManager(LoggingConfigurable):
'name': row['name'],
'path': row['path']
},
'kernel': {
'id': row['kernel_id'],
}
'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
}
return model
@ -195,5 +201,6 @@ class SessionManager(LoggingConfigurable):
def delete_session(self, session_id):
"""Deletes the row in the session database with given session_id"""
# Check that session exists before deleting
self.get_session(session_id=session_id)
session = self.get_session(session_id=session_id)
self.kernel_manager.shutdown_kernel(session['kernel']['id'])
self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))

@ -5,79 +5,101 @@ from unittest import TestCase
from tornado import web
from ..sessionmanager import SessionManager
from IPython.html.services.kernels.kernelmanager import MappingKernelManager
class DummyKernel(object):
def __init__(self, kernel_name='python'):
self.kernel_name = kernel_name
class DummyMKM(MappingKernelManager):
"""MappingKernelManager interface that doesn't start kernels, for testing"""
def __init__(self, *args, **kwargs):
super(DummyMKM, self).__init__(*args, **kwargs)
self.id_letters = iter(u'ABCDEFGHIJK')
def _new_id(self):
return next(self.id_letters)
def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
kernel_id = kernel_id or self._new_id()
self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
return kernel_id
def shutdown_kernel(self, kernel_id, now=False):
del self._kernels[kernel_id]
class TestSessionManager(TestCase):
def test_get_session(self):
sm = SessionManager()
session_id = sm.new_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
session_id = sm.create_session(name='test.ipynb', path='/path/to/',
kernel_name='bar')['id']
model = sm.get_session(session_id=session_id)
expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
expected = {'id':session_id,
'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'},
'kernel': {'id':u'A', 'name': 'bar'}}
self.assertEqual(model, expected)
def test_bad_get_session(self):
# Should raise error if a bad key is passed to the database.
sm = SessionManager()
session_id = sm.new_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
session_id = sm.create_session(name='test.ipynb', path='/path/to/',
kernel_name='foo')['id']
self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
def test_list_sessions(self):
sm = SessionManager()
session_id1 = sm.new_session_id()
session_id2 = sm.new_session_id()
session_id3 = sm.new_session_id()
sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
sessions = [
sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
]
sessions = sm.list_sessions()
expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
{'id':session_id2, 'notebook': {'name':u'test2.ipynb',
'path': u'/path/to/2/'}, 'kernel':{'id':u'5678'}},
{'id':session_id3, 'notebook':{'name':u'test3.ipynb',
'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
{'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
{'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
self.assertEqual(sessions, expected)
def test_update_session(self):
sm = SessionManager()
session_id = sm.new_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None)
sm.update_session(session_id, kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
session_id = sm.create_session(name='test.ipynb', path='/path/to/',
kernel_name='julia')['id']
sm.update_session(session_id, name='new_name.ipynb')
model = sm.get_session(session_id=session_id)
expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
expected = {'id':session_id,
'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
'kernel':{'id':u'A', 'name':'julia'}}
self.assertEqual(model, expected)
def test_bad_update_session(self):
# try to update a session with a bad keyword ~ raise error
sm = SessionManager()
session_id = sm.new_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
session_id = sm.create_session(name='test.ipynb', path='/path/to/',
kernel_name='ir')['id']
self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
def test_delete_session(self):
sm = SessionManager()
session_id1 = sm.new_session_id()
session_id2 = sm.new_session_id()
session_id3 = sm.new_session_id()
sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
sm.delete_session(session_id2)
sessions = sm.list_sessions()
expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
{'id':session_id3, 'notebook':{'name':u'test3.ipynb',
'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
self.assertEqual(sessions, expected)
sm = SessionManager(kernel_manager=DummyMKM())
sessions = [
sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
]
sm.delete_session(sessions[1]['id'])
new_sessions = sm.list_sessions()
expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
{'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
self.assertEqual(new_sessions, expected)
def test_bad_delete_session(self):
# try to delete a session that doesn't exist ~ raise error
sm = SessionManager()
session_id = sm.new_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
sm = SessionManager(kernel_manager=DummyMKM())
sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant

@ -37,8 +37,9 @@ class SessionAPI(object):
def get(self, id):
return self._req('GET', id)
def create(self, name, path):
body = json.dumps({'notebook': {'name':name, 'path':path}})
def create(self, name, path, kernel_name='python'):
body = json.dumps({'notebook': {'name':name, 'path':path},
'kernel': {'name': kernel_name}})
return self._req('POST', '', body)
def modify(self, id, name, path):

@ -1,21 +1,12 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// On document ready
//============================================================================
$(document).ready(function () {
IPython.page = new IPython.Page();
var ipython = ipython || {};
require(['base/js/page'], function(page) {
var page_instance = new page.Page();
$('button#login_submit').addClass("btn btn-default");
IPython.page.show();
page_instance.show();
$('input#password_input').focus();
ipython.page = page_instance;
});

@ -1,43 +1,35 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Login button
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'base/js/utils',
'jquery',
], function(IPython, utils, $){
"use strict";
var LoginWidget = function (selector, options) {
options = options || {};
this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.selector = selector;
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
this.bind_events();
}
};
LoginWidget.prototype.style = function () {
this.element.find("button").addClass("btn btn-default btn-sm");
};
LoginWidget.prototype.bind_events = function () {
var that = this;
this.element.find("button#logout").click(function () {
window.location = IPython.utils.url_join_encode(
window.location = utils.url_join_encode(
that.base_url,
"logout"
);
});
this.element.find("button#login").click(function () {
window.location = IPython.utils.url_join_encode(
window.location = utils.url_join_encode(
that.base_url,
"login"
);
@ -47,6 +39,5 @@ var IPython = (function (IPython) {
// Set module variables
IPython.LoginWidget = LoginWidget;
return IPython;
}(IPython));
return {'LoginWidget': LoginWidget};
});

@ -1,20 +1,10 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// On document ready
//============================================================================
$(document).ready(function () {
IPython.page = new IPython.Page();
$('#ipython-main-app').addClass('border-box-sizing');
IPython.page.show();
var ipython = ipython || {};
require(['base/js/page'], function(page) {
var page_instance = new page.Page();
page_instance.show();
ipython.page = page_instance;
});

@ -1,2 +1,7 @@
/*!
*
* IPython auth
*
*/
@import "login.less";
@import "logout.less";

@ -1,20 +1,14 @@
//----------------------------------------------------------------------------
// Copyright (C) 2013 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Utility for modal dialogs with bootstrap
//============================================================================
IPython.namespace('IPython.dialog');
IPython.dialog = (function (IPython) {
define([
'base/js/namespace',
'jquery',
], function(IPython, $) {
"use strict";
var modal = function (options) {
var modal = $("<div/>")
.addClass("modal")
.addClass("fade")
@ -79,26 +73,28 @@ IPython.dialog = (function (IPython) {
});
}
modal.on("hidden.bs.modal", function () {
if (IPython.notebook) {
var cell = IPython.notebook.get_selected_cell();
if (options.notebook) {
var cell = options.notebook.get_selected_cell();
if (cell) cell.select();
IPython.keyboard_manager.enable();
IPython.keyboard_manager.command_mode();
}
if (options.keyboard_manager) {
options.keyboard_manager.enable();
options.keyboard_manager.command_mode();
}
});
if (IPython.keyboard_manager) {
IPython.keyboard_manager.disable();
if (options.keyboard_manager) {
options.keyboard_manager.disable();
}
return modal.modal(options);
};
var edit_metadata = function (md, callback, name) {
name = name || "Cell";
var edit_metadata = function (options) {
options.name = options.name || "Cell";
var error_div = $('<div/>').css('color', 'red');
var message =
"Manually edit the JSON below to manipulate the metadata for this " + name + "." +
"Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
" We recommend putting custom metadata attributes in an appropriately named sub-structure," +
" so they don't conflict with those of others.";
@ -106,7 +102,7 @@ IPython.dialog = (function (IPython) {
.attr('rows', '13')
.attr('cols', '80')
.attr('name', 'metadata')
.text(JSON.stringify(md || {}, null, 2));
.text(JSON.stringify(options.md || {}, null, 2));
var dialogform = $('<div/>').attr('title', 'Edit the metadata')
.append(
@ -128,8 +124,8 @@ IPython.dialog = (function (IPython) {
autoIndent: true,
mode: 'application/json',
});
var modal = IPython.dialog.modal({
title: "Edit " + name + " Metadata",
var modal_obj = modal({
title: "Edit " + options.name + " Metadata",
body: dialogform,
buttons: {
OK: { class : "btn-primary",
@ -143,19 +139,25 @@ IPython.dialog = (function (IPython) {
error_div.text('WARNING: Could not save invalid JSON.');
return false;
}
callback(new_md);
options.callback(new_md);
}
},
Cancel: {}
}
},
notebook: options.notebook,
keyboard_manager: options.keyboard_manager,
});
modal.on('shown.bs.modal', function(){ editor.refresh(); });
modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
};
return {
var dialog = {
modal : modal,
edit_metadata : edit_metadata,
};
}(IPython));
// Backwards compatability.
IPython.dialog = dialog;
return dialog;
});

@ -1,32 +1,24 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Events
//============================================================================
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
// Give us an object to bind all events to. This object should be created
// before all other objects so it exists when others register event handlers.
// To trigger an event handler:
// $([IPython.events]).trigger('event.Namespace');
// To handle it:
// $([IPython.events]).on('event.Namespace',function () {});
// To register an event handler:
//
// require(['base/js/events'], function (events) {
// events.on("event.Namespace", function () { do_stuff(); });
// });
var IPython = (function (IPython) {
define(['base/js/namespace', 'jquery'], function(IPython, $) {
"use strict";
var utils = IPython.utils;
var Events = function () {};
var events = new Events();
// Backwards compatability.
IPython.Events = Events;
IPython.events = new Events();
return IPython;
}(IPython));
IPython.events = events;
return $([events]);
});

@ -1,19 +1,14 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Keyboard management
//============================================================================
IPython.namespace('IPython.keyboard');
IPython.keyboard = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
// Setup global keycodes and inverse keycodes.
// See http://unixpapa.com/js/key.html for a complete description. The short of
@ -51,8 +46,8 @@ IPython.keyboard = (function (IPython) {
'; :': 186, '= +': 187, '- _': 189
};
var browser = IPython.utils.browser[0];
var platform = IPython.utils.platform;
var browser = utils.browser[0];
var platform = utils.platform;
if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
$.extend(_keycodes, _mozilla_keycodes);
@ -130,18 +125,19 @@ IPython.keyboard = (function (IPython) {
// Shortcut manager class
var ShortcutManager = function (delay) {
var ShortcutManager = function (delay, events) {
this._shortcuts = {};
this._counts = {};
this._timers = {};
this.delay = delay || 800; // delay in milliseconds
this.events = events;
};
ShortcutManager.prototype.help = function () {
var help = [];
for (var shortcut in this._shortcuts) {
var help_string = this._shortcuts[shortcut]['help'];
var help_index = this._shortcuts[shortcut]['help_index'];
var help_string = this._shortcuts[shortcut].help;
var help_index = this._shortcuts[shortcut].help_index;
if (help_string) {
if (platform === 'MacOS') {
shortcut = shortcut.replace('meta', 'cmd');
@ -182,7 +178,7 @@ IPython.keyboard = (function (IPython) {
this._shortcuts[shortcut] = data;
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
$([IPython.events]).trigger('rebuild.QuickHelp');
this.events.trigger('rebuild.QuickHelp');
}
};
@ -191,7 +187,7 @@ IPython.keyboard = (function (IPython) {
this.add_shortcut(shortcut, data[shortcut], true);
}
// update the keyboard shortcuts notebook help
$([IPython.events]).trigger('rebuild.QuickHelp');
this.events.trigger('rebuild.QuickHelp');
};
ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
@ -200,7 +196,7 @@ IPython.keyboard = (function (IPython) {
delete this._shortcuts[shortcut];
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
$([IPython.events]).trigger('rebuild.QuickHelp');
this.events.trigger('rebuild.QuickHelp');
}
};
@ -211,7 +207,7 @@ IPython.keyboard = (function (IPython) {
var timer = null;
if (c[shortcut] === data.count-1) {
c[shortcut] = 0;
var timer = t[shortcut];
timer = t[shortcut];
if (timer) {clearTimeout(timer); delete t[shortcut];}
return data.handler(event);
} else {
@ -228,7 +224,7 @@ IPython.keyboard = (function (IPython) {
var shortcut = event_to_shortcut(event);
var data = this._shortcuts[shortcut];
if (data) {
var handler = data['handler'];
var handler = data.handler;
if (handler) {
if (data.count === 1) {
return handler(event);
@ -243,10 +239,10 @@ IPython.keyboard = (function (IPython) {
ShortcutManager.prototype.handles = function (event) {
var shortcut = event_to_shortcut(event);
var data = this._shortcuts[shortcut];
return !( data === undefined || data.handler === undefined )
}
return !( data === undefined || data.handler === undefined );
};
return {
var keyboard = {
keycodes : keycodes,
inv_keycodes : inv_keycodes,
ShortcutManager : ShortcutManager,
@ -256,4 +252,8 @@ IPython.keyboard = (function (IPython) {
event_to_shortcut : event_to_shortcut
};
}(IPython));
// For backwards compatability.
IPython.keyboard = keyboard;
return keyboard;
});

@ -1,34 +1,8 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
var IPython = IPython || {};
IPython.version = "3.0.0-dev";
IPython.namespace = function (ns_string) {
"use strict";
var parts = ns_string.split('.'),
parent = IPython,
i;
// String redundant leading global
if (parts[0] === "IPython") {
parts = parts.slice(1);
}
for (i=0; i<parts.length; i+=1) {
// Create property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
}
return parent;
};
define([], function(){
IPython.version = "3.0.0-dev";
return IPython;
});

@ -1,32 +1,19 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Global header/site setup.
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
], function(IPython, $){
"use strict";
var Page = function () {
this.style();
this.bind_events();
};
Page.prototype.style = function () {
$('div#header').addClass('border-box-sizing');
$('div#site').addClass('border-box-sizing');
};
Page.prototype.bind_events = function () {
};
Page.prototype.show = function () {
// The header and site divs start out hidden to prevent FLOUC.
// Main scripts should call this method after styling everything.
@ -34,23 +21,21 @@ var IPython = (function (IPython) {
this.show_site();
};
Page.prototype.show_header = function () {
// The header and site divs start out hidden to prevent FLOUC.
// Main scripts should call this method after styling everything.
// TODO: selector are hardcoded, pass as constructor argument
$('div#header').css('display','block');
};
Page.prototype.show_site = function () {
// The header and site divs start out hidden to prevent FLOUC.
// Main scripts should call this method after styling everything.
// TODO: selector are hardcoded, pass as constructor argument
$('div#site').css('display','block');
};
// Register self in the global namespace for convenience.
IPython.Page = Page;
return IPython;
}(IPython));
return {'Page': Page};
});

@ -1,20 +0,0 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// On document ready
//============================================================================
$(document).ready(function () {
"use strict";
IPython.page = new IPython.Page();
IPython.page.show();
});

@ -1,19 +1,12 @@
//----------------------------------------------------------------------------
// Copyright (C) 2014 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Utilities
//============================================================================
IPython.namespace('IPython.security');
IPython.security = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'components/google-caja/html-css-sanitizer-minified',
], function(IPython, $) {
"use strict";
var utils = IPython.utils;
var noop = function (x) { return x; };
@ -117,10 +110,12 @@ IPython.security = (function (IPython) {
return sanitized;
};
return {
var security = {
caja: caja,
sanitize_html: sanitize_html
};
}(IPython));
IPython.security = security;
return security;
});

@ -1,13 +1,10 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Utilities
//============================================================================
IPython.namespace('IPython.utils');
IPython.utils = (function (IPython) {
define([
'base/js/namespace',
'jquery',
], function(IPython, $){
"use strict";
IPython.load_extensions = function () {
@ -517,19 +514,24 @@ IPython.utils = (function (IPython) {
}
};
var ajax_error_msg = function (jqXHR) {
// Return a JSON error message if there is one,
// otherwise the basic HTTP status text.
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
return jqXHR.responseJSON.message;
} else {
return jqXHR.statusText;
}
}
var log_ajax_error = function (jqXHR, status, error) {
// log ajax failures with informative messages
var msg = "API request failed (" + jqXHR.status + "): ";
console.log(jqXHR);
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
msg += jqXHR.responseJSON.message;
} else {
msg += jqXHR.statusText;
}
msg += ajax_error_msg(jqXHR);
console.log(msg);
};
return {
var utils = {
regex_split : regex_split,
uuid : uuid,
fixConsole : fixConsole,
@ -550,8 +552,12 @@ IPython.utils = (function (IPython) {
platform: platform,
is_or_has : is_or_has,
is_focused : is_focused,
ajax_error_msg : ajax_error_msg,
log_ajax_error : log_ajax_error,
};
}(IPython));
// Backwards compatability.
IPython.utils = utils;
return utils;
});

@ -184,6 +184,30 @@ Browsers not listed, including Safari, are supported via the styling under the
justify-content: center;
}
.hbox.baseline,
.vbox.baseline,
.baseline {
/* Old browsers */
-webkit-box-pack: baseline;
-moz-box-pack: baseline;
box-pack: baseline;
/* Modern browsers */
justify-content: baseline;
}
.hbox.stretch,
.vbox.stretch,
.stretch {
/* Old browsers */
-webkit-box-pack: stretch;
-moz-box-pack: stretch;
box-pack: stretch;
/* Modern browsers */
justify-content: stretch;
}
.hbox.align-start,
.vbox.align-start,
.align-start {
@ -219,3 +243,27 @@ Browsers not listed, including Safari, are supported via the styling under the
/* Modern browsers */
align-items: center;
}
.hbox.align-baseline,
.vbox.align-baseline,
.align-baseline {
/* Old browsers */
-webkit-box-align: baseline;
-moz-box-align: baseline;
box-align: baseline;
/* Modern browsers */
align-items: baseline;
}
.hbox.align-stretch,
.vbox.align-stretch,
.align-stretch {
/* Old browsers */
-webkit-box-align: stretch;
-moz-box-align: stretch;
box-align: stretch;
/* Modern browsers */
align-items: stretch;
}

@ -24,6 +24,7 @@ div#header {
padding-left: 30px;
padding-bottom: 5px;
border-bottom: 1px solid @navbar-default-border;
.border-box-sizing();
}
#ipython_notebook {
@ -50,6 +51,7 @@ div#header {
#site {
width: 100%;
display: none;
.border-box-sizing();
}
/* Smaller buttons */
@ -69,6 +71,14 @@ span#login_widget {
float: right;
}
span#login_widget > .button,
#logout
{
.btn();
.btn-default();
.btn-sm();
}
.nav-header {
text-transform: none;
}
@ -93,3 +103,8 @@ span#login_widget {
}
}
// less mixin to be sure to add the right class to get icons with font awesome.
.icon(@ico){
.fa();
content: @ico;
}

@ -1,3 +1,8 @@
/*!
*
* IPython base
*
*/
@import "variables.less";
@import "mixins.less";
@import "flexbox.less";

@ -12,12 +12,12 @@
*
* Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
*
* Example :
* __Example 1:__
*
* Create a custom button in toolbar that execute `%qtconsole` in kernel
* and hence open a qtconsole attached to the same kernel as the current notebook
*
* $([IPython.events]).on('app_initialized.NotebookApp', function(){
* IPython.events.on('app_initialized.NotebookApp', function(){
* IPython.toolbar.add_buttons_group([
* {
* 'label' : 'run qtconsole',
@ -30,7 +30,16 @@
* ]);
* });
*
* Example :
* __Example 2:__
*
* At the completion of the dashboard loading, load an unofficial javascript extension
* that is installed in profile/static/custom/
*
* IPython.events.on('app_initialized.DashboardApp', function(){
* require(['custom/unofficial_extension.js'])
* });
*
* __Example 3:__
*
* Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
* to load custom script into the notebook.

@ -1,53 +1,61 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Cell
//============================================================================
/**
* An extendable module that provide base functionnality to create cell for notebook.
* @module IPython
* @namespace IPython
* @submodule Cell
*/
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
// TODO: remove IPython dependency here
"use strict";
var utils = IPython.utils;
var keycodes = IPython.keyboard.keycodes;
// monkey patch CM to be able to syntax highlight cell magics
// bug reported upstream,
// see https://github.com/marijnh/CodeMirror2/issues/670
if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
CodeMirror.modes.null = function() {
return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
};
}
/**
* The Base `Cell` class from which to inherit
* @class Cell
**/
CodeMirror.patchedGetMode = function(config, mode){
var cmmode = CodeMirror.getMode(config, mode);
if(cmmode.indent === null) {
console.log('patch mode "' , mode, '" on the fly');
cmmode.indent = function(){return 0;};
}
return cmmode;
};
// end monkey patching CodeMirror
/*
* @constructor
*
* @param {object|undefined} [options]
* @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
*/
var Cell = function (options) {
options = this.mergeopt(Cell, options);
// Constructor
//
// The Base `Cell` class from which to inherit.
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
options = options || {};
this.keyboard_manager = options.keyboard_manager;
this.events = options.events;
var config = this.mergeopt(Cell, options.config);
// superclass default overwrite our default
this.placeholder = options.placeholder || '';
this.read_only = options.cm_config.readOnly;
this.placeholder = config.placeholder || '';
this.read_only = config.cm_config.readOnly;
this.selected = false;
this.rendered = false;
this.mode = 'command';
this.metadata = {};
// load this from metadata later ?
this.user_highlight = 'auto';
this.cm_config = options.cm_config;
this.cm_config = config.cm_config;
this.cell_id = utils.uuid();
this._options = options;
this._options = config;
// For JS VM engines optimization, attributes should be all set (even
// to null) in the constructor, and if possible, if different subclass
@ -70,7 +78,12 @@ var IPython = (function (IPython) {
cm_config : {
indentUnit : 4,
readOnly: false,
theme: "default"
theme: "default",
extraKeys: {
"Cmd-Right":"goLineRight",
"End":"goLineRight",
"Cmd-Left":"goLineLeft"
}
}
};
@ -127,27 +140,27 @@ var IPython = (function (IPython) {
// We trigger events so that Cell doesn't have to depend on Notebook.
that.element.click(function (event) {
if (!that.selected) {
$([IPython.events]).trigger('select.Cell', {'cell':that});
that.events.trigger('select.Cell', {'cell':that});
}
});
that.element.focusin(function (event) {
if (!that.selected) {
$([IPython.events]).trigger('select.Cell', {'cell':that});
that.events.trigger('select.Cell', {'cell':that});
}
});
if (this.code_mirror) {
this.code_mirror.on("change", function(cm, change) {
$([IPython.events]).trigger("set_dirty.Notebook", {value: true});
that.events.trigger("set_dirty.Notebook", {value: true});
});
}
if (this.code_mirror) {
this.code_mirror.on('focus', function(cm, change) {
$([IPython.events]).trigger('edit_mode.Cell', {cell: that});
that.events.trigger('edit_mode.Cell', {cell: that});
});
}
if (this.code_mirror) {
this.code_mirror.on('blur', function(cm, change) {
$([IPython.events]).trigger('command_mode.Cell', {cell: that});
that.events.trigger('command_mode.Cell', {cell: that});
});
}
};
@ -165,8 +178,7 @@ var IPython = (function (IPython) {
* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
*/
Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
var that = this;
var shortcuts = IPython.keyboard_manager.edit_shortcuts;
var shortcuts = this.keyboard_manager.edit_shortcuts;
// if this is an edit_shortcuts shortcut, the global keyboard/shortcut
// manager will handle it
@ -544,9 +556,8 @@ var IPython = (function (IPython) {
this.code_mirror.setOption('mode', default_mode);
};
// Backwards compatibility.
IPython.Cell = Cell;
return IPython;
}(IPython));
return {'Cell': Cell};
});

@ -1,32 +1,28 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// CellToolbar
//============================================================================
/**
* A Module to control the per-cell toolbar.
* @module IPython
* @namespace IPython
* @submodule CellToolbar
*/
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/events'
], function(IPython, $, events) {
"use strict";
/**
* @constructor
* @class CellToolbar
* @param {The cell to attach the metadata UI to} cell
*/
var CellToolbar = function (cell) {
var CellToolbar = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// cell: Cell instance
// notebook: Notebook instance
//
// TODO: This leaks, when cell are deleted
// There is still a reference to each celltoolbars.
CellToolbar._instances.push(this);
this.cell = cell;
this.notebook = options.notebook;
this.cell = options.cell;
this.create_element();
this.rebuild();
return this;
@ -34,7 +30,7 @@ var IPython = (function (IPython) {
CellToolbar.prototype.create_element = function () {
this.inner_element = $('<div/>').addClass('celltoolbar')
this.inner_element = $('<div/>').addClass('celltoolbar');
this.element = $('<div/>').addClass('ctb_hideshow')
.append(this.inner_element);
};
@ -182,13 +178,14 @@ var IPython = (function (IPython) {
* CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
* CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
*/
CellToolbar.register_preset = function(name, preset_list) {
CellToolbar.register_preset = function(name, preset_list, notebook) {
CellToolbar._presets[name] = preset_list;
$([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
events.trigger('preset_added.CellToolbar', {name: name});
// When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
// In that case, activate the preset if needed.
if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
this.activate_preset(name);
if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
CellToolbar.activate_preset(name);
}
};
@ -229,7 +226,7 @@ var IPython = (function (IPython) {
CellToolbar.rebuild_all();
}
$([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name});
events.trigger('preset_activated.CellToolbar', {name: preset_name});
};
@ -283,7 +280,7 @@ var IPython = (function (IPython) {
}
// If there are no controls or the cell is a rendered TextCell hide the toolbar.
if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
this.hide();
} else {
this.show();
@ -347,7 +344,7 @@ var IPython = (function (IPython) {
setter(cell, !v);
chkb.attr("checked", !v);
});
button_container.append($('<div/>').append(lbl));
button_container.append($('<span/>').append(lbl));
};
};
@ -411,12 +408,12 @@ var IPython = (function (IPython) {
select.change(function(){
setter(cell, select.val());
});
button_container.append($('<div/>').append(lbl).append(select));
button_container.append($('<span/>').append(lbl).append(select));
};
};
// Backwards compatability.
IPython.CellToolbar = CellToolbar;
return IPython;
}(IPython));
return {'CellToolbar': CellToolbar};
});

@ -1,31 +1,29 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// CellToolbar Default
//============================================================================
/**
* Example Use for the CellToolbar library
*/
// IIFE without asignement, we don't modifiy the IPython namespace
(function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'jquery',
'notebook/js/celltoolbar',
'base/js/dialog',
], function($, celltoolbar, dialog) {
"use strict";
var CellToolbar = IPython.CellToolbar;
var raw_edit = function(cell){
IPython.dialog.edit_metadata(cell.metadata, function (md) {
cell.metadata = md;
var CellToolbar = celltoolbar.CellToolbar;
var raw_edit = function (cell) {
dialog.edit_metadata({
md: cell.metadata,
callback: function (md) {
cell.metadata = md;
},
name: 'Cell',
notebook: this.notebook,
keyboard_manager: this.keyboard_manager
});
};
var add_raw_edit_button = function(div, cell) {
var button_container = div;
var button_container = $(div);
var button = $('<button/>')
.addClass("btn btn-default btn-xs")
.text("Edit Metadata")
@ -36,11 +34,18 @@
button_container.append(button);
};
CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
var example_preset = [];
example_preset.push('default.rawedit');
var register = function (notebook) {
CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
raw_edit = $.proxy(raw_edit, {
notebook: notebook,
keyboard_manager: notebook.keyboard_manager
});
CellToolbar.register_preset('Edit Metadata', example_preset);
console.log('Default extension for cell metadata editing loaded.');
var example_preset = [];
example_preset.push('default.rawedit');
}(IPython));
CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
console.log('Default extension for cell metadata editing loaded.');
};
return {'register': register};
});

@ -1,28 +1,20 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// CellToolbar Example
//============================================================================
/**
* Example Use for the CellToolbar library
* add the following to your custom.js to load
* Celltoolbar UI for slideshow
*
* ```
* $.getScript('/static/js/celltoolbarpresets/example.js');
* ```
*/
// IIFE without asignement, we don't modifiy the IPython namespace
(function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
// Example Use for the CellToolbar library
// add the following to your custom.js to load
// Celltoolbar UI for slideshow
// ```
// $.getScript('/static/js/celltoolbarpresets/example.js');
// ```
define([
'jquery',
'notebook/js/celltoolbar',
], function($, celltoolbar) {
"use strict";
var CellToolbar = IPython.CellToolbar;
var CellToolbar = celltoolbar.CellToolbar;
var example_preset = [];
@ -32,32 +24,32 @@
var fun = function(value){
try{
if(value){
cell.code_mirror.setOption('readOnly','nocursor')
button.button('option','icons',{primary:'ui-icon-locked'})
cell.code_mirror.setOption('readOnly','nocursor');
button.button('option','icons',{primary:'ui-icon-locked'});
} else {
cell.code_mirror.setOption('readOnly',false)
button.button('option','icons',{primary:'ui-icon-unlocked'})
cell.code_mirror.setOption('readOnly',false);
button.button('option','icons',{primary:'ui-icon-unlocked'});
}
} catch(e){}
}
fun(cell.metadata.ro)
};
fun(cell.metadata.ro);
button.click(function(){
var v = cell.metadata.ro;
var locked = !v;
cell.metadata.ro = locked;
fun(locked)
fun(locked);
})
.css('height','16px')
.css('width','35px');
button_container.append(button);
}
};
CellToolbar.register_callback('example.lock',simple_button);
example_preset.push('example.lock');
var toggle_test = function(div, cell) {
var button_container = $(div)
var button_container = $(div);
var button = $('<div/>')
.button({label:String(cell.metadata.foo)}).
css('width','65px');
@ -65,9 +57,9 @@
var v = cell.metadata.foo;
cell.metadata.foo = !v;
button.button("option","label",String(!v));
})
});
button_container.append(button);
}
};
CellToolbar.register_callback('example.toggle',toggle_test);
example_preset.push('example.toggle');
@ -76,16 +68,16 @@
// setter
function(cell, value){
// we check that the slideshow namespace exist and create it if needed
if (cell.metadata.yn_test == undefined){cell.metadata.yn_test = {}}
if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
// set the value
cell.metadata.yn_test.value = value
cell.metadata.yn_test.value = value;
},
//geter
function(cell){ var ns = cell.metadata.yn_test;
// if the slideshow namespace does not exist return `undefined`
// (will be interpreted as `false` by checkbox) otherwise
// return the value
return (ns == undefined)? undefined: ns.value
return (ns === undefined)? undefined: ns.value;
}
);
@ -103,16 +95,16 @@
// setter
function(cell,value){
// we check that the slideshow namespace exist and create it if needed
if (cell.metadata.test == undefined){cell.metadata.test = {}}
if (cell.metadata.test === undefined){cell.metadata.test = {};}
// set the value
cell.metadata.test.slide_type = value
cell.metadata.test.slide_type = value;
},
//geter
function(cell){ var ns = cell.metadata.test;
// if the slideshow namespace does not exist return `undefined`
// (will be interpreted as `false` by checkbox) otherwise
// return the value
return (ns == undefined)? undefined: ns.slide_type
return (ns === undefined)? undefined: ns.slide_type;
});
CellToolbar.register_callback('example.select',select_test);
@ -120,7 +112,7 @@
var simple_dialog = function(title,text){
var dlg = $('<div/>').attr('title',title)
.append($('<p/>').text(text))
.append($('<p/>').text(text));
$(dlg).dialog({
autoOpen: true,
height: 300,
@ -131,24 +123,26 @@
$(this).remove();
}
});
}
};
var add_simple_dialog_button = function(div, cell) {
var help_text = ["This is the Metadata editting UI.",
"It heavily rely on plugin to work ",
"and is still under developpement. You shouldn't wait too long before",
" seeing some customisable buttons in those toolbar."
].join('\n')
var button_container = $(div)
].join('\n');
var button_container = $(div);
var button = $('<div/>').button({label:'?'})
.click(function(){simple_dialog('help',help_text); return false;})
.click(function(){simple_dialog('help',help_text); return false;});
button_container.append(button);
}
CellToolbar.register_callback('example.help',add_simple_dialog_button)
example_preset.push('example.help')
};
CellToolbar.register_preset('Example',example_preset);
console.log('Example extension for metadata editing loaded.');
var register = function (notebook) {
CellToolbar.register_callback('example.help',add_simple_dialog_button);
example_preset.push('example.help');
}(IPython));
CellToolbar.register_preset('Example',example_preset, notebook);
console.log('Example extension for metadata editing loaded.');
};
return {'register': register};
});

@ -1,18 +1,15 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// CellToolbar Example
//============================================================================
define([
'jquery',
'notebook/js/celltoolbar',
'base/js/dialog',
'base/js/keyboard',
], function($, celltoolbar, dialog, keyboard) {
"use strict";
(function(IPython) {
"use strict";
var CellToolbar = IPython.CellToolbar;
var CellToolbar = celltoolbar.CellToolbar;
var raw_cell_preset = [];
var select_type = CellToolbar.utils.select_ui_generator([
@ -39,7 +36,7 @@
$('<input/>').attr('type','text').attr('size','25')
.val(cell.metadata.raw_mimetype || "-")
);
IPython.dialog.modal({
dialog.modal({
title: "Raw Cell MIME Type",
body: dialog,
buttons : {
@ -57,7 +54,7 @@
var that = $(this);
// Upon ENTER, click the OK button.
that.find('input[type="text"]').keydown(function (event, ui) {
if (event.which === IPython.keyboard.keycodes.enter) {
if (event.which === keyboard.keycodes.enter) {
that.find('.btn-primary').first().click();
return false;
}
@ -77,11 +74,13 @@
"Raw NBConvert Format"
);
CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
raw_cell_preset.push('raw_cell.select');
var register = function (notebook) {
CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
raw_cell_preset.push('raw_cell.select');
CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
console.log('Raw Cell Format toolbar preset loaded.');
CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
console.log('Raw Cell Format toolbar preset loaded.');
};
return {'register': register};
}(IPython));
});

@ -1,19 +1,14 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
//CellToolbar Example
//============================================================================
// IIFE without asignement, we don't modifiy the IPython namespace
(function (IPython) {
define([
'jquery',
'notebook/js/celltoolbar',
], function($, celltoolbar) {
"use strict";
var CellToolbar = IPython.CellToolbar;
var CellToolbar = celltoolbar.CellToolbar;
var slideshow_preset = [];
var select_type = CellToolbar.utils.select_ui_generator([
@ -27,24 +22,25 @@
// setter
function(cell, value){
// we check that the slideshow namespace exist and create it if needed
if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
// set the value
cell.metadata.slideshow.slide_type = value
cell.metadata.slideshow.slide_type = value;
},
//geter
function(cell){ var ns = cell.metadata.slideshow;
// if the slideshow namespace does not exist return `undefined`
// (will be interpreted as `false` by checkbox) otherwise
// return the value
return (ns == undefined)? undefined: ns.slide_type
return (ns === undefined)? undefined: ns.slide_type;
},
"Slide Type");
CellToolbar.register_callback('slideshow.select',select_type);
slideshow_preset.push('slideshow.select');
CellToolbar.register_preset('Slideshow',slideshow_preset);
console.log('Slideshow extension for metadata editing loaded.');
var register = function (notebook) {
CellToolbar.register_callback('slideshow.select',select_type);
slideshow_preset.push('slideshow.select');
}(IPython));
CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
console.log('Slideshow extension for metadata editing loaded.');
};
return {'register': register};
});

@ -1,68 +1,67 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// CodeCell
//============================================================================
/**
* An extendable module that provide base functionnality to create cell for notebook.
* @module IPython
* @namespace IPython
* @submodule CodeCell
*/
/* local util for codemirror */
var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
/**
*
* function to delete until previous non blanking space character
* or first multiple of 4 tabstop.
* @private
*/
CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
var cur = cm.getCursor(), line = cm.getLine(cur.line);
var tabsize = cm.getOption('tabSize');
var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
var select = cm.getRange(from,cur);
if( select.match(/^\ +$/) !== null){
cm.replaceRange("",from,cur);
} else {
cm.deleteH(-1,"char");
}
};
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/keyboard',
'notebook/js/cell',
'notebook/js/outputarea',
'notebook/js/completer',
'notebook/js/celltoolbar',
], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
"use strict";
var Cell = cell.Cell;
var utils = IPython.utils;
var keycodes = IPython.keyboard.keycodes;
/* local util for codemirror */
var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
/**
* A Cell conceived to write code.
*
* The kernel doesn't have to be set at creation time, in that case
* it will be null and set_kernel has to be called later.
* @class CodeCell
* @extends IPython.Cell
*
* @constructor
* @param {Object|null} kernel
* @param {object|undefined} [options]
* @param [options.cm_config] {object} config to pass to CodeMirror
* function to delete until previous non blanking space character
* or first multiple of 4 tabstop.
* @private
*/
CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
var cur = cm.getCursor(), line = cm.getLine(cur.line);
var tabsize = cm.getOption('tabSize');
var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
var select = cm.getRange(from,cur);
if( select.match(/^\ +$/) !== null){
cm.replaceRange("",from,cur);
} else {
cm.deleteH(-1,"char");
}
};
var keycodes = keyboard.keycodes;
var CodeCell = function (kernel, options) {
// Constructor
//
// A Cell conceived to write code.
//
// Parameters:
// kernel: Kernel instance
// The kernel doesn't have to be set at creation time, in that case
// it will be null and set_kernel has to be called later.
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
// tooltip: Tooltip instance
this.kernel = kernel || null;
this.notebook = options.notebook;
this.collapsed = false;
this.events = options.events;
this.tooltip = options.tooltip;
this.config = options.config;
// create all attributed in constructor function
// even if null for V8 VM optimisation
@ -77,9 +76,11 @@ var IPython = (function (IPython) {
onKeyEvent: $.proxy(this.handle_keyevent,this)
};
options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
IPython.Cell.apply(this,[options]);
var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
Cell.apply(this,[{
config: config,
keyboard_manager: options.keyboard_manager,
events: this.events}]);
// Attributes we want to override in this subclass.
this.cell_type = "code";
@ -109,29 +110,31 @@ var IPython = (function (IPython) {
CodeCell.msg_cells = {};
CodeCell.prototype = new IPython.Cell();
CodeCell.prototype = new Cell();
/**
* @method auto_highlight
*/
CodeCell.prototype.auto_highlight = function () {
this._auto_highlight(IPython.config.cell_magic_highlight);
this._auto_highlight(this.config.cell_magic_highlight);
};
/** @method create_element */
CodeCell.prototype.create_element = function () {
IPython.Cell.prototype.create_element.apply(this, arguments);
Cell.prototype.create_element.apply(this, arguments);
var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
var cell = $('<div></div>').addClass('cell code_cell');
cell.attr('tabindex','2');
var input = $('<div></div>').addClass('input');
var prompt = $('<div/>').addClass('prompt input_prompt');
var inner_cell = $('<div/>').addClass('inner_cell');
this.celltoolbar = new IPython.CellToolbar(this);
this.celltoolbar = new celltoolbar.CellToolbar({
cell: this,
notebook: this.notebook});
inner_cell.append(this.celltoolbar.element);
var input_area = $('<div/>').addClass('input_area');
this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
$(this.code_mirror.getInputField()).attr("spellcheck", "false");
inner_cell.append(input_area);
input.append(prompt).append(inner_cell);
@ -158,13 +161,17 @@ var IPython = (function (IPython) {
var output = $('<div></div>');
cell.append(input).append(widget_area).append(output);
this.element = cell;
this.output_area = new IPython.OutputArea(output, true);
this.completer = new IPython.Completer(this);
this.output_area = new outputarea.OutputArea({
selector: output,
prompt_area: true,
events: this.events,
keyboard_manager: this.keyboard_manager});
this.completer = new completer.Completer(this, this.events);
};
/** @method bind_events */
CodeCell.prototype.bind_events = function () {
IPython.Cell.prototype.bind_events.apply(this);
Cell.prototype.bind_events.apply(this);
var that = this;
this.element.focusout(
@ -187,7 +194,7 @@ var IPython = (function (IPython) {
// they are sent, and remove tooltip if any, except for tab again
var tooltip_closed = null;
if (event.type === 'keydown' && event.which != keycodes.tab ) {
tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
}
var cur = editor.getCursor();
@ -195,21 +202,21 @@ var IPython = (function (IPython) {
this.auto_highlight();
}
if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
// triger on keypress (!) otherwise inconsistent event.which depending on plateform
// browser and keyboard layout !
// Pressing '(' , request tooltip, don't forget to reappend it
// The second argument says to hide the tooltip if the docstring
// is actually empty
IPython.tooltip.pending(that, true);
this.tooltip.pending(that, true);
} else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
// If tooltip is active, cancel it. The call to
// remove_and_cancel_tooltip above doesn't pass, force=true.
// Because of this it won't actually close the tooltip
// if it is in sticky mode. Thus, we have to check again if it is open
// and close it with force=true.
if (!IPython.tooltip._hidden) {
IPython.tooltip.remove_and_cancel_tooltip(true);
if (!this.tooltip._hidden) {
this.tooltip.remove_and_cancel_tooltip(true);
}
// If we closed the tooltip, don't let CM or the global handlers
// handle this event.
@ -223,12 +230,12 @@ var IPython = (function (IPython) {
return false;
}
}
IPython.tooltip.request(that);
this.tooltip.request(that);
event.stop();
return true;
} else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
// Tab completion.
IPython.tooltip.remove_and_cancel_tooltip();
this.tooltip.remove_and_cancel_tooltip();
if (editor.somethingSelected()) {
return false;
}
@ -246,7 +253,7 @@ var IPython = (function (IPython) {
// keyboard event wasn't one of those unique to code cells, let's see
// if it's one of the generic ones (i.e. check edit mode shortcuts)
return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
};
// Kernel related calls.
@ -305,7 +312,7 @@ var IPython = (function (IPython) {
};
CodeCell.prototype._open_with_pager = function (payload) {
$([IPython.events]).trigger('open_with_text.Pager', payload);
this.events.trigger('open_with_text.Pager', payload);
};
/**
@ -315,7 +322,7 @@ var IPython = (function (IPython) {
CodeCell.prototype._handle_execute_reply = function (msg) {
this.set_input_prompt(msg.content.execution_count);
this.element.removeClass("running");
$([IPython.events]).trigger('set_dirty.Notebook', {value: true});
this.events.trigger('set_dirty.Notebook', {value: true});
};
/**
@ -324,7 +331,7 @@ var IPython = (function (IPython) {
*/
CodeCell.prototype._handle_set_next_input = function (payload) {
var data = {'cell': this, 'text': payload.text};
$([IPython.events]).trigger('set_next_input.Notebook', data);
this.events.trigger('set_next_input.Notebook', data);
};
/**
@ -339,7 +346,7 @@ var IPython = (function (IPython) {
// Basic cell manipulation.
CodeCell.prototype.select = function () {
var cont = IPython.Cell.prototype.select.apply(this);
var cont = Cell.prototype.select.apply(this);
if (cont) {
this.code_mirror.refresh();
this.auto_highlight();
@ -348,7 +355,7 @@ var IPython = (function (IPython) {
};
CodeCell.prototype.render = function () {
var cont = IPython.Cell.prototype.render.apply(this);
var cont = Cell.prototype.render.apply(this);
// Always execute, even if we are already in the rendered state
return cont;
};
@ -451,7 +458,7 @@ var IPython = (function (IPython) {
// JSON serialization
CodeCell.prototype.fromJSON = function (data) {
IPython.Cell.prototype.fromJSON.apply(this, arguments);
Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === 'code') {
if (data.input !== undefined) {
this.set_text(data.input);
@ -479,7 +486,7 @@ var IPython = (function (IPython) {
CodeCell.prototype.toJSON = function () {
var data = IPython.Cell.prototype.toJSON.apply(this);
var data = Cell.prototype.toJSON.apply(this);
data.input = this.get_text();
// is finite protect against undefined and '*' value
if (isFinite(this.input_prompt_number)) {
@ -499,11 +506,11 @@ var IPython = (function (IPython) {
* @return is the action being taken
*/
CodeCell.prototype.unselect = function () {
var cont = IPython.Cell.prototype.unselect.apply(this);
var cont = Cell.prototype.unselect.apply(this);
if (cont) {
// When a code cell is usnelected, make sure that the corresponding
// tooltip and completer to that cell is closed.
IPython.tooltip.remove_and_cancel_tooltip(true);
this.tooltip.remove_and_cancel_tooltip(true);
if (this.completer !== null) {
this.completer.close();
}
@ -511,7 +518,8 @@ var IPython = (function (IPython) {
return cont;
};
// Backwards compatability.
IPython.CodeCell = CodeCell;
return IPython;
}(IPython));
return {'CodeCell': CodeCell};
});

@ -7,10 +7,15 @@ CodeMirror.requireMode('python',function(){
"use strict";
CodeMirror.defineMode("ipython", function(conf, parserConf) {
parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
parserConf.name = 'python'
return CodeMirror.getMode(conf, parserConf);
var pythonConf = {};
for (var prop in parserConf) {
if (parserConf.hasOwnProperty(prop)) {
pythonConf[prop] = parserConf[prop];
}
}
pythonConf.name = 'python';
pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
return CodeMirror.getMode(conf, pythonConf);
}, 'python');
CodeMirror.defineMIME("text/x-ipython", "ipython");

@ -8,7 +8,6 @@
CodeMirror.requireMode('gfm', function(){
CodeMirror.requireMode('stex', function(){
console.log('defining custom mode...');
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
var gfm_mode = CodeMirror.getMode(config, "gfm");

@ -1,17 +1,17 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
// Completer
//
// Completer is be a class that takes a cell instance.
var IPython = (function (IPython) {
// that will prevent us from misspelling
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/keyboard',
'notebook/js/contexthint',
], function(IPython, $, utils, keyboard) {
"use strict";
// easier key mapping
var keycodes = IPython.keyboard.keycodes;
var utils = IPython.utils;
var keycodes = keyboard.keycodes;
var prepend_n_prc = function(str, n) {
for( var i =0 ; i< n ; i++){
@ -78,14 +78,14 @@ var IPython = (function (IPython) {
}
var Completer = function (cell) {
var Completer = function (cell, events) {
this.cell = cell;
this.editor = cell.code_mirror;
var that = this;
$([IPython.events]).on('status_busy.Kernel', function () {
events.on('status_busy.Kernel', function () {
that.skip_kernel_completion = true;
});
$([IPython.events]).on('status_idle.Kernel', function () {
events.on('status_idle.Kernel', function () {
that.skip_kernel_completion = false;
});
};
@ -351,6 +351,18 @@ var IPython = (function (IPython) {
}
index = Math.min(Math.max(index, 0), options.length-1);
this.sel[0].selectedIndex = index;
} else if (code == keycodes.pageup || code == keycodes.pagedown) {
CodeMirror.e_stop(event);
var options = this.sel.find('option');
var index = this.sel[0].selectedIndex;
if (code == keycodes.pageup) {
index -= 10; // As 10 is the hard coded size of the drop down menu
} else {
index += 10;
}
index = Math.min(Math.max(index, 0), options.length-1);
this.sel[0].selectedIndex = index;
} else if (code == keycodes.left || code == keycodes.right) {
this.close();
}
@ -379,7 +391,9 @@ var IPython = (function (IPython) {
that.carry_on_completion();
}, 50);
};
// For backwards compatability.
IPython.Completer = Completer;
return IPython;
}(IPython));
return {'Completer': Completer};
});

@ -1,12 +1,15 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
// highly adapted for codemiror jshint
(function () {
define([], function() {
"use strict";
function forEach(arr, f) {
var forEach = function(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
};
function arrayContains(arr, item) {
var arrayContains = function(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
@ -17,7 +20,7 @@
return false;
}
return arr.indexOf(item) != -1;
}
};
CodeMirror.contextHint = function (editor) {
// Find the token at the cursor
@ -26,7 +29,7 @@
tprop = token;
// If it's not a 'word-style' token, ignore the token.
// If it is a property, find out what it is a property of.
var list = new Array();
var list = [];
var clist = getCompletions(token, editor);
for (var i = 0; i < clist.length; i++) {
list.push({
@ -40,55 +43,56 @@
line: cur.line,
ch: token.end
}
})
});
}
return list;
}
};
// find all 'words' of current cell
var getAllTokens = function (editor) {
var found = [];
var found = [];
// add to found if not already in it
// add to found if not already in it
function maybeAdd(str) {
if (!arrayContains(found, str)) found.push(str);
}
function maybeAdd(str) {
if (!arrayContains(found, str)) found.push(str);
}
// loop through all token on all lines
var lineCount = editor.lineCount();
// loop on line
for (var l = 0; l < lineCount; l++) {
var line = editor.getLine(l);
//loop on char
for (var c = 1; c < line.length; c++) {
var tk = editor.getTokenAt({
line: l,
ch: c
});
// if token has a class, it has geat chances of beeing
// of interest. Add it to the list of possible completions.
// we could skip token of ClassName 'comment'
// or 'number' and 'operator'
if (tk.className != null) {
maybeAdd(tk.string);
}
// jump to char after end of current token
c = tk.end;
// loop through all token on all lines
var lineCount = editor.lineCount();
// loop on line
for (var l = 0; l < lineCount; l++) {
var line = editor.getLine(l);
//loop on char
for (var c = 1; c < line.length; c++) {
var tk = editor.getTokenAt({
line: l,
ch: c
});
// if token has a class, it has geat chances of beeing
// of interest. Add it to the list of possible completions.
// we could skip token of ClassName 'comment'
// or 'number' and 'operator'
if (tk.className !== null) {
maybeAdd(tk.string);
}
// jump to char after end of current token
c = tk.end;
}
return found;
}
return found;
};
function getCompletions(token, editor) {
var getCompletions = function(token, editor) {
var candidates = getAllTokens(editor);
// filter all token that have a common start (but nox exactly) the lenght of the current token
var lambda = function (x) {
return (x.indexOf(token.string) == 0 && x != token.string)
return (x.indexOf(token.string) === 0 && x != token.string);
};
var filterd = candidates.filter(lambda);
return filterd;
}
})();
};
return {'contextHint': CodeMirror.contextHint};
});

@ -0,0 +1,91 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var KernelSelector = function(selector, notebook) {
this.selector = selector;
this.notebook = notebook;
this.events = notebook.events;
this.current_selection = notebook.default_kernel_name;
this.kernelspecs = {};
if (this.selector !== undefined) {
this.element = $(selector);
this.request_kernelspecs();
}
this.bind_events();
// Make the object globally available for user convenience & inspection
IPython.kernelselector = this;
};
KernelSelector.prototype.request_kernelspecs = function() {
var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
$.ajax(url, {success: $.proxy(this._got_kernelspecs, this)});
};
KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) {
this.kernelspecs = {};
var menu = this.element.find("#kernel_selector");
var change_kernel_submenu = $("#menu-change-kernel-submenu");
for (var i = 0; i < data.length; i++) {
var ks = data[i];
this.kernelspecs[ks.name] = ks;
var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
.attr('href', '#')
.click($.proxy(this.change_kernel, this, ks.name))
.text(ks.display_name));
menu.append(ksentry);
var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
.attr('href', '#')
.click($.proxy(this.change_kernel, this, ks.name))
.text(ks.display_name));
change_kernel_submenu.append(ks_submenu_entry);
}
};
KernelSelector.prototype.change_kernel = function(kernel_name) {
if (kernel_name === this.current_selection) {
return;
}
var ks = this.kernelspecs[kernel_name];
try {
this.notebook.start_session(kernel_name);
} catch (e) {
if (e.name === 'SessionAlreadyStarting') {
console.log("Cannot change kernel while waiting for pending session start.");
} else {
// unhandled error
throw e;
}
// only trigger spec_changed if change was successful
return;
}
this.events.trigger('spec_changed.Kernel', ks);
};
KernelSelector.prototype.bind_events = function() {
var that = this;
this.events.on('spec_changed.Kernel', function(event, data) {
that.current_selection = data.name;
that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
});
this.events.on('started.Session', function(events, session) {
if (session.kernel_name !== that.current_selection) {
// If we created a 'python' session, we only know if it's Python
// 3 or 2 on the server's reply, so we fire the event again to
// set things up.
var ks = that.kernelspecs[session.kernel_name];
that.events.trigger('spec_changed.Kernel', ks);
}
});
};
return {'KernelSelector': KernelSelector};
});

@ -1,468 +1,476 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Keyboard management
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/keyboard',
], function(IPython, $, utils, keyboard) {
"use strict";
var browser = utils.browser[0];
var platform = utils.platform;
var browser = IPython.utils.browser[0];
var platform = IPython.utils.platform;
// Main keyboard manager for the notebook
var keycodes = keyboard.keycodes;
// Default keyboard shortcuts
var KeyboardManager = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// pager: Pager instance
this.mode = 'command';
this.enabled = true;
this.pager = options.pager;
this.quick_help = undefined;
this.notebook = undefined;
this.bind_events();
this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
};
var default_common_shortcuts = {
'shift' : {
help : '',
help_index : '',
handler : function (event) {
// ignore shift keydown
return true;
}
},
'shift-enter' : {
help : 'run cell, select below',
help_index : 'ba',
handler : function (event) {
IPython.notebook.execute_cell_and_select_below();
return false;
}
},
'ctrl-enter' : {
help : 'run cell',
help_index : 'bb',
handler : function (event) {
IPython.notebook.execute_cell();
return false;
}
},
'alt-enter' : {
help : 'run cell, insert below',
help_index : 'bc',
handler : function (event) {
IPython.notebook.execute_cell_and_insert_below();
return false;
KeyboardManager.prototype.get_default_common_shortcuts = function() {
var that = this;
var shortcuts = {
'shift' : {
help : '',
help_index : '',
handler : function (event) {
// ignore shift keydown
return true;
}
},
'shift-enter' : {
help : 'run cell, select below',
help_index : 'ba',
handler : function (event) {
that.notebook.execute_cell_and_select_below();
return false;
}
},
'ctrl-enter' : {
help : 'run cell',
help_index : 'bb',
handler : function (event) {
that.notebook.execute_cell();
return false;
}
},
'alt-enter' : {
help : 'run cell, insert below',
help_index : 'bc',
handler : function (event) {
that.notebook.execute_cell_and_insert_below();
return false;
}
}
};
if (platform === 'MacOS') {
shortcuts['cmd-s'] =
{
help : 'save notebook',
help_index : 'fb',
handler : function (event) {
that.notebook.save_checkpoint();
event.preventDefault();
return false;
}
};
} else {
shortcuts['ctrl-s'] =
{
help : 'save notebook',
help_index : 'fb',
handler : function (event) {
that.notebook.save_checkpoint();
event.preventDefault();
return false;
}
};
}
return shortcuts;
};
if (platform === 'MacOS') {
default_common_shortcuts['cmd-s'] =
{
help : 'save notebook',
help_index : 'fb',
KeyboardManager.prototype.get_default_edit_shortcuts = function() {
var that = this;
return {
'esc' : {
help : 'command mode',
help_index : 'aa',
handler : function (event) {
IPython.notebook.save_checkpoint();
event.preventDefault();
that.notebook.command_mode();
return false;
}
};
} else {
default_common_shortcuts['ctrl-s'] =
{
help : 'save notebook',
help_index : 'fb',
},
'ctrl-m' : {
help : 'command mode',
help_index : 'ab',
handler : function (event) {
IPython.notebook.save_checkpoint();
event.preventDefault();
that.notebook.command_mode();
return false;
}
};
}
// Edit mode defaults
var default_edit_shortcuts = {
'esc' : {
help : 'command mode',
help_index : 'aa',
handler : function (event) {
IPython.notebook.command_mode();
return false;
}
},
'ctrl-m' : {
help : 'command mode',
help_index : 'ab',
handler : function (event) {
IPython.notebook.command_mode();
return false;
}
},
'up' : {
help : '',
help_index : '',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
var cell = IPython.notebook.get_cell(index);
if (cell && cell.at_top() && index !== 0) {
event.preventDefault();
IPython.notebook.command_mode();
IPython.notebook.select_prev();
IPython.notebook.edit_mode();
var cm = IPython.notebook.get_selected_cell().code_mirror;
cm.setCursor(cm.lastLine(), 0);
return false;
} else if (cell) {
var cm = cell.code_mirror;
cm.execCommand('goLineUp');
},
'up' : {
help : '',
help_index : '',
handler : function (event) {
var index = that.notebook.get_selected_index();
var cell = that.notebook.get_cell(index);
if (cell && cell.at_top() && index !== 0) {
event.preventDefault();
that.notebook.command_mode();
that.notebook.select_prev();
that.notebook.edit_mode();
var cm = that.notebook.get_selected_cell().code_mirror;
cm.setCursor(cm.lastLine(), 0);
return false;
} else if (cell) {
var cm = cell.code_mirror;
cm.execCommand('goLineUp');
return false;
}
}
},
'down' : {
help : '',
help_index : '',
handler : function (event) {
var index = that.notebook.get_selected_index();
var cell = that.notebook.get_cell(index);
if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
event.preventDefault();
that.notebook.command_mode();
that.notebook.select_next();
that.notebook.edit_mode();
var cm = that.notebook.get_selected_cell().code_mirror;
cm.setCursor(0, 0);
return false;
} else {
var cm = cell.code_mirror;
cm.execCommand('goLineDown');
return false;
}
}
},
'ctrl-shift--' : {
help : 'split cell',
help_index : 'ea',
handler : function (event) {
that.notebook.split_cell();
return false;
}
}
},
'down' : {
help : '',
help_index : '',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
var cell = IPython.notebook.get_cell(index);
if (cell.at_bottom() && index !== (IPython.notebook.ncells()-1)) {
event.preventDefault();
IPython.notebook.command_mode();
IPython.notebook.select_next();
IPython.notebook.edit_mode();
var cm = IPython.notebook.get_selected_cell().code_mirror;
cm.setCursor(0, 0);
return false;
} else {
var cm = cell.code_mirror;
cm.execCommand('goLineDown');
},
'ctrl-shift-subtract' : {
help : '',
help_index : 'eb',
handler : function (event) {
that.notebook.split_cell();
return false;
}
}
},
'ctrl-shift--' : {
help : 'split cell',
help_index : 'ea',
handler : function (event) {
IPython.notebook.split_cell();
return false;
}
},
'ctrl-shift-subtract' : {
help : '',
help_index : 'eb',
handler : function (event) {
IPython.notebook.split_cell();
return false;
}
},
},
};
};
// Command mode defaults
var default_command_shortcuts = {
'enter' : {
help : 'edit mode',
help_index : 'aa',
handler : function (event) {
IPython.notebook.edit_mode();
return false;
}
},
'up' : {
help : 'select previous cell',
help_index : 'da',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
if (index !== 0 && index !== null) {
IPython.notebook.select_prev();
IPython.notebook.focus_cell();
KeyboardManager.prototype.get_default_command_shortcuts = function() {
var that = this;
return {
'enter' : {
help : 'edit mode',
help_index : 'aa',
handler : function (event) {
that.notebook.edit_mode();
return false;
}
return false;
}
},
'down' : {
help : 'select next cell',
help_index : 'db',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
if (index !== (IPython.notebook.ncells()-1) && index !== null) {
IPython.notebook.select_next();
IPython.notebook.focus_cell();
},
'up' : {
help : 'select previous cell',
help_index : 'da',
handler : function (event) {
var index = that.notebook.get_selected_index();
if (index !== 0 && index !== null) {
that.notebook.select_prev();
that.notebook.focus_cell();
}
return false;
}
return false;
}
},
'k' : {
help : 'select previous cell',
help_index : 'dc',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
if (index !== 0 && index !== null) {
IPython.notebook.select_prev();
IPython.notebook.focus_cell();
},
'down' : {
help : 'select next cell',
help_index : 'db',
handler : function (event) {
var index = that.notebook.get_selected_index();
if (index !== (that.notebook.ncells()-1) && index !== null) {
that.notebook.select_next();
that.notebook.focus_cell();
}
return false;
}
return false;
}
},
'j' : {
help : 'select next cell',
help_index : 'dd',
handler : function (event) {
var index = IPython.notebook.get_selected_index();
if (index !== (IPython.notebook.ncells()-1) && index !== null) {
IPython.notebook.select_next();
IPython.notebook.focus_cell();
},
'k' : {
help : 'select previous cell',
help_index : 'dc',
handler : function (event) {
var index = that.notebook.get_selected_index();
if (index !== 0 && index !== null) {
that.notebook.select_prev();
that.notebook.focus_cell();
}
return false;
}
return false;
}
},
'x' : {
help : 'cut cell',
help_index : 'ee',
handler : function (event) {
IPython.notebook.cut_cell();
return false;
}
},
'c' : {
help : 'copy cell',
help_index : 'ef',
handler : function (event) {
IPython.notebook.copy_cell();
return false;
}
},
'shift-v' : {
help : 'paste cell above',
help_index : 'eg',
handler : function (event) {
IPython.notebook.paste_cell_above();
return false;
}
},
'v' : {
help : 'paste cell below',
help_index : 'eh',
handler : function (event) {
IPython.notebook.paste_cell_below();
return false;
}
},
'd' : {
help : 'delete cell (press twice)',
help_index : 'ej',
count: 2,
handler : function (event) {
IPython.notebook.delete_cell();
return false;
}
},
'a' : {
help : 'insert cell above',
help_index : 'ec',
handler : function (event) {
IPython.notebook.insert_cell_above();
IPython.notebook.select_prev();
IPython.notebook.focus_cell();
return false;
}
},
'b' : {
help : 'insert cell below',
help_index : 'ed',
handler : function (event) {
IPython.notebook.insert_cell_below();
IPython.notebook.select_next();
IPython.notebook.focus_cell();
return false;
}
},
'y' : {
help : 'to code',
help_index : 'ca',
handler : function (event) {
IPython.notebook.to_code();
return false;
}
},
'm' : {
help : 'to markdown',
help_index : 'cb',
handler : function (event) {
IPython.notebook.to_markdown();
return false;
}
},
'r' : {
help : 'to raw',
help_index : 'cc',
handler : function (event) {
IPython.notebook.to_raw();
return false;
}
},
'1' : {
help : 'to heading 1',
help_index : 'cd',
handler : function (event) {
IPython.notebook.to_heading(undefined, 1);
return false;
}
},
'2' : {
help : 'to heading 2',
help_index : 'ce',
handler : function (event) {
IPython.notebook.to_heading(undefined, 2);
return false;
}
},
'3' : {
help : 'to heading 3',
help_index : 'cf',
handler : function (event) {
IPython.notebook.to_heading(undefined, 3);
return false;
}
},
'4' : {
help : 'to heading 4',
help_index : 'cg',
handler : function (event) {
IPython.notebook.to_heading(undefined, 4);
return false;
}
},
'5' : {
help : 'to heading 5',
help_index : 'ch',
handler : function (event) {
IPython.notebook.to_heading(undefined, 5);
return false;
}
},
'6' : {
help : 'to heading 6',
help_index : 'ci',
handler : function (event) {
IPython.notebook.to_heading(undefined, 6);
return false;
}
},
'o' : {
help : 'toggle output',
help_index : 'gb',
handler : function (event) {
IPython.notebook.toggle_output();
return false;
}
},
'shift-o' : {
help : 'toggle output scrolling',
help_index : 'gc',
handler : function (event) {
IPython.notebook.toggle_output_scroll();
return false;
}
},
's' : {
help : 'save notebook',
help_index : 'fa',
handler : function (event) {
IPython.notebook.save_checkpoint();
return false;
}
},
'ctrl-j' : {
help : 'move cell down',
help_index : 'eb',
handler : function (event) {
IPython.notebook.move_cell_down();
return false;
}
},
'ctrl-k' : {
help : 'move cell up',
help_index : 'ea',
handler : function (event) {
IPython.notebook.move_cell_up();
return false;
}
},
'l' : {
help : 'toggle line numbers',
help_index : 'ga',
handler : function (event) {
IPython.notebook.cell_toggle_line_numbers();
return false;
}
},
'i' : {
help : 'interrupt kernel (press twice)',
help_index : 'ha',
count: 2,
handler : function (event) {
IPython.notebook.kernel.interrupt();
return false;
}
},
'0' : {
help : 'restart kernel (press twice)',
help_index : 'hb',
count: 2,
handler : function (event) {
IPython.notebook.restart_kernel();
return false;
}
},
'h' : {
help : 'keyboard shortcuts',
help_index : 'ge',
handler : function (event) {
IPython.quick_help.show_keyboard_shortcuts();
return false;
}
},
'z' : {
help : 'undo last delete',
help_index : 'ei',
handler : function (event) {
IPython.notebook.undelete_cell();
return false;
}
},
'shift-m' : {
help : 'merge cell below',
help_index : 'ek',
handler : function (event) {
IPython.notebook.merge_cell_below();
return false;
}
},
'q' : {
help : 'close pager',
help_index : 'gd',
handler : function (event) {
IPython.pager.collapse();
return false;
}
},
};
// Main keyboard manager for the notebook
var ShortcutManager = IPython.keyboard.ShortcutManager;
var keycodes = IPython.keyboard.keycodes;
var KeyboardManager = function () {
this.mode = 'command';
this.enabled = true;
this.bind_events();
this.command_shortcuts = new ShortcutManager();
this.command_shortcuts.add_shortcuts(default_common_shortcuts);
this.command_shortcuts.add_shortcuts(default_command_shortcuts);
this.edit_shortcuts = new ShortcutManager();
this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
},
'j' : {
help : 'select next cell',
help_index : 'dd',
handler : function (event) {
var index = that.notebook.get_selected_index();
if (index !== (that.notebook.ncells()-1) && index !== null) {
that.notebook.select_next();
that.notebook.focus_cell();
}
return false;
}
},
'x' : {
help : 'cut cell',
help_index : 'ee',
handler : function (event) {
that.notebook.cut_cell();
return false;
}
},
'c' : {
help : 'copy cell',
help_index : 'ef',
handler : function (event) {
that.notebook.copy_cell();
return false;
}
},
'shift-v' : {
help : 'paste cell above',
help_index : 'eg',
handler : function (event) {
that.notebook.paste_cell_above();
return false;
}
},
'v' : {
help : 'paste cell below',
help_index : 'eh',
handler : function (event) {
that.notebook.paste_cell_below();
return false;
}
},
'd' : {
help : 'delete cell (press twice)',
help_index : 'ej',
count: 2,
handler : function (event) {
that.notebook.delete_cell();
return false;
}
},
'a' : {
help : 'insert cell above',
help_index : 'ec',
handler : function (event) {
that.notebook.insert_cell_above();
that.notebook.select_prev();
that.notebook.focus_cell();
return false;
}
},
'b' : {
help : 'insert cell below',
help_index : 'ed',
handler : function (event) {
that.notebook.insert_cell_below();
that.notebook.select_next();
that.notebook.focus_cell();
return false;
}
},
'y' : {
help : 'to code',
help_index : 'ca',
handler : function (event) {
that.notebook.to_code();
return false;
}
},
'm' : {
help : 'to markdown',
help_index : 'cb',
handler : function (event) {
that.notebook.to_markdown();
return false;
}
},
'r' : {
help : 'to raw',
help_index : 'cc',
handler : function (event) {
that.notebook.to_raw();
return false;
}
},
'1' : {
help : 'to heading 1',
help_index : 'cd',
handler : function (event) {
that.notebook.to_heading(undefined, 1);
return false;
}
},
'2' : {
help : 'to heading 2',
help_index : 'ce',
handler : function (event) {
that.notebook.to_heading(undefined, 2);
return false;
}
},
'3' : {
help : 'to heading 3',
help_index : 'cf',
handler : function (event) {
that.notebook.to_heading(undefined, 3);
return false;
}
},
'4' : {
help : 'to heading 4',
help_index : 'cg',
handler : function (event) {
that.notebook.to_heading(undefined, 4);
return false;
}
},
'5' : {
help : 'to heading 5',
help_index : 'ch',
handler : function (event) {
that.notebook.to_heading(undefined, 5);
return false;
}
},
'6' : {
help : 'to heading 6',
help_index : 'ci',
handler : function (event) {
that.notebook.to_heading(undefined, 6);
return false;
}
},
'o' : {
help : 'toggle output',
help_index : 'gb',
handler : function (event) {
that.notebook.toggle_output();
return false;
}
},
'shift-o' : {
help : 'toggle output scrolling',
help_index : 'gc',
handler : function (event) {
that.notebook.toggle_output_scroll();
return false;
}
},
's' : {
help : 'save notebook',
help_index : 'fa',
handler : function (event) {
that.notebook.save_checkpoint();
return false;
}
},
'ctrl-j' : {
help : 'move cell down',
help_index : 'eb',
handler : function (event) {
that.notebook.move_cell_down();
return false;
}
},
'ctrl-k' : {
help : 'move cell up',
help_index : 'ea',
handler : function (event) {
that.notebook.move_cell_up();
return false;
}
},
'l' : {
help : 'toggle line numbers',
help_index : 'ga',
handler : function (event) {
that.notebook.cell_toggle_line_numbers();
return false;
}
},
'i' : {
help : 'interrupt kernel (press twice)',
help_index : 'ha',
count: 2,
handler : function (event) {
that.notebook.kernel.interrupt();
return false;
}
},
'0' : {
help : 'restart kernel (press twice)',
help_index : 'hb',
count: 2,
handler : function (event) {
that.notebook.restart_kernel();
return false;
}
},
'h' : {
help : 'keyboard shortcuts',
help_index : 'ge',
handler : function (event) {
that.quick_help.show_keyboard_shortcuts();
return false;
}
},
'z' : {
help : 'undo last delete',
help_index : 'ei',
handler : function (event) {
that.notebook.undelete_cell();
return false;
}
},
'shift-m' : {
help : 'merge cell below',
help_index : 'ek',
handler : function (event) {
that.notebook.merge_cell_below();
return false;
}
},
'q' : {
help : 'close pager',
help_index : 'gd',
handler : function (event) {
that.pager.collapse();
return false;
}
},
};
};
KeyboardManager.prototype.bind_events = function () {
@ -473,7 +481,7 @@ var IPython = (function (IPython) {
};
KeyboardManager.prototype.handle_keydown = function (event) {
var notebook = IPython.notebook;
var notebook = this.notebook;
if (event.which === keycodes.esc) {
// Intercept escape at highest level to avoid closing
@ -545,18 +553,14 @@ var IPython = (function (IPython) {
// is_focused must be used to check for the case where an element within
// the element being removed is focused.
e.on('remove', function () {
if (IPython.utils.is_focused(e[0])) {
if (utils.is_focused(e[0])) {
that.enable();
}
});
};
IPython.default_common_shortcuts = default_common_shortcuts;
IPython.default_edit_shortcuts = default_edit_shortcuts;
IPython.default_command_shortcuts = default_command_shortcuts;
// For backwards compatability.
IPython.KeyboardManager = KeyboardManager;
return IPython;
}(IPython));
return {'KeyboardManager': KeyboardManager};
});

@ -1,78 +1,96 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// On document ready
//============================================================================
// for the time beeing, we have to pass marked as a parameter here,
// as injecting require.js make marked not to put itself in the globals,
// which make both this file fail at setting marked configuration, and textcell.js
// which search marked into global.
require(['components/marked/lib/marked',
'widgets/js/init',
'components/bootstrap-tour/build/js/bootstrap-tour.min'],
function (marked) {
require([
'base/js/namespace',
'jquery',
'notebook/js/notebook',
'base/js/utils',
'base/js/page',
'notebook/js/layoutmanager',
'base/js/events',
'auth/js/loginwidget',
'notebook/js/maintoolbar',
'notebook/js/pager',
'notebook/js/quickhelp',
'notebook/js/menubar',
'notebook/js/notificationarea',
'notebook/js/savewidget',
'notebook/js/keyboardmanager',
'notebook/js/config',
'notebook/js/kernelselector',
// only loaded, not used:
'custom/custom',
], function(
IPython,
$,
notebook,
utils,
page,
layoutmanager,
events,
loginwidget,
maintoolbar,
pager,
quickhelp,
menubar,
notificationarea,
savewidget,
keyboardmanager,
config,
kernelselector
) {
"use strict";
window.marked = marked;
// monkey patch CM to be able to syntax highlight cell magics
// bug reported upstream,
// see https://github.com/marijnh/CodeMirror2/issues/670
if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
console.log('patching CM for undefined indent');
CodeMirror.modes.null = function() {
return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
};
}
CodeMirror.patchedGetMode = function(config, mode){
var cmmode = CodeMirror.getMode(config, mode);
if(cmmode.indent === null) {
console.log('patch mode "' , mode, '" on the fly');
cmmode.indent = function(){return 0;};
}
return cmmode;
};
// end monkey patching CodeMirror
IPython.mathjaxutils.init();
$('#ipython-main-app').addClass('border-box-sizing');
$('div#notebook_panel').addClass('border-box-sizing');
var opts = {
base_url : IPython.utils.get_body_data("baseUrl"),
notebook_path : IPython.utils.get_body_data("notebookPath"),
notebook_name : IPython.utils.get_body_data('notebookName')
var common_options = {
base_url : utils.get_body_data("baseUrl"),
ws_url : IPython.utils.get_body_data("wsUrl"),
notebook_path : utils.get_body_data("notebookPath"),
notebook_name : utils.get_body_data('notebookName')
};
IPython.page = new IPython.Page();
IPython.layout_manager = new IPython.LayoutManager();
IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
IPython.quick_help = new IPython.QuickHelp();
try {
IPython.tour = new IPython.NotebookTour();
} catch (e) {
console.log("Failed to instantiate Notebook Tour", e);
}
IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
IPython.notebook = new IPython.Notebook('div#notebook', opts);
IPython.keyboard_manager = new IPython.KeyboardManager();
IPython.save_widget = new IPython.SaveWidget('span#save_widget');
IPython.menubar = new IPython.MenuBar('#menubar', opts);
IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
IPython.tooltip = new IPython.Tooltip();
IPython.notification_area = new IPython.NotificationArea('#notification_area');
IPython.notification_area.init_notification_widgets();
IPython.layout_manager.do_resize();
var user_config = $.extend({}, config.default_config);
var page = new page.Page();
var layout_manager = new layoutmanager.LayoutManager();
var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
layout_manager: layout_manager,
events: events});
var keyboard_manager = new keyboardmanager.KeyboardManager({
pager: pager,
events: events});
var save_widget = new savewidget.SaveWidget('span#save_widget', {
events: events,
keyboard_manager: keyboard_manager});
var notebook = new notebook.Notebook('div#notebook', $.extend({
events: events,
keyboard_manager: keyboard_manager,
save_widget: save_widget,
config: user_config},
common_options));
var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
notebook: notebook,
events: events});
var quick_help = new quickhelp.QuickHelp({
keyboard_manager: keyboard_manager,
events: events,
notebook: notebook});
var menubar = new menubar.MenuBar('#menubar', $.extend({
notebook: notebook,
layout_manager: layout_manager,
events: events,
save_widget: save_widget,
quick_help: quick_help},
common_options));
var notification_area = new notificationarea.NotificationArea(
'#notification_area', {
events: events,
save_widget: save_widget,
notebook: notebook,
keyboard_manager: keyboard_manager});
notification_area.init_notification_widgets();
var kernel_selector = new kernelselector.KernelSelector(
'#kernel_selector_widget', notebook);
$('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
'<span id="test2" style="font-weight: bold;">x</span>'+
@ -85,43 +103,37 @@ function (marked) {
}
$('#fonttest').remove();
IPython.page.show();
page.show();
IPython.layout_manager.do_resize();
layout_manager.do_resize();
var first_load = function () {
IPython.layout_manager.do_resize();
layout_manager.do_resize();
var hash = document.location.hash;
if (hash) {
document.location.hash = '';
document.location.hash = hash;
}
IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
notebook.set_autosave_interval(notebook.minimum_autosave_interval);
// only do this once
$([IPython.events]).off('notebook_loaded.Notebook', first_load);
events.off('notebook_loaded.Notebook', first_load);
};
events.on('notebook_loaded.Notebook', first_load);
$([IPython.events]).on('notebook_loaded.Notebook', first_load);
$([IPython.events]).trigger('app_initialized.NotebookApp');
IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
IPython.page = page;
IPython.layout_manager = layout_manager;
IPython.notebook = notebook;
IPython.pager = pager;
IPython.quick_help = quick_help;
IPython.login_widget = login_widget;
IPython.menubar = menubar;
IPython.toolbar = toolbar;
IPython.notification_area = notification_area;
IPython.keyboard_manager = keyboard_manager;
IPython.save_widget = save_widget;
IPython.config = user_config;
IPython.tooltip = notebook.tooltip;
events.trigger('app_initialized.NotebookApp');
notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
if (marked) {
marked.setOptions({
gfm : true,
tables: true,
langPrefix: "language-",
highlight: function(code, lang) {
if (!lang) {
// no language, no highlight
return code;
}
var highlighted;
try {
highlighted = hljs.highlight(lang, code, false);
} catch(err) {
highlighted = hljs.highlightAuto(code);
}
return highlighted.value;
}
});
}
});

@ -1,35 +1,43 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// ToolBar
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'notebook/js/toolbar',
'notebook/js/celltoolbar',
], function(IPython, $, toolbar, celltoolbar) {
"use strict";
var MainToolBar = function (selector) {
IPython.ToolBar.apply(this, arguments);
var MainToolBar = function (selector, options) {
// Constructor
//
// Parameters:
// selector: string
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// notebook: Notebook instance
toolbar.ToolBar.apply(this, arguments);
this.events = options.events;
this.notebook = options.notebook;
this.construct();
this.add_celltype_list();
this.add_celltoolbar_list();
this.bind_events();
};
MainToolBar.prototype = new IPython.ToolBar();
MainToolBar.prototype = new toolbar.ToolBar();
MainToolBar.prototype.construct = function () {
var that = this;
this.add_buttons_group([
{
id : 'save_b',
label : 'Save and Checkpoint',
icon : 'icon-save',
icon : 'fa-save',
callback : function () {
IPython.notebook.save_checkpoint();
that.notebook.save_checkpoint();
}
}
]);
@ -38,11 +46,11 @@ var IPython = (function (IPython) {
{
id : 'insert_below_b',
label : 'Insert Cell Below',
icon : 'icon-plus-sign',
icon : 'fa-plus',
callback : function () {
IPython.notebook.insert_cell_below('code');
IPython.notebook.select_next();
IPython.notebook.focus_cell();
that.notebook.insert_cell_below('code');
that.notebook.select_next();
that.notebook.focus_cell();
}
}
],'insert_above_below');
@ -51,25 +59,25 @@ var IPython = (function (IPython) {
{
id : 'cut_b',
label : 'Cut Cell',
icon : 'icon-cut',
icon : 'fa-cut',
callback : function () {
IPython.notebook.cut_cell();
that.notebook.cut_cell();
}
},
{
id : 'copy_b',
label : 'Copy Cell',
icon : 'icon-copy',
icon : 'fa-copy',
callback : function () {
IPython.notebook.copy_cell();
that.notebook.copy_cell();
}
},
{
id : 'paste_b',
label : 'Paste Cell Below',
icon : 'icon-paste',
icon : 'fa-paste',
callback : function () {
IPython.notebook.paste_cell_below();
that.notebook.paste_cell_below();
}
}
],'cut_copy_paste');
@ -78,17 +86,17 @@ var IPython = (function (IPython) {
{
id : 'move_up_b',
label : 'Move Cell Up',
icon : 'icon-arrow-up',
icon : 'fa-arrow-up',
callback : function () {
IPython.notebook.move_cell_up();
that.notebook.move_cell_up();
}
},
{
id : 'move_down_b',
label : 'Move Cell Down',
icon : 'icon-arrow-down',
icon : 'fa-arrow-down',
callback : function () {
IPython.notebook.move_cell_down();
that.notebook.move_cell_down();
}
}
],'move_up_down');
@ -98,26 +106,26 @@ var IPython = (function (IPython) {
{
id : 'run_b',
label : 'Run Cell',
icon : 'icon-play',
icon : 'fa-play',
callback : function () {
// emulate default shift-enter behavior
IPython.notebook.execute_cell_and_select_below();
that.notebook.execute_cell_and_select_below();
}
},
{
id : 'interrupt_b',
label : 'Interrupt',
icon : 'icon-stop',
icon : 'fa-stop',
callback : function () {
IPython.notebook.session.interrupt_kernel();
that.notebook.session.interrupt_kernel();
}
},
{
id : 'repeat_b',
label : 'Restart Kernel',
icon : 'icon-repeat',
icon : 'fa-repeat',
callback : function () {
IPython.notebook.restart_kernel();
that.notebook.restart_kernel();
}
}
],'run_int');
@ -150,30 +158,31 @@ var IPython = (function (IPython) {
.addClass('form-control select-xs')
.append($('<option/>').attr('value', '').text('None'));
this.element.append(label).append(select);
var that = this;
select.change(function() {
var val = $(this).val()
if (val =='') {
IPython.CellToolbar.global_hide();
delete IPython.notebook.metadata.celltoolbar;
var val = $(this).val();
if (val ==='') {
celltoolbar.CellToolbar.global_hide();
delete that.notebook.metadata.celltoolbar;
} else {
IPython.CellToolbar.global_show();
IPython.CellToolbar.activate_preset(val);
IPython.notebook.metadata.celltoolbar = val;
celltoolbar.CellToolbar.global_show();
celltoolbar.CellToolbar.activate_preset(val, that.events);
that.notebook.metadata.celltoolbar = val;
}
});
// Setup the currently registered presets.
var presets = IPython.CellToolbar.list_presets();
var presets = celltoolbar.CellToolbar.list_presets();
for (var i=0; i<presets.length; i++) {
var name = presets[i];
select.append($('<option/>').attr('value', name).text(name));
}
// Setup future preset registrations.
$([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
this.events.on('preset_added.CellToolbar', function (event, data) {
var name = data.name;
select.append($('<option/>').attr('value', name).text(name));
});
// Update select value when a preset is activated.
$([IPython.events]).on('preset_activated.CellToolbar', function (event, data) {
this.events.on('preset_activated.CellToolbar', function (event, data) {
if (select.val() !== data.name)
select.val(data.name);
});
@ -186,26 +195,26 @@ var IPython = (function (IPython) {
this.element.find('#cell_type').change(function () {
var cell_type = $(this).val();
if (cell_type === 'code') {
IPython.notebook.to_code();
that.notebook.to_code();
} else if (cell_type === 'markdown') {
IPython.notebook.to_markdown();
that.notebook.to_markdown();
} else if (cell_type === 'raw') {
IPython.notebook.to_raw();
that.notebook.to_raw();
} else if (cell_type === 'heading1') {
IPython.notebook.to_heading(undefined, 1);
that.notebook.to_heading(undefined, 1);
} else if (cell_type === 'heading2') {
IPython.notebook.to_heading(undefined, 2);
that.notebook.to_heading(undefined, 2);
} else if (cell_type === 'heading3') {
IPython.notebook.to_heading(undefined, 3);
that.notebook.to_heading(undefined, 3);
} else if (cell_type === 'heading4') {
IPython.notebook.to_heading(undefined, 4);
that.notebook.to_heading(undefined, 4);
} else if (cell_type === 'heading5') {
IPython.notebook.to_heading(undefined, 5);
that.notebook.to_heading(undefined, 5);
} else if (cell_type === 'heading6') {
IPython.notebook.to_heading(undefined, 6);
that.notebook.to_heading(undefined, 6);
}
});
$([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
if (data.cell_type === 'heading') {
that.element.find('#cell_type').val(data.cell_type+data.level);
} else {
@ -214,8 +223,8 @@ var IPython = (function (IPython) {
});
};
// Backwards compatability.
IPython.MainToolBar = MainToolBar;
return IPython;
}(IPython));
return {'MainToolBar': MainToolBar};
});

@ -1,18 +1,12 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// MathJax utility functions
//============================================================================
IPython.namespace('IPython.mathjaxutils');
IPython.mathjaxutils = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/dialog',
], function(IPython, $, utils, dialog) {
"use strict";
var init = function () {
@ -75,7 +69,7 @@ IPython.mathjaxutils = (function (IPython) {
"which will prevent this dialog from appearing."
)
);
IPython.dialog.modal({
dialog.modal({
title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
body : message,
buttons : {
@ -110,7 +104,7 @@ IPython.mathjaxutils = (function (IPython) {
.replace(/</g, "&lt;") // use HTML entity for <
.replace(/>/g, "&gt;") // use HTML entity for >
;
if (IPython.utils.browser === 'msie') {
if (utils.browser === 'msie') {
block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
}
while (j > i) {
@ -159,7 +153,7 @@ IPython.mathjaxutils = (function (IPython) {
de_tilde = function (text) { return text; };
}
var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
for (var i = 1, m = blocks.length; i < m; i += 2) {
var block = blocks[i];
@ -242,10 +236,13 @@ IPython.mathjaxutils = (function (IPython) {
return text;
};
return {
var mathjaxutils = {
init : init,
remove_math : remove_math,
replace_math : replace_math
};
}(IPython));
IPython.mathjaxutils = mathjaxutils;
return mathjaxutils;
});

@ -1,40 +1,49 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// MenuBar
//============================================================================
/**
* @module IPython
* @namespace IPython
* @submodule MenuBar
*/
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'notebook/js/tour',
'bootstrap',
'moment',
], function(IPython, $, utils, tour, bootstrap, moment) {
"use strict";
var utils = IPython.utils;
/**
* A MenuBar Class to generate the menubar of IPython notebook
* @Class MenuBar
*
* @constructor
*
*
* @param selector {string} selector for the menubar element in DOM
* @param {object} [options]
* @param [options.base_url] {String} String to use for the
* base project url. Default is to inspect
* $('body').data('baseUrl');
* does not support change for now is set through this option
*/
var MenuBar = function (selector, options) {
// Constructor
//
// A MenuBar Class to generate the menubar of IPython notebook
//
// Parameters:
// selector: string
// options: dictionary
// Dictionary of keyword arguments.
// notebook: Notebook instance
// layout_manager: LayoutManager instance
// events: $(Events) instance
// save_widget: SaveWidget instance
// quick_help: QuickHelp instance
// base_url : string
// notebook_path : string
// notebook_name : string
options = options || {};
this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.selector = selector;
this.notebook = options.notebook;
this.layout_manager = options.layout_manager;
this.events = options.events;
this.save_widget = options.save_widget;
this.quick_help = options.quick_help;
try {
this.tour = new tour.Tour(this.notebook, this.events);
} catch (e) {
this.tour = undefined;
console.log("Failed to instantiate Notebook Tour", e);
}
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
@ -42,23 +51,24 @@ var IPython = (function (IPython) {
}
};
// TODO: This has definitively nothing to do with style ...
MenuBar.prototype.style = function () {
this.element.addClass('border-box-sizing');
var that = this;
this.element.find("li").click(function (event, ui) {
// The selected cell loses focus when the menu is entered, so we
// re-select it upon selection.
var i = IPython.notebook.get_selected_index();
IPython.notebook.select(i);
var i = that.notebook.get_selected_index();
that.notebook.select(i);
}
);
};
MenuBar.prototype._nbconvert = function (format, download) {
download = download || false;
var notebook_path = IPython.notebook.notebook_path;
var notebook_name = IPython.notebook.notebook_name;
if (IPython.notebook.dirty) {
IPython.notebook.save_notebook({async : false});
var notebook_path = this.notebook.notebook_path;
var notebook_name = this.notebook.notebook_name;
if (this.notebook.dirty) {
this.notebook.save_notebook({async : false});
}
var url = utils.url_join_encode(
this.base_url,
@ -75,25 +85,25 @@ var IPython = (function (IPython) {
// File
var that = this;
this.element.find('#new_notebook').click(function () {
IPython.notebook.new_notebook();
that.notebook.new_notebook();
});
this.element.find('#open_notebook').click(function () {
window.open(utils.url_join_encode(
IPython.notebook.base_url,
that.notebook.base_url,
'tree',
IPython.notebook.notebook_path
that.notebook.notebook_path
));
});
this.element.find('#copy_notebook').click(function () {
IPython.notebook.copy_notebook();
that.notebook.copy_notebook();
return false;
});
this.element.find('#download_ipynb').click(function () {
var base_url = IPython.notebook.base_url;
var notebook_path = IPython.notebook.notebook_path;
var notebook_name = IPython.notebook.notebook_name;
if (IPython.notebook.dirty) {
IPython.notebook.save_notebook({async : false});
var base_url = that.notebook.base_url;
var notebook_path = that.notebook.notebook_path;
var notebook_name = that.notebook.notebook_name;
if (that.notebook.dirty) {
that.notebook.save_notebook({async : false});
}
var url = utils.url_join_encode(
@ -126,17 +136,17 @@ var IPython = (function (IPython) {
});
this.element.find('#rename_notebook').click(function () {
IPython.save_widget.rename_notebook();
that.save_widget.rename_notebook({notebook: that.notebook});
});
this.element.find('#save_checkpoint').click(function () {
IPython.notebook.save_checkpoint();
that.notebook.save_checkpoint();
});
this.element.find('#restore_checkpoint').click(function () {
});
this.element.find('#trust_notebook').click(function () {
IPython.notebook.trust_notebook();
that.notebook.trust_notebook();
});
$([IPython.events]).on('trust_changed.Notebook', function (event, trusted) {
this.events.on('trust_changed.Notebook', function (event, trusted) {
if (trusted) {
that.element.find('#trust_notebook')
.addClass("disabled")
@ -148,157 +158,160 @@ var IPython = (function (IPython) {
}
});
this.element.find('#kill_and_exit').click(function () {
IPython.notebook.session.delete();
setTimeout(function(){
var close_window = function () {
// allow closing of new tabs in Chromium, impossible in FF
window.open('', '_self', '');
window.close();
}, 500);
};
// finish with close on success or failure
that.notebook.session.delete(close_window, close_window);
});
// Edit
this.element.find('#cut_cell').click(function () {
IPython.notebook.cut_cell();
that.notebook.cut_cell();
});
this.element.find('#copy_cell').click(function () {
IPython.notebook.copy_cell();
that.notebook.copy_cell();
});
this.element.find('#delete_cell').click(function () {
IPython.notebook.delete_cell();
that.notebook.delete_cell();
});
this.element.find('#undelete_cell').click(function () {
IPython.notebook.undelete_cell();
that.notebook.undelete_cell();
});
this.element.find('#split_cell').click(function () {
IPython.notebook.split_cell();
that.notebook.split_cell();
});
this.element.find('#merge_cell_above').click(function () {
IPython.notebook.merge_cell_above();
that.notebook.merge_cell_above();
});
this.element.find('#merge_cell_below').click(function () {
IPython.notebook.merge_cell_below();
that.notebook.merge_cell_below();
});
this.element.find('#move_cell_up').click(function () {
IPython.notebook.move_cell_up();
that.notebook.move_cell_up();
});
this.element.find('#move_cell_down').click(function () {
IPython.notebook.move_cell_down();
that.notebook.move_cell_down();
});
this.element.find('#edit_nb_metadata').click(function () {
IPython.notebook.edit_metadata();
that.notebook.edit_metadata({
notebook: that.notebook,
keyboard_manager: that.notebook.keyboard_manager});
});
// View
this.element.find('#toggle_header').click(function () {
$('div#header').toggle();
IPython.layout_manager.do_resize();
that.layout_manager.do_resize();
});
this.element.find('#toggle_toolbar').click(function () {
$('div#maintoolbar').toggle();
IPython.layout_manager.do_resize();
that.layout_manager.do_resize();
});
// Insert
this.element.find('#insert_cell_above').click(function () {
IPython.notebook.insert_cell_above('code');
IPython.notebook.select_prev();
that.notebook.insert_cell_above('code');
that.notebook.select_prev();
});
this.element.find('#insert_cell_below').click(function () {
IPython.notebook.insert_cell_below('code');
IPython.notebook.select_next();
that.notebook.insert_cell_below('code');
that.notebook.select_next();
});
// Cell
this.element.find('#run_cell').click(function () {
IPython.notebook.execute_cell();
that.notebook.execute_cell();
});
this.element.find('#run_cell_select_below').click(function () {
IPython.notebook.execute_cell_and_select_below();
that.notebook.execute_cell_and_select_below();
});
this.element.find('#run_cell_insert_below').click(function () {
IPython.notebook.execute_cell_and_insert_below();
that.notebook.execute_cell_and_insert_below();
});
this.element.find('#run_all_cells').click(function () {
IPython.notebook.execute_all_cells();
that.notebook.execute_all_cells();
});
this.element.find('#run_all_cells_above').click(function () {
IPython.notebook.execute_cells_above();
that.notebook.execute_cells_above();
});
this.element.find('#run_all_cells_below').click(function () {
IPython.notebook.execute_cells_below();
that.notebook.execute_cells_below();
});
this.element.find('#to_code').click(function () {
IPython.notebook.to_code();
that.notebook.to_code();
});
this.element.find('#to_markdown').click(function () {
IPython.notebook.to_markdown();
that.notebook.to_markdown();
});
this.element.find('#to_raw').click(function () {
IPython.notebook.to_raw();
that.notebook.to_raw();
});
this.element.find('#to_heading1').click(function () {
IPython.notebook.to_heading(undefined, 1);
that.notebook.to_heading(undefined, 1);
});
this.element.find('#to_heading2').click(function () {
IPython.notebook.to_heading(undefined, 2);
that.notebook.to_heading(undefined, 2);
});
this.element.find('#to_heading3').click(function () {
IPython.notebook.to_heading(undefined, 3);
that.notebook.to_heading(undefined, 3);
});
this.element.find('#to_heading4').click(function () {
IPython.notebook.to_heading(undefined, 4);
that.notebook.to_heading(undefined, 4);
});
this.element.find('#to_heading5').click(function () {
IPython.notebook.to_heading(undefined, 5);
that.notebook.to_heading(undefined, 5);
});
this.element.find('#to_heading6').click(function () {
IPython.notebook.to_heading(undefined, 6);
that.notebook.to_heading(undefined, 6);
});
this.element.find('#toggle_current_output').click(function () {
IPython.notebook.toggle_output();
that.notebook.toggle_output();
});
this.element.find('#toggle_current_output_scroll').click(function () {
IPython.notebook.toggle_output_scroll();
that.notebook.toggle_output_scroll();
});
this.element.find('#clear_current_output').click(function () {
IPython.notebook.clear_output();
that.notebook.clear_output();
});
this.element.find('#toggle_all_output').click(function () {
IPython.notebook.toggle_all_output();
that.notebook.toggle_all_output();
});
this.element.find('#toggle_all_output_scroll').click(function () {
IPython.notebook.toggle_all_output_scroll();
that.notebook.toggle_all_output_scroll();
});
this.element.find('#clear_all_output').click(function () {
IPython.notebook.clear_all_output();
that.notebook.clear_all_output();
});
// Kernel
this.element.find('#int_kernel').click(function () {
IPython.notebook.session.interrupt_kernel();
that.notebook.session.interrupt_kernel();
});
this.element.find('#restart_kernel').click(function () {
IPython.notebook.restart_kernel();
that.notebook.restart_kernel();
});
// Help
if (IPython.tour) {
if (this.tour) {
this.element.find('#notebook_tour').click(function () {
IPython.tour.start();
that.tour.start();
});
} else {
this.element.find('#notebook_tour').addClass("disabled");
}
this.element.find('#keyboard_shortcuts').click(function () {
IPython.quick_help.show_keyboard_shortcuts();
that.quick_help.show_keyboard_shortcuts();
});
this.update_restore_checkpoint(null);
$([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
that.update_restore_checkpoint(IPython.notebook.checkpoints);
this.events.on('checkpoints_listed.Notebook', function (event, data) {
that.update_restore_checkpoint(that.notebook.checkpoints);
});
$([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
that.update_restore_checkpoint(IPython.notebook.checkpoints);
this.events.on('checkpoint_created.Notebook', function (event, data) {
that.update_restore_checkpoint(that.notebook.checkpoints);
});
};
@ -317,23 +330,24 @@ var IPython = (function (IPython) {
return;
}
var that = this;
checkpoints.map(function (checkpoint) {
var d = new Date(checkpoint.last_modified);
ul.append(
$("<li/>").append(
$("<a/>")
.attr("href", "#")
.text(d.format("mmm dd HH:MM:ss"))
.text(moment(d).format("LLLL"))
.click(function () {
IPython.notebook.restore_checkpoint_dialog(checkpoint);
that.notebook.restore_checkpoint_dialog(checkpoint);
})
)
);
});
};
// Backwards compatability.
IPython.MenuBar = MenuBar;
return IPython;
}(IPython));
return {'MenuBar': MenuBar};
});

File diff suppressed because it is too large Load Diff

@ -1,21 +1,31 @@
//----------------------------------------------------------------------------
// Copyright (C) 2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Notification widget
//============================================================================
var IPython = (function (IPython) {
"use strict";
var utils = IPython.utils;
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/dialog',
'notebook/js/notificationwidget',
'moment'
], function(IPython, $, utils, dialog, notificationwidget, moment) {
"use strict";
var NotificationArea = function (selector) {
var NotificationArea = function (selector, options) {
// Constructor
//
// Parameters:
// selector: string
// options: dictionary
// Dictionary of keyword arguments.
// notebook: Notebook instance
// events: $(Events) instance
// save_widget: SaveWidget instance
this.selector = selector;
this.events = options.events;
this.save_widget = options.save_widget;
this.notebook = options.notebook;
this.keyboard_manager = options.keyboard_manager;
if (this.selector !== undefined) {
this.element = $(selector);
}
@ -23,13 +33,10 @@ var IPython = (function (IPython) {
};
NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
var uuid = utils.uuid();
if( css_class == 'danger') {css_class = 'ui-state-error';}
if( css_class == 'warning') {css_class = 'ui-state-highlight';}
var tdiv = $('<div>')
.attr('id',uuid)
.addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
.addClass('border-box-sizing')
.addClass('notification_widget')
.addClass(css_class)
.hide()
.text(msg);
@ -63,46 +70,53 @@ var IPython = (function (IPython) {
}
var div = $('<div/>').attr('id','notification_'+name);
$(this.selector).append(div);
this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
return this.widget_dict[name];
};
NotificationArea.prototype.init_notification_widgets = function() {
var that = this;
var knw = this.new_notification_widget('kernel');
var $kernel_ind_icon = $("#kernel_indicator_icon");
var $modal_ind_icon = $("#modal_indicator_icon");
// Command/Edit mode
$([IPython.events]).on('edit_mode.Notebook',function () {
IPython.save_widget.update_document_title();
this.events.on('edit_mode.Notebook',function () {
that.save_widget.update_document_title();
$modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
});
$([IPython.events]).on('command_mode.Notebook',function () {
IPython.save_widget.update_document_title();
this.events.on('command_mode.Notebook',function () {
that.save_widget.update_document_title();
$modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
});
// Implicitly start off in Command mode, switching to Edit mode will trigger event
$modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
$modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
// Kernel events
$([IPython.events]).on('status_idle.Kernel',function () {
IPython.save_widget.update_document_title();
this.events.on('status_idle.Kernel',function () {
that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
});
$([IPython.events]).on('status_busy.Kernel',function () {
this.events.on('status_busy.Kernel',function () {
window.document.title='(Busy) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
});
$([IPython.events]).on('status_restarting.Kernel',function () {
IPython.save_widget.update_document_title();
this.events.on('status_restarting.Kernel',function () {
that.save_widget.update_document_title();
knw.set_message("Restarting kernel", 2000);
});
$([IPython.events]).on('status_interrupting.Kernel',function () {
this.events.on('status_dead.Kernel',function () {
that.save_widget.update_document_title();
knw.danger("Dead kernel");
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
});
this.events.on('status_interrupting.Kernel',function () {
knw.set_message("Interrupting kernel", 2000);
});
@ -110,28 +124,32 @@ var IPython = (function (IPython) {
// When the kernel_info reply arrives, the kernel is idle.
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
$([IPython.events]).on('status_started.Kernel', function (evt, data) {
this.events.on('status_started.Kernel', function (evt, data) {
knw.info("Websockets Connected", 500);
that.events.trigger('status_busy.Kernel');
data.kernel.kernel_info(function () {
$([IPython.events]).trigger('status_idle.Kernel');
that.events.trigger('status_idle.Kernel');
});
});
$([IPython.events]).on('status_dead.Kernel',function () {
this.events.on('status_dead.Kernel',function () {
var msg = 'The kernel has died, and the automatic restart has failed.' +
' It is possible the kernel cannot be restarted.' +
' If you are not able to restart the kernel, you will still be able to save' +
' the notebook, but running code will no longer work until the notebook' +
' is reopened.';
IPython.dialog.modal({
dialog.modal({
title: "Dead kernel",
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"Manual Restart": {
class: "btn-danger",
click: function () {
$([IPython.events]).trigger('status_restarting.Kernel');
IPython.notebook.start_kernel();
that.events.trigger('status_restarting.Kernel');
that.notebook.start_kernel();
}
},
"Don't restart": {}
@ -139,13 +157,18 @@ var IPython = (function (IPython) {
});
});
$([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
this.events.on('websocket_closed.Kernel', function (event, data) {
var kernel = data.kernel;
var ws_url = data.ws_url;
var early = data.early;
var msg;
$kernel_ind_icon
.attr('class', 'kernel_disconnected_icon')
.attr('title', 'No Connection to Kernel');
if (!early) {
knw.set_message('Reconnecting WebSockets', 1000);
knw.warning('Reconnecting');
setTimeout(function () {
kernel.start_channels();
}, 5000);
@ -155,14 +178,16 @@ var IPython = (function (IPython) {
msg = "A WebSocket connection could not be established." +
" You will NOT be able to run code. Check your" +
" network connection or notebook server configuration.";
IPython.dialog.modal({
dialog.modal({
title: "WebSocket connection failed",
body: msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"OK": {},
"Reconnect": {
click: function () {
knw.set_message('Reconnecting WebSockets', 1000);
knw.warning('Reconnecting');
setTimeout(function () {
kernel.start_channels();
}, 5000);
@ -176,52 +201,52 @@ var IPython = (function (IPython) {
var nnw = this.new_notification_widget('notebook');
// Notebook events
$([IPython.events]).on('notebook_loading.Notebook', function () {
this.events.on('notebook_loading.Notebook', function () {
nnw.set_message("Loading notebook",500);
});
$([IPython.events]).on('notebook_loaded.Notebook', function () {
this.events.on('notebook_loaded.Notebook', function () {
nnw.set_message("Notebook loaded",500);
});
$([IPython.events]).on('notebook_saving.Notebook', function () {
this.events.on('notebook_saving.Notebook', function () {
nnw.set_message("Saving notebook",500);
});
$([IPython.events]).on('notebook_saved.Notebook', function () {
this.events.on('notebook_saved.Notebook', function () {
nnw.set_message("Notebook saved",2000);
});
$([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
nnw.set_message(data || "Notebook save failed");
this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
nnw.warning(data || "Notebook save failed");
});
// Checkpoint events
$([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
this.events.on('checkpoint_created.Notebook', function (evt, data) {
var msg = "Checkpoint created";
if (data.last_modified) {
var d = new Date(data.last_modified);
msg = msg + ": " + d.format("HH:MM:ss");
msg = msg + ": " + moment(d).format("HH:mm:ss");
}
nnw.set_message(msg, 2000);
});
$([IPython.events]).on('checkpoint_failed.Notebook', function () {
nnw.set_message("Checkpoint failed");
this.events.on('checkpoint_failed.Notebook', function () {
nnw.warning("Checkpoint failed");
});
$([IPython.events]).on('checkpoint_deleted.Notebook', function () {
this.events.on('checkpoint_deleted.Notebook', function () {
nnw.set_message("Checkpoint deleted", 500);
});
$([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
nnw.set_message("Checkpoint delete failed");
this.events.on('checkpoint_delete_failed.Notebook', function () {
nnw.warning("Checkpoint delete failed");
});
$([IPython.events]).on('checkpoint_restoring.Notebook', function () {
this.events.on('checkpoint_restoring.Notebook', function () {
nnw.set_message("Restoring to checkpoint...", 500);
});
$([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
nnw.set_message("Checkpoint restore failed");
this.events.on('checkpoint_restore_failed.Notebook', function () {
nnw.warning("Checkpoint restore failed");
});
// Autosave events
$([IPython.events]).on('autosave_disabled.Notebook', function () {
this.events.on('autosave_disabled.Notebook', function () {
nnw.set_message("Autosave disabled", 2000);
});
$([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
this.events.on('autosave_enabled.Notebook', function (evt, interval) {
nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
});
@ -229,7 +254,5 @@ var IPython = (function (IPython) {
IPython.NotificationArea = NotificationArea;
return IPython;
}(IPython));
return {'NotificationArea': NotificationArea};
});

@ -1,18 +1,11 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Notification widget
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
], function(IPython, $) {
"use strict";
var utils = IPython.utils;
var NotificationWidget = function (selector) {
this.selector = selector;
@ -22,7 +15,6 @@ var IPython = (function (IPython) {
this.element = $(selector);
this.style();
}
this.element.button();
this.element.hide();
var that = this;
@ -31,10 +23,8 @@ var IPython = (function (IPython) {
};
NotificationWidget.prototype.style = function () {
this.element.addClass('notification_widget pull-right');
this.element.addClass('border-box-sizing');
this.element.addClass('notification_widget');
};
// msg : message to display
@ -43,14 +33,24 @@ var IPython = (function (IPython) {
// if timeout <= 0
// click_callback : function called if user click on notification
// could return false to prevent the notification to be dismissed
NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
var opts = opts || {};
var callback = click_callback || function() {return false;};
NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
var options = options || {};
var callback = click_callback || function() {return true;};
var that = this;
this.inner.attr('class', opts.icon);
this.inner.attr('title', opts.title);
// unbind potential previous callback
this.element.unbind('click');
this.inner.attr('class', options.icon);
this.inner.attr('title', options.title);
this.inner.text(msg);
this.element.fadeIn(100);
// reset previous set style
this.element.removeClass();
this.style();
if (options.class){
this.element.addClass(options.class)
}
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
@ -62,7 +62,7 @@ var IPython = (function (IPython) {
}, timeout);
} else {
this.element.click(function() {
if( callback() != false ) {
if( callback() !== false ) {
that.element.fadeOut(100, function () {that.inner.text('');});
that.element.unbind('click');
}
@ -75,14 +75,30 @@ var IPython = (function (IPython) {
};
NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
var options = options || {};
options.class = options.class +' info';
var timeout = timeout || 3500;
this.set_message(msg, timeout, click_callback, options);
}
NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
var options = options || {};
options.class = options.class +' warning';
this.set_message(msg, timeout, click_callback, options);
}
NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
var options = options || {};
options.class = options.class +' danger';
this.set_message(msg, timeout, click_callback, options);
}
NotificationWidget.prototype.get_message = function () {
return this.inner.html();
};
// For backwards compatibility.
IPython.NotificationWidget = NotificationWidget;
return IPython;
}(IPython));
return {'NotificationWidget': NotificationWidget};
});

@ -1,38 +1,37 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// OutputArea
//============================================================================
/**
* @module IPython
* @namespace IPython
* @submodule OutputArea
*/
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jqueryui',
'base/js/utils',
'base/js/security',
'base/js/keyboard',
'notebook/js/mathjaxutils',
'components/marked/lib/marked',
], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
"use strict";
var utils = IPython.utils;
/**
* @class OutputArea
*
* @constructor
*/
var OutputArea = function (selector, prompt_area) {
this.selector = selector;
this.wrapper = $(selector);
var OutputArea = function (options) {
this.selector = options.selector;
this.events = options.events;
this.keyboard_manager = options.keyboard_manager;
this.wrapper = $(options.selector);
this.outputs = [];
this.collapsed = false;
this.scrolled = false;
this.trusted = true;
this.clear_queued = null;
if (prompt_area === undefined) {
if (options.prompt_area === undefined) {
this.prompt_area = true;
} else {
this.prompt_area = prompt_area;
this.prompt_area = options.prompt_area;
}
this.create_elements();
this.style();
@ -101,7 +100,7 @@ var IPython = (function (IPython) {
this.element.resize(function () {
// FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
if ( IPython.utils.browser[0] === "Firefox" ) {
if ( utils.browser[0] === "Firefox" ) {
return;
}
// maybe scroll output,
@ -282,12 +281,15 @@ var IPython = (function (IPython) {
needs_height_reset = true;
}
var record_output = true;
if (json.output_type === 'execute_result') {
this.append_execute_result(json);
} else if (json.output_type === 'error') {
this.append_error(json);
} else if (json.output_type === 'stream') {
this.append_stream(json);
// append_stream might have merged the output with earlier stream output
record_output = this.append_stream(json);
}
// We must release the animation fixed height in a callback since Gecko
@ -308,7 +310,9 @@ var IPython = (function (IPython) {
handle_appended();
}
this.outputs.push(json);
if (record_output) {
this.outputs.push(json);
}
};
@ -459,20 +463,23 @@ var IPython = (function (IPython) {
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
last.text = utils.fixCarriageReturn(last.text + json.text);
var pre = this.element.find('div.'+subclass).last().find('pre');
var html = utils.fixCarriageReturn(
pre.html() + utils.fixConsole(text));
var html = utils.fixConsole(last.text);
// The only user content injected with this HTML call is
// escaped by the fixConsole() method.
pre.html(html);
return;
// return false signals that we merged this output with the previous one,
// and the new output shouldn't be recorded.
return false;
}
}
if (!text.replace("\r", "")) {
// text is nothing (empty string, \r, etc.)
// so don't append any elements, which might add undesirable space
return;
// return true to indicate the output should be recorded.
return true;
}
// If we got here, attach a new div
@ -482,6 +489,7 @@ var IPython = (function (IPython) {
append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
}
this._safe_append(toinsert);
return true;
};
@ -515,7 +523,7 @@ var IPython = (function (IPython) {
if (!this.trusted && !OutputArea.safe_outputs[type]) {
// not trusted, sanitize HTML
if (type==='text/html' || type==='text/svg') {
value = IPython.security.sanitize_html(value);
value = security.sanitize_html(value);
} else {
// don't display if we don't know how to sanitize it
console.log("Ignoring untrusted " + type + " output.");
@ -531,7 +539,7 @@ var IPython = (function (IPython) {
if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
setTimeout(handle_inserted, 0);
}
$([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
return toinsert;
}
}
@ -542,7 +550,7 @@ var IPython = (function (IPython) {
var append_html = function (html, md, element) {
var type = 'text/html';
var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
IPython.keyboard_manager.register_events(toinsert);
this.keyboard_manager.register_events(toinsert);
toinsert.append(html);
element.append(toinsert);
return toinsert;
@ -552,11 +560,11 @@ var IPython = (function (IPython) {
var append_markdown = function(markdown, md, element) {
var type = 'text/markdown';
var toinsert = this.create_output_subarea(md, "output_markdown", type);
var text_and_math = IPython.mathjaxutils.remove_math(markdown);
var text_and_math = mathjaxutils.remove_math(markdown);
var text = text_and_math[0];
var math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
html = IPython.mathjaxutils.replace_math(html, math);
html = mathjaxutils.replace_math(html, math);
toinsert.append(html);
element.append(toinsert);
return toinsert;
@ -567,13 +575,8 @@ var IPython = (function (IPython) {
// We just eval the JS code, element appears in the local scope.
var type = 'application/javascript';
var toinsert = this.create_output_subarea(md, "output_javascript", type);
IPython.keyboard_manager.register_events(toinsert);
this.keyboard_manager.register_events(toinsert);
element.append(toinsert);
// FIXME TODO : remove `container element for 3.0`
//backward compat, js should be eval'ed in a context where `container` is defined.
var container = element;
container.show = function(){console.log('Warning "container.show()" is deprecated.')};
// end backward compat
// Fix for ipython/issues/5293, make sure `element` is the area which
// output can be inserted into at the time of JS execution.
@ -763,7 +766,7 @@ var IPython = (function (IPython) {
.keydown(function (event, ui) {
// make sure we submit on enter,
// and don't re-execute the *cell* on shift-enter
if (event.which === IPython.keyboard.keycodes.enter) {
if (event.which === keyboard.keycodes.enter) {
that._submit_raw_input();
return false;
}
@ -775,7 +778,7 @@ var IPython = (function (IPython) {
var raw_input = area.find('input.raw_input');
// Register events that enable/disable the keyboard manager while raw
// input is focused.
IPython.keyboard_manager.register_events(raw_input);
this.keyboard_manager.register_events(raw_input);
// Note, the following line used to read raw_input.focus().focus().
// This seemed to be needed otherwise only the cell would be focused.
// But with the modal UI, this seems to work fine with one call to focus().
@ -794,14 +797,14 @@ var IPython = (function (IPython) {
}
var content = {
output_type : 'stream',
name : 'stdout',
stream : 'stdout',
text : theprompt.text() + echo + '\n'
}
// remove form container
container.parent().remove();
// replace with plaintext version in stdout
this.append_output(content, false);
$([IPython.events]).trigger('send_input_reply.Kernel', value);
this.events.trigger('send_input_reply.Kernel', value);
}
@ -992,8 +995,8 @@ var IPython = (function (IPython) {
"application/pdf" : append_pdf
};
// For backwards compatability.
IPython.OutputArea = OutputArea;
return IPython;
}(IPython));
return {'OutputArea': OutputArea};
});

@ -1,20 +1,29 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Pager
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jqueryui',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var utils = IPython.utils;
var Pager = function (pager_selector, pager_splitter_selector) {
var Pager = function (pager_selector, pager_splitter_selector, options) {
// Constructor
//
// Parameters:
// pager_selector: string
// pager_splitter_selector: string
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// layout_manager: LayoutManager instance
this.events = options.events;
this.pager_element = $(pager_selector);
this.pager_button_area = $('#pager_button_area');
var that = this;
this.percentage_height = 0.40;
options.layout_manager.pager = this;
this.pager_splitter_element = $(pager_splitter_selector)
.draggable({
containment: 'window',
@ -23,7 +32,7 @@ var IPython = (function (IPython) {
drag: function(event, ui) {
// recalculate the amount of space the pager should take
var pheight = ($(document.body).height()-event.clientY-4);
var downprct = pheight/IPython.layout_manager.app_height();
var downprct = pheight/options.layout_manager.app_height();
downprct = Math.min(0.9, downprct);
if (downprct < 0.1) {
that.percentage_height = 0.1;
@ -32,7 +41,7 @@ var IPython = (function (IPython) {
that.percentage_height = downprct;
that.expand({'duration':0});
}
IPython.layout_manager.do_resize();
options.layout_manager.do_resize();
}
});
this.expanded = false;
@ -47,28 +56,26 @@ var IPython = (function (IPython) {
$('<a>').attr('role', "button")
.attr('title',"Open the pager in an external window")
.addClass('ui-button')
.click(function(){that.detach()})
.click(function(){that.detach();})
.attr('style','position: absolute; right: 20px;')
.append(
$('<span>').addClass("ui-icon ui-icon-extlink")
)
)
);
this.pager_button_area.append(
$('<a>').attr('role', "button")
.attr('title',"Close the pager")
.addClass('ui-button')
.click(function(){that.collapse()})
.click(function(){that.collapse();})
.attr('style','position: absolute; right: 5px;')
.append(
$('<span>').addClass("ui-icon ui-icon-close")
)
)
);
};
Pager.prototype.style = function () {
this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
this.pager_element.addClass('border-box-sizing');
this.pager_element.find(".container").addClass('border-box-sizing');
this.pager_splitter_element.addClass('ui-widget ui-state-default');
this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
};
@ -105,7 +112,7 @@ var IPython = (function (IPython) {
that.toggle();
});
$([IPython.events]).on('open_with_text.Pager', function (event, payload) {
this.events.on('open_with_text.Pager', function (event, payload) {
// FIXME: support other mime types
if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
that.clear();
@ -171,10 +178,8 @@ var IPython = (function (IPython) {
this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
};
// Backwards compatability.
IPython.Pager = Pager;
return IPython;
}(IPython));
return {'Pager': Pager};
});

@ -1,16 +1,28 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// QuickHelp button
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/dialog',
], function(IPython, $, utils, dialog) {
"use strict";
var platform = IPython.utils.platform;
var QuickHelp = function (selector) {
var platform = utils.platform;
var QuickHelp = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
this.keyboard_manager = options.keyboard_manager;
this.notebook = options.notebook;
this.keyboard_manager.quick_help = this;
this.events = options.events;
};
var cmd_ctrl = 'Ctrl-';
@ -70,8 +82,8 @@ var IPython = (function (IPython) {
$(this.shortcut_dialog).modal("toggle");
return;
}
var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
var help, shortcut;
var i, half, n;
var element = $('<div/>');
@ -96,21 +108,23 @@ var IPython = (function (IPython) {
var edit_div = this.build_edit_help(cm_shortcuts);
element.append(edit_div);
this.shortcut_dialog = IPython.dialog.modal({
this.shortcut_dialog = dialog.modal({
title : "Keyboard shortcuts",
body : element,
destroy : false,
buttons : {
Close : {}
}
},
notebook: this.notebook,
keyboard_manager: this.keyboard_manager,
});
this.shortcut_dialog.addClass("modal_stretch");
$([IPython.events]).on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
};
QuickHelp.prototype.build_command_help = function () {
var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
};
@ -134,7 +148,7 @@ var IPython = (function (IPython) {
};
QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
jQuery.merge(cm_shortcuts, edit_shortcuts);
return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
};
@ -163,9 +177,8 @@ var IPython = (function (IPython) {
return div;
};
// Set module variables
// Backwards compatability.
IPython.QuickHelp = QuickHelp;
return IPython;
}(IPython));
return {'QuickHelp': QuickHelp};
});

@ -1,33 +1,30 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// SaveWidget
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/dialog',
'base/js/keyboard',
'moment',
], function(IPython, $, utils, dialog, keyboard, moment) {
"use strict";
var utils = IPython.utils;
var SaveWidget = function (selector) {
var SaveWidget = function (selector, options) {
// TODO: Remove circular ref.
this.notebook = undefined;
this.selector = selector;
this.events = options.events;
this._checkpoint_date = undefined;
this.keyboard_manager = options.keyboard_manager;
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
this.bind_events();
}
};
SaveWidget.prototype.style = function () {
};
SaveWidget.prototype.bind_events = function () {
var that = this;
this.element.find('span#notebook_name').click(function () {
@ -38,56 +35,59 @@ var IPython = (function (IPython) {
}, function () {
$(this).removeClass("ui-state-hover");
});
$([IPython.events]).on('notebook_loaded.Notebook', function () {
this.events.on('notebook_loaded.Notebook', function () {
that.update_notebook_name();
that.update_document_title();
});
$([IPython.events]).on('notebook_saved.Notebook', function () {
this.events.on('notebook_saved.Notebook', function () {
that.update_notebook_name();
that.update_document_title();
});
$([IPython.events]).on('notebook_renamed.Notebook', function () {
this.events.on('notebook_renamed.Notebook', function () {
that.update_notebook_name();
that.update_document_title();
that.update_address_bar();
});
$([IPython.events]).on('notebook_save_failed.Notebook', function () {
this.events.on('notebook_save_failed.Notebook', function () {
that.set_save_status('Autosave Failed!');
});
$([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
that.set_last_checkpoint(data[0]);
this.events.on('checkpoints_listed.Notebook', function (event, data) {
that._set_last_checkpoint(data[0]);
});
$([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
that.set_last_checkpoint(data);
this.events.on('checkpoint_created.Notebook', function (event, data) {
that._set_last_checkpoint(data);
});
$([IPython.events]).on('set_dirty.Notebook', function (event, data) {
this.events.on('set_dirty.Notebook', function (event, data) {
that.set_autosaved(data.value);
});
};
SaveWidget.prototype.rename_notebook = function () {
SaveWidget.prototype.rename_notebook = function (options) {
options = options || {};
var that = this;
var dialog = $('<div/>').append(
var dialog_body = $('<div/>').append(
$("<p/>").addClass("rename-message")
.text('Enter a new notebook name:')
).append(
$("<br/>")
).append(
$('<input/>').attr('type','text').attr('size','25').addClass('form-control')
.val(IPython.notebook.get_notebook_name())
.val(that.notebook.get_notebook_name())
);
IPython.dialog.modal({
dialog.modal({
title: "Rename Notebook",
body: dialog,
body: dialog_body,
notebook: options.notebook,
keyboard_manager: this.keyboard_manager,
buttons : {
"Cancel": {},
"OK": {
class: "btn-primary",
click: function () {
var new_name = $(this).find('input').val();
if (!IPython.notebook.test_notebook_name(new_name)) {
if (!that.notebook.test_notebook_name(new_name)) {
$(this).find('.rename-message').text(
"Invalid notebook name. Notebook names must "+
"have 1 or more characters and can contain any characters " +
@ -95,7 +95,7 @@ var IPython = (function (IPython) {
);
return false;
} else {
IPython.notebook.rename(new_name);
that.notebook.rename(new_name);
}
}}
},
@ -103,7 +103,7 @@ var IPython = (function (IPython) {
var that = $(this);
// Upon ENTER, click the OK button.
that.find('input[type="text"]').keydown(function (event, ui) {
if (event.which === IPython.keyboard.keycodes.enter) {
if (event.which === keyboard.keycodes.enter) {
that.find('.btn-primary').first().click();
return false;
}
@ -111,24 +111,24 @@ var IPython = (function (IPython) {
that.find('input[type="text"]').focus().select();
}
});
}
};
SaveWidget.prototype.update_notebook_name = function () {
var nbname = IPython.notebook.get_notebook_name();
var nbname = this.notebook.get_notebook_name();
this.element.find('span#notebook_name').text(nbname);
};
SaveWidget.prototype.update_document_title = function () {
var nbname = IPython.notebook.get_notebook_name();
var nbname = this.notebook.get_notebook_name();
document.title = nbname;
};
SaveWidget.prototype.update_address_bar = function(){
var base_url = IPython.notebook.base_url;
var nbname = IPython.notebook.notebook_name;
var path = IPython.notebook.notebook_path;
var base_url = this.notebook.base_url;
var nbname = this.notebook.notebook_name;
var path = this.notebook.notebook_path;
var state = {path : path, name: nbname};
window.history.replaceState(state, "", utils.url_join_encode(
base_url,
@ -141,23 +141,91 @@ var IPython = (function (IPython) {
SaveWidget.prototype.set_save_status = function (msg) {
this.element.find('span#autosave_status').text(msg);
}
};
SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
var el = this.element.find('span#checkpoint_status')
if(human_date){
el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
} else {
el.text('').attr('title','no-checkpoint')
}
};
// compute (roughly) the remaining time in millisecond until the next
// moment.js relative time update of the string, which by default
// happend at
// (a few seconds ago)
// - 45sec,
// (a minute ago)
// - 90sec,
// ( x minutes ago)
// - then every minutes until
// - 45 min,
// (an hour ago)
// - 1h45,
// (x hours ago )
// - then every hours
// - 22 hours ago
var _next_timeago_update = function(deltatime_ms){
var s = 1000;
var m = 60*s;
var h = 60*m;
var mtt = moment.relativeTimeThreshold;
if(deltatime_ms < mtt.s*s){
return mtt.s*s-deltatime_ms;
} else if (deltatime_ms < (mtt.s*s+m)) {
return (mtt.s*s+m)-deltatime_ms;
} else if (deltatime_ms < mtt.m*m){
return m;
} else if (deltatime_ms < (mtt.m*m+h)){
return (mtt.m*m+h)-deltatime_ms;
} else {
return h;
}
SaveWidget.prototype.set_checkpoint_status = function (msg) {
this.element.find('span#checkpoint_status').text(msg);
}
SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
if (!checkpoint) {
this.set_checkpoint_status("");
SaveWidget.prototype._regularly_update_checkpoint_date = function(){
if (!this._checkpoint_date) {
this.set_checkpoint_status(null);
console.log('no checkpoint done');
return;
}
var d = new Date(checkpoint.last_modified);
this.set_checkpoint_status(
"Last Checkpoint: " + d.format('mmm dd HH:MM')
);
var chkd = moment(this._checkpoint_date);
var longdate = chkd.format('llll');
var that = this;
var recall = function(t){
// recall slightly later (1s) as long timeout in js might be imprecise,
// and you want to be call **after** the change of formatting should happend.
return setTimeout($.proxy(that._regularly_update_checkpoint_date, that),(t+1000))
}
var tdelta = Math.ceil(new Date()-this._checkpoint_date);
// update regularly for the first 6hours and show
// <x time> ago
if(tdelta < tdelta < 6*3600*1000){
recall(_next_timeago_update(tdelta));
this._set_checkpoint_status( chkd.fromNow(), longdate);
// otherwise update every hour and show
// <Today | yesterday|...> at hh,mm,ss
} else {
recall(1*3600*1000)
this._set_checkpoint_status( chkd.calendar(), longdate);
}
}
SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
this._checkpoint_date = new Date(checkpoint.last_modified);
this._regularly_update_checkpoint_date();
};
SaveWidget.prototype.set_autosaved = function (dirty) {
if (dirty) {
this.set_save_status("(unsaved changes)");
@ -166,10 +234,9 @@ var IPython = (function (IPython) {
}
};
// Backwards compatibility.
IPython.SaveWidget = SaveWidget;
return IPython;
}(IPython));
return {'SaveWidget': SaveWidget};
});

@ -1,60 +1,57 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2012 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// TextCell
//============================================================================
/**
A module that allow to create different type of Text Cell
@module IPython
@namespace IPython
*/
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'notebook/js/cell',
'base/js/security',
'notebook/js/mathjaxutils',
'notebook/js/celltoolbar',
'components/marked/lib/marked',
], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
"use strict";
var Cell = cell.Cell;
// TextCell base class
var keycodes = IPython.keyboard.keycodes;
var security = IPython.security;
/**
* Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
* cell start as not redered.
*
* @class TextCell
* @constructor TextCell
* @extend IPython.Cell
* @param {object|undefined} [options]
* @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
* @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
*/
var TextCell = function (options) {
// Constructor
//
// Construct a new TextCell, codemirror mode is by default 'htmlmixed',
// and cell type is 'text' cell start as not redered.
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
options = options || {};
// in all TextCell/Cell subclasses
// do not assign most of members here, just pass it down
// in the options dict potentially overwriting what you wish.
// they will be assigned in the base class.
this.notebook = options.notebook;
this.events = options.events;
this.config = options.config;
// we cannot put this as a class key as it has handle to "this".
var cm_overwrite_options = {
onKeyEvent: $.proxy(this.handle_keyevent,this)
};
options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
Cell.apply(this, [{
config: config,
keyboard_manager: options.keyboard_manager,
events: this.events}]);
this.cell_type = this.cell_type || 'text';
IPython.Cell.apply(this, [options]);
mathjaxutils = mathjaxutils;
this.rendered = false;
};
TextCell.prototype = new IPython.Cell();
TextCell.prototype = new Cell();
TextCell.options_default = {
cm_config : {
@ -71,21 +68,23 @@ var IPython = (function (IPython) {
* @private
*/
TextCell.prototype.create_element = function () {
IPython.Cell.prototype.create_element.apply(this, arguments);
Cell.prototype.create_element.apply(this, arguments);
var cell = $("<div>").addClass('cell text_cell border-box-sizing');
var cell = $("<div>").addClass('cell text_cell');
cell.attr('tabindex','2');
var prompt = $('<div/>').addClass('prompt input_prompt');
cell.append(prompt);
var inner_cell = $('<div/>').addClass('inner_cell');
this.celltoolbar = new IPython.CellToolbar(this);
this.celltoolbar = new celltoolbar.CellToolbar({
cell: this,
notebook: this.notebook});
inner_cell.append(this.celltoolbar.element);
var input_area = $('<div/>').addClass('input_area');
this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
// The tabindex=-1 makes this div focusable.
var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
addClass('rendered_html').attr('tabindex','-1');
var render_area = $('<div/>').addClass('text_cell_render rendered_html')
.attr('tabindex','-1');
inner_cell.append(input_area).append(render_area);
cell.append(inner_cell);
this.element = cell;
@ -99,12 +98,12 @@ var IPython = (function (IPython) {
* @method bind_event
*/
TextCell.prototype.bind_events = function () {
IPython.Cell.prototype.bind_events.apply(this);
Cell.prototype.bind_events.apply(this);
var that = this;
this.element.dblclick(function () {
if (that.selected === false) {
$([IPython.events]).trigger('select.Cell', {'cell':that});
this.events.trigger('select.Cell', {'cell':that});
}
var cont = that.unrender();
if (cont) {
@ -116,7 +115,7 @@ var IPython = (function (IPython) {
// Cell level actions
TextCell.prototype.select = function () {
var cont = IPython.Cell.prototype.select.apply(this);
var cont = Cell.prototype.select.apply(this);
if (cont) {
if (this.mode === 'edit') {
this.code_mirror.refresh();
@ -127,7 +126,7 @@ var IPython = (function (IPython) {
TextCell.prototype.unrender = function () {
if (this.read_only) return;
var cont = IPython.Cell.prototype.unrender.apply(this);
var cont = Cell.prototype.unrender.apply(this);
if (cont) {
var text_cell = this.element;
var output = text_cell.find("div.text_cell_render");
@ -164,13 +163,13 @@ var IPython = (function (IPython) {
* */
TextCell.prototype.set_text = function(text) {
this.code_mirror.setValue(text);
this.unrender();
this.code_mirror.refresh();
};
/**
* setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
* @method get_rendered
* @return {html} html of rendered element
* */
TextCell.prototype.get_rendered = function() {
return this.element.find('div.text_cell_render').html();
@ -191,7 +190,7 @@ var IPython = (function (IPython) {
* @method fromJSON
*/
TextCell.prototype.fromJSON = function (data) {
IPython.Cell.prototype.fromJSON.apply(this, arguments);
Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === this.cell_type) {
if (data.source !== undefined) {
this.set_text(data.source);
@ -211,7 +210,7 @@ var IPython = (function (IPython) {
* @return {object} cell data serialised to json
*/
TextCell.prototype.toJSON = function () {
var data = IPython.Cell.prototype.toJSON.apply(this);
var data = Cell.prototype.toJSON.apply(this);
data.source = this.get_text();
if (data.source == this.placeholder) {
data.source = "";
@ -220,16 +219,21 @@ var IPython = (function (IPython) {
};
/**
* @class MarkdownCell
* @constructor MarkdownCell
* @extends IPython.HTMLCell
*/
var MarkdownCell = function (options) {
options = this.mergeopt(MarkdownCell, options);
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
options = options || {};
var config = this.mergeopt(MarkdownCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.cell_type = 'markdown';
TextCell.apply(this, [options]);
};
MarkdownCell.options_default = {
@ -245,16 +249,16 @@ var IPython = (function (IPython) {
* @method render
*/
MarkdownCell.prototype.render = function () {
var cont = IPython.TextCell.prototype.render.apply(this);
var cont = TextCell.prototype.render.apply(this);
if (cont) {
var text = this.get_text();
var math = null;
if (text === "") { text = this.placeholder; }
var text_and_math = IPython.mathjaxutils.remove_math(text);
var text_and_math = mathjaxutils.remove_math(text);
text = text_and_math[0];
math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
html = IPython.mathjaxutils.replace_math(html, math);
html = mathjaxutils.replace_math(html, math);
html = security.sanitize_html(html);
html = $($.parseHTML(html));
// links in markdown cells should open in new tabs
@ -268,20 +272,23 @@ var IPython = (function (IPython) {
};
// RawCell
/**
* @class RawCell
* @constructor RawCell
* @extends IPython.TextCell
*/
var RawCell = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
options = options || {};
var config = this.mergeopt(RawCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
options = this.mergeopt(RawCell,options);
TextCell.apply(this, [options]);
this.cell_type = 'raw';
// RawCell should always hide its rendered div
this.element.find('div.text_cell_render').hide();
this.cell_type = 'raw';
};
RawCell.options_default = {
@ -309,12 +316,12 @@ var IPython = (function (IPython) {
* @method auto_highlight
*/
RawCell.prototype.auto_highlight = function () {
this._auto_highlight(IPython.config.raw_cell_highlight);
this._auto_highlight(this.config.raw_cell_highlight);
};
/** @method render **/
RawCell.prototype.render = function () {
var cont = IPython.TextCell.prototype.render.apply(this);
var cont = TextCell.prototype.render.apply(this);
if (cont){
var text = this.get_text();
if (text === "") { text = this.placeholder; }
@ -325,29 +332,28 @@ var IPython = (function (IPython) {
};
/**
* @class HeadingCell
* @extends IPython.TextCell
*/
/**
* @constructor HeadingCell
* @extends IPython.TextCell
*/
var HeadingCell = function (options) {
options = this.mergeopt(HeadingCell, options);
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// config: dictionary
// keyboard_manager: KeyboardManager instance
// notebook: Notebook instance
options = options || {};
var config = this.mergeopt(HeadingCell, options.config);
TextCell.apply(this, [$.extend({}, options, {config: config})]);
this.level = 1;
this.cell_type = 'heading';
TextCell.apply(this, [options]);
/**
* heading level of the cell, use getter and setter to access
* @property level
*/
};
HeadingCell.options_default = {
cm_config: {
theme: 'heading-1'
},
placeholder: "Type Heading Here"
};
@ -359,6 +365,7 @@ var IPython = (function (IPython) {
this.level = data.level;
}
TextCell.prototype.fromJSON.apply(this, arguments);
this.code_mirror.setOption("theme", "heading-"+this.level);
};
@ -369,29 +376,14 @@ var IPython = (function (IPython) {
return data;
};
/**
* can the cell be split into two cells
* @method is_splittable
**/
HeadingCell.prototype.is_splittable = function () {
return false;
};
/**
* can the cell be merged with other cells
* @method is_mergeable
**/
HeadingCell.prototype.is_mergeable = function () {
return false;
};
/**
* Change heading level of cell, and re-render
* @method set_level
*/
HeadingCell.prototype.set_level = function (level) {
this.level = level;
this.code_mirror.setOption("theme", "heading-"+level);
if (this.rendered) {
this.rendered = false;
this.render();
@ -412,21 +404,20 @@ var IPython = (function (IPython) {
return r.children().first().html();
};
HeadingCell.prototype.render = function () {
var cont = IPython.TextCell.prototype.render.apply(this);
var cont = TextCell.prototype.render.apply(this);
if (cont) {
var text = this.get_text();
var math = null;
// Markdown headings must be a single line
text = text.replace(/\n/g, ' ');
if (text === "") { text = this.placeholder; }
text = Array(this.level + 1).join("#") + " " + text;
var text_and_math = IPython.mathjaxutils.remove_math(text);
text = new Array(this.level + 1).join("#") + " " + text;
var text_and_math = mathjaxutils.remove_math(text);
text = text_and_math[0];
math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
html = IPython.mathjaxutils.replace_math(html, math);
html = mathjaxutils.replace_math(html, math);
html = security.sanitize_html(html);
var h = $($.parseHTML(html));
// add id and linkback anchor
@ -446,13 +437,17 @@ var IPython = (function (IPython) {
return cont;
};
// Backwards compatability.
IPython.TextCell = TextCell;
IPython.MarkdownCell = MarkdownCell;
IPython.RawCell = RawCell;
IPython.HeadingCell = HeadingCell;
return IPython;
}(IPython));
var textcell = {
'TextCell': TextCell,
'MarkdownCell': MarkdownCell,
'RawCell': RawCell,
'HeadingCell': HeadingCell,
};
return textcell;
});

@ -1,20 +1,10 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// ToolBar
//============================================================================
/**
* @module IPython
* @namespace IPython
* @submodule ToolBar
*/
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
], function(IPython, $) {
"use strict";
/**
@ -23,8 +13,9 @@ var IPython = (function (IPython) {
* @constructor
* @param {Dom object} selector
*/
var ToolBar = function (selector) {
var ToolBar = function (selector, layout_manager) {
this.selector = selector;
this.layout_manager = layout_manager;
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
@ -75,7 +66,7 @@ var IPython = (function (IPython) {
.addClass('btn btn-default')
.attr("title", el.label)
.append(
$("<i/>").addClass(el.icon)
$("<i/>").addClass(el.icon).addClass('fa')
);
var id = el.id;
if( id !== undefined )
@ -88,8 +79,7 @@ var IPython = (function (IPython) {
};
ToolBar.prototype.style = function () {
this.element.addClass('border-box-sizing')
.addClass('toolbar');
this.element.addClass('toolbar');
};
/**
@ -98,14 +88,13 @@ var IPython = (function (IPython) {
*/
ToolBar.prototype.toggle = function () {
this.element.toggle();
if (IPython.layout_manager !== undefined) {
IPython.layout_manager.do_resize();
if (this.layout_manager !== undefined) {
this.layout_manager.do_resize();
}
};
// Backwards compatibility.
IPython.ToolBar = ToolBar;
return IPython;
}(IPython));
return {'ToolBar': ToolBar};
});

@ -1,125 +1,115 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Tooltip
//============================================================================
//
// you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
//
// you can configure the differents action of pressing shift-tab several times in a row by
// setting/appending different fonction in the array
// IPython.tooltip.tabs_functions
//
// eg :
// IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
//
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var utils = IPython.utils;
// tooltip constructor
var Tooltip = function () {
var that = this;
this.time_before_tooltip = 1200;
// handle to html
this.tooltip = $('#tooltip');
this._hidden = true;
// variable for consecutive call
this._old_cell = null;
this._old_request = null;
this._consecutive_counter = 0;
// 'sticky ?'
this._sticky = false;
// display tooltip if the docstring is empty?
this._hide_if_no_docstring = false;
var Tooltip = function (events) {
var that = this;
this.events = events;
this.time_before_tooltip = 1200;
// contain the button in the upper right corner
this.buttons = $('<div/>').addClass('tooltipbuttons');
// handle to html
this.tooltip = $('#tooltip');
this._hidden = true;
// will contain the docstring
this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
// variable for consecutive call
this._old_cell = null;
this._old_request = null;
this._consecutive_counter = 0;
// build the buttons menu on the upper right
// expand the tooltip to see more
var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
.attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
that.expand();
}).append(
$('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
// 'sticky ?'
this._sticky = false;
// open in pager
var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
morelink.append(morespan);
morelink.click(function () {
that.showInPager(that._old_cell);
});
// display tooltip if the docstring is empty?
this._hide_if_no_docstring = false;
// contain the button in the upper right corner
this.buttons = $('<div/>').addClass('tooltipbuttons');
// will contain the docstring
this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
// build the buttons menu on the upper right
// expand the tooltip to see more
var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
.attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
that.expand();
}).append(
$('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
// open in pager
var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
morelink.append(morespan);
morelink.click(function () {
that.showInPager(that._old_cell);
});
// close the tooltip
var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
closelink.append(closespan);
closelink.click(function () {
that.remove_and_cancel_tooltip(true);
});
// close the tooltip
var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
closelink.append(closespan);
closelink.click(function () {
that.remove_and_cancel_tooltip(true);
});
this._clocklink = $('<a/>').attr('href', "#");
this._clocklink.attr('role', "button");
this._clocklink.addClass('ui-button');
this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
var clockspan = $('<span/>').text('Close');
clockspan.addClass('ui-icon');
clockspan.addClass('ui-icon-clock');
this._clocklink.append(clockspan);
this._clocklink.click(function () {
that.cancel_stick();
});
this._clocklink = $('<a/>').attr('href', "#");
this._clocklink.attr('role', "button");
this._clocklink.addClass('ui-button');
this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
var clockspan = $('<span/>').text('Close');
clockspan.addClass('ui-icon');
clockspan.addClass('ui-icon-clock');
this._clocklink.append(clockspan);
this._clocklink.click(function () {
that.cancel_stick();
});
//construct the tooltip
// add in the reverse order you want them to appear
this.buttons.append(closelink);
this.buttons.append(expandlink);
this.buttons.append(morelink);
this.buttons.append(this._clocklink);
this._clocklink.hide();
// we need a phony element to make the small arrow
// of the tooltip in css
// we will move the arrow later
this.arrow = $('<div/>').addClass('pretooltiparrow');
this.tooltip.append(this.buttons);
this.tooltip.append(this.arrow);
this.tooltip.append(this.text);
// function that will be called if you press tab 1, 2, 3... times in a row
this.tabs_functions = [function (cell, text, cursor) {
that._request_tooltip(cell, text, cursor);
}, function () {
that.expand();
}, function () {
that.stick();
}, function (cell) {
that.cancel_stick();
that.showInPager(cell);
}];
// call after all the tabs function above have bee call to clean their effects
// if necessary
this.reset_tabs_function = function (cell, text) {
this._old_cell = (cell) ? cell : null;
this._old_request = (text) ? text : null;
this._consecutive_counter = 0;
};
//construct the tooltip
// add in the reverse order you want them to appear
this.buttons.append(closelink);
this.buttons.append(expandlink);
this.buttons.append(morelink);
this.buttons.append(this._clocklink);
this._clocklink.hide();
// we need a phony element to make the small arrow
// of the tooltip in css
// we will move the arrow later
this.arrow = $('<div/>').addClass('pretooltiparrow');
this.tooltip.append(this.buttons);
this.tooltip.append(this.arrow);
this.tooltip.append(this.text);
// function that will be called if you press tab 1, 2, 3... times in a row
this.tabs_functions = [function (cell, text, cursor) {
that._request_tooltip(cell, text, cursor);
}, function () {
that.expand();
}, function () {
that.stick();
}, function (cell) {
that.cancel_stick();
that.showInPager(cell);
}];
// call after all the tabs function above have bee call to clean their effects
// if necessary
this.reset_tabs_function = function (cell, text) {
this._old_cell = (cell) ? cell : null;
this._old_request = (text) ? text : null;
this._consecutive_counter = 0;
};
};
Tooltip.prototype.is_visible = function () {
return !this._hidden;
@ -131,7 +121,7 @@ var IPython = (function (IPython) {
var payload = {};
payload.text = that._reply.content.data['text/plain'];
$([IPython.events]).trigger('open_with_text.Pager', payload);
this.events.trigger('open_with_text.Pager', payload);
this.remove_and_cancel_tooltip();
};
@ -338,8 +328,8 @@ var IPython = (function (IPython) {
this.text.scrollTop(0);
};
// Backwards compatability.
IPython.Tooltip = Tooltip;
return IPython;
}(IPython));
return {'Tooltip': Tooltip};
});

@ -1,135 +1,124 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Tour of IPython Notebok UI (with Bootstrap Tour)
//============================================================================
define([
'base/js/namespace',
'jquery',
'bootstraptour',
], function(IPython, $, Tour) {
"use strict";
var tour_steps = [
{
title: "Welcome to the Notebook Tour",
placement: 'bottom',
orphan: true,
content: "This tour will take 2 minutes.",
}, {
element: "#notebook_name",
title: "Filename",
placement: 'bottom',
content: "Click here to change the filename for this notebook."
}, {
element: $("#menus").parent(),
placement: 'bottom',
backdrop: true,
title: "Notebook Menubar",
content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
}, {
element: "#maintoolbar",
placement: 'bottom',
backdrop: true,
title: "Notebook Toolbar",
content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
}, {
element: "#modal_indicator",
title: "Mode Indicator",
placement: 'bottom',
content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
onShow: function(tour) { command_icon_hack(); }
}, {
element: "#modal_indicator",
title: "Command Mode",
placement: 'bottom',
onShow: function(tour) { IPython.notebook.command_mode(); command_icon_hack(); },
onNext: function(tour) { edit_mode(); },
content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
}, {
element: "#modal_indicator",
title: "Edit Mode",
placement: 'bottom',
onShow: function(tour) { edit_mode(); },
content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
}, {
element: '.selected',
title: "Edit Mode",
placement: 'bottom',
onShow: function(tour) { edit_mode(); },
content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
}, {
element: '.selected',
title: "Back to Command Mode",
placement: 'bottom',
onShow: function(tour) { IPython.notebook.command_mode(); },
onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
}, {
element: '#keyboard_shortcuts',
title: "Keyboard Shortcuts",
placement: 'bottom',
onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
content: "You can click here to get a list of all of the keyboard shortcuts."
}, {
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { $([IPython.events]).trigger('status_idle.Kernel');},
content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
}, {
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { $([IPython.events]).trigger('status_busy.Kernel'); },
content: "The Kernel indicator looks like this when the Kernel is busy.",
}, {
element: ".icon-stop",
placement: 'bottom',
title: "Interrupting the Kernel",
onHide: function(tour) { $([IPython.events]).trigger('status_idle.Kernel'); },
content: "To cancel a computation in progress, you can click here."
}, {
element: "#notification_kernel",
placement: 'bottom',
onShow: function(tour) { $('.icon-stop').click(); },
title: "Notification Area",
content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
}, {
title: "Fin.",
placement: 'bottom',
orphan: true,
content: "This concludes the IPython Notebook User Interface Tour. Happy hacking!",
}
];
var tour_style = "<div class='popover tour'>\n" +
"<div class='arrow'></div>\n" +
"<div style='position:absolute; top:7px; right:7px'>\n" +
"<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
"</div><h3 class='popover-title'></h3>\n" +
"<div class='popover-content'></div>\n" +
"<div class='popover-navigation'>\n" +
"<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
"<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
"<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
"</div>\n" +
"</div>";
var tour_style = "<div class='popover tour'>\
<div class='arrow'></div>\
<div style='position:absolute; top:7px; right:7px'>\
<button class='btn btn-default btn-sm icon-remove' data-role='end'></button></div>\
<h3 class='popover-title'></h3>\
<div class='popover-content'></div>\
<div class='popover-navigation'>\
<button class='btn btn-default icon-step-backward' data-role='prev'></button>\
<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\
<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\
</div>\
</div>";
var command_icon_hack = function() {$('#modal_indicator').css('min-height', 20);}
var toggle_pause_play = function () { $('#tour-pause').toggleClass('icon-pause icon-play'); };
var edit_mode = function() {
IPython.notebook.focus_cell();
IPython.notebook.edit_mode();
;}
IPython = (function (IPython) {
"use strict";
var NotebookTour = function () {
var NotebookTour = function (notebook, events) {
var that = this;
this.notebook = notebook;
this.step_duration = 0;
this.tour_steps = tour_steps ;
this.tour_steps[0].content = "You can use the left and right arrow keys to go backwards and forwards.";
this.events = events;
this.tour_steps = [
{
title: "Welcome to the Notebook Tour",
placement: 'bottom',
orphan: true,
content: "You can use the left and right arrow keys to go backwards and forwards.",
}, {
element: "#notebook_name",
title: "Filename",
placement: 'bottom',
content: "Click here to change the filename for this notebook."
}, {
element: $("#menus").parent(),
placement: 'bottom',
backdrop: true,
title: "Notebook Menubar",
content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
}, {
element: "#maintoolbar",
placement: 'bottom',
backdrop: true,
title: "Notebook Toolbar",
content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
}, {
element: "#modal_indicator",
title: "Mode Indicator",
placement: 'bottom',
content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
onShow: function(tour) { that.command_icon_hack(); }
}, {
element: "#modal_indicator",
title: "Command Mode",
placement: 'bottom',
onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
onNext: function(tour) { that.edit_mode(); },
content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
}, {
element: "#modal_indicator",
title: "Edit Mode",
placement: 'bottom',
onShow: function(tour) { that.edit_mode(); },
content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
}, {
element: '.selected',
title: "Edit Mode",
placement: 'bottom',
onShow: function(tour) { that.edit_mode(); },
content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
}, {
element: '.selected',
title: "Back to Command Mode",
placement: 'bottom',
onShow: function(tour) { notebook.command_mode(); },
onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
}, {
element: '#keyboard_shortcuts',
title: "Keyboard Shortcuts",
placement: 'bottom',
onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
content: "You can click here to get a list of all of the keyboard shortcuts."
}, {
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { events.trigger('status_idle.Kernel');},
content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
}, {
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { events.trigger('status_busy.Kernel'); },
content: "The Kernel indicator looks like this when the Kernel is busy.",
}, {
element: ".icon-stop",
placement: 'bottom',
title: "Interrupting the Kernel",
onHide: function(tour) { events.trigger('status_idle.Kernel'); },
content: "To cancel a computation in progress, you can click here."
}, {
element: "#notification_kernel",
placement: 'bottom',
onShow: function(tour) { $('.icon-stop').click(); },
title: "Notification Area",
content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
}, {
title: "Fin.",
placement: 'bottom',
orphan: true,
content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
}
];
this.tour = new Tour({
//orphan: true,
storage: false, // start tour from beginning every time
@ -143,8 +132,8 @@ IPython = (function (IPython) {
// TODO: remove the onPause/onResume logic once pi's patch has been
// merged upstream to make this work via data-resume-class and
// data-resume-text attributes.
onPause: toggle_pause_play,
onResume: toggle_pause_play,
onPause: this.toggle_pause_play,
onResume: this.toggle_pause_play,
steps: this.tour_steps,
template: tour_style,
orphan: true
@ -162,9 +151,23 @@ IPython = (function (IPython) {
}
};
// Set module variables
NotebookTour.prototype.command_icon_hack = function() {
$('#modal_indicator').css('min-height', 20);
};
NotebookTour.prototype.toggle_pause_play = function () {
$('#tour-pause').toggleClass('icon-pause icon-play');
};
NotebookTour.prototype.edit_mode = function() {
this.notebook.focus_cell();
this.notebook.edit_mode();
};
// For backwards compatability.
IPython.NotebookTour = NotebookTour;
return IPython;
return {'Tour': NotebookTour};
});
}(IPython));

@ -2,19 +2,18 @@ div.cell {
border: 1px solid transparent;
.vbox();
.corner-all();
.border-box-sizing();
border-width: thin;
border-style: solid;
&.selected {
border-color: @border_color;
}
&.edit_mode {
border-color: green;
}
}
div.cell {
width: 100%;
padding: 5px 5px 5px 0px;
/* This acts as a spacer between cells, that is outside the border */

@ -19,7 +19,7 @@ div.input {
/* input_area and input_prompt must match in top border and margin for alignment */
div.input_prompt {
color: navy;
color: @input_prompt_color;
border-top: 1px solid transparent;
}

@ -0,0 +1,14 @@
#kernel_selector_widget {
margin-right: 1em;
float:right;
& > button {
.btn();
.btn-default();
.btn-sm();
& > span.caret {
margin-top:0px;
}
}
}

@ -2,6 +2,7 @@
margin-top: 0px;
margin-bottom: -19px;
position: relative;
.border-box-sizing();
.navbar {
border-top: 1px;

@ -15,6 +15,10 @@ body.notebook_app {
}
}
#ipython-main-app {
.border-box-sizing();
}
span#notebook_name {
height: 1em;
line-height: 1em;
@ -27,6 +31,7 @@ div#notebook_panel {
margin: 0px 0px 0px 0px;
padding: 0px;
.box-shadow(@notebook-shadow);
.border-box-sizing();
}
div#notebook {
font-size: @notebook_font_size;

@ -28,19 +28,27 @@
}
.edit_mode_icon:before {
.icon(@pencil);
.icon(@fa-var-pencil)
}
.command_mode_icon:before {
.icon(' ');
.icon(' ');
}
.kernel_idle_icon:before {
.icon(@circle-blank);
.icon(@fa-var-circle-o);
}
.kernel_busy_icon:before {
.icon(@circle);
.icon(@fa-var-circle);
}
.kernel_dead_icon:before {
.icon(@fa-var-bomb);
}
.kernel_disconnected_icon:before {
.icon(@fa-var-chain-broken);
}

@ -3,11 +3,29 @@
padding: 1px 12px;
margin: 2px 4px;
z-index: 10;
border: 1px solid #ccc;
border-radius: @border-radius-base;
background: rgba(240, 240, 240, 0.5);
background: @notification_widget_bg;
.pull-right();
.border-box-sizing();
.btn();
.btn-default();
.btn-xs();
&.span {
padding-right:2px;
}
}
.notification_widget.warning {
.btn-warning();
}
.notification_widget.success {
.btn-success();
}
.notification_widget.info {
.btn-info();
}
.notification_widget.danger {
.btn-danger();
}

@ -38,7 +38,7 @@ div.out_prompt_overlay:hover {
}
div.output_prompt {
color: darkred;
color: @output_prompt_color;
}
/* This class is the outer container of all output sections. */
@ -84,7 +84,7 @@ div.output_area pre {
padding: 0;
border: 0;
vertical-align: baseline;
color: black;
color: @output_pre_color;
background-color: transparent;
.border-radius(0);
}

@ -1,10 +1,12 @@
div#pager_splitter {
height: 8px;
.border-box-sizing();
}
#pager-container {
position: relative;
padding: 15px 0px;
.border-box-sizing();
}
div#pager {
@ -19,4 +21,5 @@ div#pager {
background-color: @cell_background;
padding: @code_padding;
}
.border-box-sizing();
}

@ -38,8 +38,8 @@
* + ol {margin-top: 1em;}
hr {
color: black;
background-color: black;
color: @rendered_html_border_color;
background-color: @rendered_html_border_color;
}
pre {margin: 1em 2em;}
@ -57,11 +57,11 @@
table {
margin-left: auto;
margin-right: auto;
border: 1px solid black;
border: 1px solid @rendered_html_border_color;
border-collapse: collapse;
}
tr, th, td {
border: 1px solid black;
border: 1px solid @rendered_html_border_color;
border-collapse: collapse;
margin: 1em 2em;
}

@ -31,4 +31,4 @@ span#checkpoint_status, span#autosave_status {
}
}

@ -1,8 +1,14 @@
@import "style_noapp.less";
/*!
*
* IPython notebook webapp
*
*/
@import "notebook.less";
@import "celltoolbar.less";
@import "completer.less";
@import "kernelselector.less";
@import "menubar.less";
@import "notificationarea.less";
@import "notificationwidget.less";

@ -1,3 +1,8 @@
/*!
*
* IPython notebook
*
*/
@import "variables.less";
@import "ansicolors.less";
@import "cell.less";

@ -17,6 +17,7 @@ div.text_cell_render {
border-style: none;
padding: 0.5em 0.5em 0.5em @code_padding;
color: @text-color;
.border-box-sizing();
}
a.anchor-link:link {
@ -34,3 +35,26 @@ h1,h2,h3,h4,h5,h6 {
div.cell.text_cell.rendered {
padding: 0px;
}
.cm-s-heading-1,
.cm-s-heading-2,
.cm-s-heading-3,
.cm-s-heading-4,
.cm-s-heading-5,
.cm-s-heading-6 {
font-weight: bold;
font-family: @font-family-sans-serif;
}
.cm-s-heading-1 { font-size:150%; }
.cm-s-heading-2 { font-size: 130%; }
.cm-s-heading-3 { font-size: 120%; }
.cm-s-heading-4 { font-size: 110%; }
.cm-s-heading-5 {
font-size: 100%;
font-style: italic;
}
.cm-s-heading-6 {
font-size: 90%;
font-style: italic;
}

@ -18,6 +18,8 @@
.btn {
padding: 2px 8px;
}
.border-box-sizing();
}
.toolbar .btn-group {
@ -40,7 +42,7 @@
text-align: right;
margin-left: 5px;
margin-right: 0px;
margin-top: 0px
margin-top: 0px;
}
.toolbar {

@ -11,4 +11,8 @@
@code_line_height: 1.21429em; // changed from 1.231 to get 17px even
@code_padding: 0.4em; // 5.6 px
@notebook-shadow: inset 1px 4px 9px -6px rgba(0,0,0,.25);
@rendered_html_border_color: black;
@input_prompt_color: navy;
@output_prompt_color: darkred;
@output_pre_color: black;
@notification_widget_bg: rgba(240, 240, 240, 0.5);

@ -1,21 +1,11 @@
//----------------------------------------------------------------------------
// Copyright (C) 2013 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Comm and CommManager bases
//============================================================================
/**
* Base Comm classes
* @module IPython
* @namespace IPython
* @submodule comm
*/
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
//-----------------------------------------------------------------------
@ -66,9 +56,9 @@ var IPython = (function (IPython) {
return comm.comm_id;
};
CommManager.prototype.unregister_comm = function (comm_id) {
CommManager.prototype.unregister_comm = function (comm) {
// Remove a comm from the mapping
delete this.comms[comm_id];
delete this.comms[comm.comm_id];
};
// comm message handlers
@ -98,7 +88,7 @@ var IPython = (function (IPython) {
if (comm === undefined) {
return;
}
delete this.comms[content.comm_id];
this.unregister_comm(comm);
try {
comm.handle_close(msg);
} catch (e) {
@ -125,7 +115,7 @@ var IPython = (function (IPython) {
var Comm = function (target_name, comm_id) {
this.target_name = target_name;
this.comm_id = comm_id || IPython.utils.uuid();
this.comm_id = comm_id || utils.uuid();
this._msg_callback = this._close_callback = null;
};
@ -189,10 +179,12 @@ var IPython = (function (IPython) {
this._maybe_callback('close', msg);
};
// For backwards compatability.
IPython.CommManager = CommManager;
IPython.Comm = Comm;
return IPython;
}(IPython));
return {
'CommManager': CommManager,
'Comm': Comm
};
});

@ -1,32 +1,33 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Kernel
//============================================================================
/**
* @module IPython
* @namespace IPython
* @submodule Kernel
*/
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'services/kernels/js/comm',
'widgets/js/init',
], function(IPython, $, utils, comm, widgetmanager) {
"use strict";
var utils = IPython.utils;
// Initialization and connection.
/**
* A Kernel Class to communicate with the Python kernel
* @Class Kernel
*/
var Kernel = function (kernel_service_url) {
var Kernel = function (kernel_service_url, ws_url, notebook, name) {
this.events = notebook.events;
this.kernel_id = null;
this.shell_channel = null;
this.iopub_channel = null;
this.stdin_channel = null;
this.kernel_service_url = kernel_service_url;
this.name = name;
this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
if (!this.ws_url) {
// trailing 's' in https will become wss for secure web sockets
this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
}
this.running = false;
this.username = "username";
this.session_id = utils.uuid();
@ -43,8 +44,8 @@ var IPython = (function (IPython) {
this.bind_events();
this.init_iopub_handlers();
this.comm_manager = new IPython.CommManager(this);
this.widget_manager = new IPython.WidgetManager(this.comm_manager);
this.comm_manager = new comm.CommManager(this);
this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
this.last_msg_id = null;
this.last_msg_callbacks = {};
@ -69,7 +70,7 @@ var IPython = (function (IPython) {
Kernel.prototype.bind_events = function () {
var that = this;
$([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
this.events.on('send_input_reply.Kernel', function(evt, data) {
that.send_input_reply(data);
});
};
@ -111,7 +112,7 @@ var IPython = (function (IPython) {
* @method restart
*/
Kernel.prototype.restart = function () {
$([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
this.events.trigger('status_restarting.Kernel', {kernel: this});
if (this.running) {
this.stop_channels();
this.post(utils.url_join_encode(this.kernel_url, "restart"),
@ -126,8 +127,6 @@ var IPython = (function (IPython) {
console.log("Kernel started: ", json.id);
this.running = true;
this.kernel_id = json.id;
// trailing 's' in https will become wss for secure web sockets
this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
this.start_channels();
};
@ -135,7 +134,7 @@ var IPython = (function (IPython) {
Kernel.prototype._websocket_closed = function(ws_url, early) {
this.stop_channels();
$([IPython.events]).trigger('websocket_closed.Kernel',
this.events.trigger('websocket_closed.Kernel',
{ws_url: ws_url, kernel: this, early: early}
);
};
@ -149,16 +148,16 @@ var IPython = (function (IPython) {
Kernel.prototype.start_channels = function () {
var that = this;
this.stop_channels();
var ws_host_url = this.ws_host + this.kernel_url;
var ws_host_url = this.ws_url + this.kernel_url;
console.log("Starting WebSockets:", ws_host_url);
this.shell_channel = new this.WebSocket(
this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
);
this.stdin_channel = new this.WebSocket(
this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
);
this.iopub_channel = new this.WebSocket(
this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
);
var already_called_onclose = false; // only alert once
@ -180,10 +179,18 @@ var IPython = (function (IPython) {
that._websocket_closed(ws_host_url, false);
}
};
var ws_error = function(evt){
if (already_called_onclose){
return;
}
already_called_onclose = true;
that._websocket_closed(ws_host_url, false);
};
var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
for (var i=0; i < channels.length; i++) {
channels[i].onopen = $.proxy(this._ws_opened, this);
channels[i].onclose = ws_closed_early;
channels[i].onerror = ws_error;
}
// switch from early-close to late-close message after 1s
setTimeout(function() {
@ -212,10 +219,10 @@ var IPython = (function (IPython) {
var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
for (var i=0; i < channels.length; i++) {
// if any channel is not ready, don't trigger event.
if ( !channels[i].readyState ) return;
if ( channels[i].readyState == WebSocket.OPEN ) return;
}
// all events ready, trigger started event.
$([IPython.events]).trigger('status_started.Kernel', {kernel: this});
this.events.trigger('status_started.Kernel', {kernel: this});
};
/**
@ -348,7 +355,7 @@ var IPython = (function (IPython) {
content.allow_stdin = true;
}
$.extend(true, content, options);
$([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
return this.send_shell_message("execute_request", content, callbacks);
};
@ -380,21 +387,23 @@ var IPython = (function (IPython) {
Kernel.prototype.interrupt = function () {
if (this.running) {
$([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
this.events.trigger('status_interrupting.Kernel', {kernel: this});
this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
}
};
Kernel.prototype.kill = function () {
Kernel.prototype.kill = function (success, error) {
if (this.running) {
this.running = false;
var settings = {
cache : false,
type : "DELETE",
error : utils.log_ajax_error,
success : success,
error : error || utils.log_ajax_error,
};
$.ajax(utils.url_join_encode(this.kernel_url), settings);
this.stop_channels();
}
};
@ -402,7 +411,7 @@ var IPython = (function (IPython) {
var content = {
value : input,
};
$([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
var msg = this._get_msg("input_reply", content);
this.stdin_channel.send(JSON.stringify(msg));
return msg.header.msg_id;
@ -482,7 +491,7 @@ var IPython = (function (IPython) {
Kernel.prototype._handle_shell_reply = function (e) {
var reply = $.parseJSON(e.data);
$([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
var content = reply.content;
var metadata = reply.metadata;
var parent_id = reply.parent_header.msg_id;
@ -532,7 +541,7 @@ var IPython = (function (IPython) {
}
if (execution_state === 'busy') {
$([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
this.events.trigger('status_busy.Kernel', {kernel: this});
} else if (execution_state === 'idle') {
// signal that iopub callbacks are (probably) done
// async output may still arrive,
@ -540,17 +549,17 @@ var IPython = (function (IPython) {
this._finish_iopub(parent_id);
// trigger status_idle event
$([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
this.events.trigger('status_idle.Kernel', {kernel: this});
} else if (execution_state === 'restarting') {
// autorestarting is distinct from restarting,
// in that it means the kernel died and the server is restarting it.
// status_restarting sets the notification widget,
// autorestart shows the more prominent dialog.
$([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
$([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
this.events.trigger('status_autorestarting.Kernel', {kernel: this});
this.events.trigger('status_restarting.Kernel', {kernel: this});
} else if (execution_state === 'dead') {
this.stop_channels();
$([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
this.events.trigger('status_dead.Kernel', {kernel: this});
}
};
@ -610,10 +619,8 @@ var IPython = (function (IPython) {
}
};
// Backwards compatability.
IPython.Kernel = Kernel;
return IPython;
}(IPython));
return {'Kernel': Kernel};
});

@ -1,34 +1,35 @@
//----------------------------------------------------------------------------
// Copyright (C) 2013 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Notebook
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'services/kernels/js/kernel',
], function(IPython, $, utils, kernel) {
"use strict";
var utils = IPython.utils;
var Session = function(notebook, options){
var Session = function(options){
this.kernel = null;
this.id = null;
this.notebook = notebook;
this.name = notebook.notebook_name;
this.path = notebook.notebook_path;
this.base_url = notebook.base_url;
this.notebook = options.notebook;
this.events = options.notebook.events;
this.name = options.notebook_name;
this.path = options.notebook_path;
this.kernel_name = options.kernel_name;
this.base_url = options.base_url;
this.ws_url = options.ws_url;
};
Session.prototype.start = function(callback) {
Session.prototype.start = function (success, error) {
var that = this;
var model = {
notebook : {
name : this.name,
path : this.path
},
kernel : {
name : this.kernel_name
}
};
var settings = {
@ -39,11 +40,17 @@ var IPython = (function (IPython) {
dataType : "json",
success : function (data, status, xhr) {
that._handle_start_success(data);
if (callback) {
callback(data, status, xhr);
if (success) {
success(data, status, xhr);
}
},
error : utils.log_ajax_error,
error : function (xhr, status, err) {
that._handle_start_failure(xhr, status, err);
if (error !== undefined) {
error(xhr, status, err);
}
utils.log_ajax_error(xhr, status, err);
}
};
var url = utils.url_join_encode(this.base_url, 'api/sessions');
$.ajax(url, settings);
@ -70,15 +77,19 @@ var IPython = (function (IPython) {
$.ajax(url, settings);
};
Session.prototype.delete = function() {
Session.prototype.delete = function (success, error) {
var settings = {
processData : false,
cache : false,
type : "DELETE",
dataType : "json",
error : utils.log_ajax_error,
success : success,
error : error || utils.log_ajax_error,
};
this.kernel.running = false;
if (this.kernel) {
this.kernel.running = false;
this.kernel.stop_channels();
}
var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
$.ajax(url, settings);
};
@ -91,10 +102,18 @@ var IPython = (function (IPython) {
*/
Session.prototype._handle_start_success = function (data, status, xhr) {
this.id = data.id;
// If we asked for 'python', the response will have 'python3' or 'python2'.
this.kernel_name = data.kernel.name;
this.events.trigger('started.Session', this);
var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
this.kernel = new IPython.Kernel(kernel_service_url);
this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
this.kernel._kernel_started(data.kernel);
};
Session.prototype._handle_start_failure = function (xhr, status, error) {
this.events.trigger('start_failed.Session', [this, xhr, status, error]);
this.events.trigger('status_dead.Kernel');
};
/**
* Prompt the user to restart the IPython kernel.
@ -114,8 +133,18 @@ var IPython = (function (IPython) {
this.kernel.kill();
};
var SessionAlreadyStarting = function (message) {
this.name = "SessionAlreadyStarting";
this.message = (message || "");
};
SessionAlreadyStarting.prototype = Error.prototype;
// For backwards compatability.
IPython.Session = Session;
return IPython;
}(IPython));
return {
Session: Session,
SessionAlreadyStarting: SessionAlreadyStarting,
};
});

File diff suppressed because it is too large Load Diff

@ -1,10 +1,18 @@
// Bootstrap
/*!
*
* Twitter Bootstrap
*
*/
@import "../components/bootstrap/less/bootstrap.less";
@import "../components/bootstrap/less/responsive-utilities.less";
// Font-Awesome
/*!
*
* Font Awesome
*
*/
@import "../components/font-awesome/less/font-awesome.less";
@FontAwesomePath: "../components/font-awesome/font";
@fa-font-path: "../components/font-awesome/fonts";
// base
@import "../base/less/style.less";

File diff suppressed because one or more lines are too long

@ -1,18 +1,12 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// NotebookList
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var utils = IPython.utils;
var ClusterList = function (selector, options) {
this.selector = selector;
@ -98,16 +92,16 @@ var IPython = (function (IPython) {
ClusterItem.prototype.state_stopped = function () {
var that = this;
var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
var status_col = $('<div/>').addClass('status_col col-md-3').text('stopped');
var engines_col = $('<div/>').addClass('engine_col col-md-3');
var profile_col = $('<div/>').addClass('profile_col col-xs-4').text(this.data.profile);
var status_col = $('<div/>').addClass('status_col col-xs-3').text('stopped');
var engines_col = $('<div/>').addClass('engine_col col-xs-3');
var input = $('<input/>').attr('type','number')
.attr('min',1)
.attr('size',3)
.addClass('engine_num_input form-control');
engines_col.append(input);
var start_button = $('<button/>').addClass("btn btn-default btn-xs").text("Start");
var action_col = $('<div/>').addClass('action_col col-md-2').append(
var action_col = $('<div/>').addClass('action_col col-xs-2').append(
$("<span/>").addClass("item_buttons btn-group").append(
start_button
)
@ -150,11 +144,11 @@ var IPython = (function (IPython) {
ClusterItem.prototype.state_running = function () {
var that = this;
var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
var status_col = $('<div/>').addClass('status_col col-md-3').text('running');
var engines_col = $('<div/>').addClass('engines_col col-md-3').text(this.data.n);
var profile_col = $('<div/>').addClass('profile_col col-xs-4').text(this.data.profile);
var status_col = $('<div/>').addClass('status_col col-xs-3').text('running');
var engines_col = $('<div/>').addClass('engines_col col-xs-3').text(this.data.n);
var stop_button = $('<button/>').addClass("btn btn-default btn-xs").text("Stop");
var action_col = $('<div/>').addClass('action_col col-md-2').append(
var action_col = $('<div/>').addClass('action_col col-xs-2').append(
$("<span/>").addClass("item_buttons btn-group").append(
stop_button
)
@ -188,11 +182,12 @@ var IPython = (function (IPython) {
});
};
// For backwards compatability.
IPython.ClusterList = ClusterList;
IPython.ClusterItem = ClusterItem;
return IPython;
}(IPython));
return {
'ClusterList': ClusterList,
'ClusterItem': ClusterItem,
};
});

@ -1,40 +1,52 @@
//----------------------------------------------------------------------------
// Copyright (C) 2014 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Running Kernels List
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'tree/js/notebooklist',
], function(IPython, $, notebooklist) {
"use strict";
var utils = IPython.utils;
var KernelList = function (selector, options) {
IPython.NotebookList.call(this, selector, options, 'running');
// Constructor
//
// Parameters:
// selector: string
// options: dictionary
// Dictionary of keyword arguments.
// session_list: SessionList instance
// base_url: string
// notebook_path: string
notebooklist.NotebookList.call(this, selector, $.extend({
element_name: 'running'},
options));
};
KernelList.prototype = Object.create(IPython.NotebookList.prototype);
KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
KernelList.prototype.sessions_loaded = function (d) {
this.sessions = d;
this.clear_list();
var item;
for (var path in d) {
item = this.new_notebook_item(-1);
this.add_link('', path, item);
this.add_shutdown_button(item, this.sessions[path]);
var item, path_name;
for (path_name in d) {
if (!d.hasOwnProperty(path_name)) {
// nothing is safe in javascript
continue;
}
item = this.new_item(-1);
this.add_link({
name: path_name,
path: '',
type: 'notebook',
}, item);
this.add_shutdown_button(item, this.sessions[path_name]);
}
$('#running_list_header').toggle($.isEmptyObject(d));
}
};
// Backwards compatability.
IPython.KernelList = KernelList;
return IPython;
}(IPython));
return {'KernelList': KernelList};
});

@ -1,32 +1,54 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// On document ready
//============================================================================
require([
'base/js/namespace',
'jquery',
'base/js/events',
'base/js/page',
'base/js/utils',
'tree/js/notebooklist',
'tree/js/clusterlist',
'tree/js/sessionlist',
'tree/js/kernellist',
'auth/js/loginwidget',
// only loaded, not used:
'jqueryui',
'bootstrap',
'custom/custom',
], function(
IPython,
$,
events,
page,
utils,
notebooklist,
clusterlist,
sesssionlist,
kernellist,
loginwidget){
$(document).ready(function () {
IPython.page = new IPython.Page();
$('#new_notebook').button().click(function (e) {
IPython.notebook_list.new_notebook()
});
page = new page.Page();
var opts = {
base_url : IPython.utils.get_body_data("baseUrl"),
notebook_path : IPython.utils.get_body_data("notebookPath"),
var common_options = {
base_url: utils.get_body_data("baseUrl"),
notebook_path: utils.get_body_data("notebookPath"),
};
IPython.session_list = new IPython.SesssionList(opts);
IPython.notebook_list = new IPython.NotebookList('#notebook_list', opts);
IPython.cluster_list = new IPython.ClusterList('#cluster_list', opts);
IPython.kernel_list = new IPython.KernelList('#running_list', opts);
IPython.login_widget = new IPython.LoginWidget('#login_widget', opts);
session_list = new sesssionlist.SesssionList($.extend({
events: events},
common_options));
notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
session_list: session_list},
common_options));
cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
kernel_list = new kernellist.KernelList('#running_list', $.extend({
session_list: session_list},
common_options));
login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
$('#new_notebook').click(function (e) {
notebook_list.new_notebook();
});
var interval_id=0;
// auto refresh every xx secondes, no need to be fast,
@ -35,31 +57,31 @@ $(document).ready(function () {
var enable_autorefresh = function(){
//refresh immediately , then start interval
if($('.upload_button').length == 0)
if($('.upload_button').length === 0)
{
IPython.session_list.load_sessions();
IPython.cluster_list.load_list();
session_list.load_sessions();
cluster_list.load_list();
}
if (!interval_id){
interval_id = setInterval(function(){
if($('.upload_button').length == 0)
if($('.upload_button').length === 0)
{
IPython.session_list.load_sessions();
IPython.cluster_list.load_list();
session_list.load_sessions();
cluster_list.load_list();
}
}, time_refresh*1000);
}
}
};
var disable_autorefresh = function(){
clearInterval(interval_id);
interval_id = 0;
}
};
// stop autorefresh when page lose focus
$(window).blur(function() {
disable_autorefresh();
})
});
//re-enable when page get focus back
$(window).focus(function() {
@ -69,23 +91,31 @@ $(document).ready(function () {
// finally start it, it will refresh immediately
enable_autorefresh();
IPython.page.show();
page.show();
// For backwards compatability.
IPython.page = page;
IPython.notebook_list = notebook_list;
IPython.cluster_list = cluster_list;
IPython.session_list = session_list;
IPython.kernel_list = kernel_list;
IPython.login_widget = login_widget;
events.trigger('app_initialized.DashboardApp');
// bound the upload method to the on change of the file select list
$("#alternate_upload").change(function (event){
IPython.notebook_list.handleFilesUpload(event,'form');
notebook_list.handleFilesUpload(event,'form');
});
// set hash on tab click
$("#tabs").find("a").click(function() {
window.location.hash = $(this).attr("href");
})
});
// load tab if url hash
if (window.location.hash) {
$("#tabs").find("a[href=" + window.location.hash + "]").click();
}
});

@ -1,23 +1,29 @@
//----------------------------------------------------------------------------
// Copyright (C) 2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// NotebookList
//============================================================================
var IPython = (function (IPython) {
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
'base/js/dialog',
], function(IPython, $, utils, dialog) {
"use strict";
var utils = IPython.utils;
var NotebookList = function (selector, options, element_name) {
var that = this
var NotebookList = function (selector, options) {
// Constructor
//
// Parameters:
// selector: string
// options: dictionary
// Dictionary of keyword arguments.
// session_list: SessionList instance
// element_name: string
// base_url: string
// notebook_path: string
var that = this;
this.session_list = options.session_list;
// allow code re-use by just changing element_name in kernellist.js
this.element_name = element_name || 'notebook';
this.element_name = options.element_name || 'notebook';
this.selector = selector;
if (this.selector !== undefined) {
this.element = $(selector);
@ -28,12 +34,14 @@ var IPython = (function (IPython) {
this.sessions = {};
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
$([IPython.events]).on('sessions_loaded.Dashboard',
function(e, d) { that.sessions_loaded(d); });
if (this.session_list && this.session_list.events) {
this.session_list.events.on('sessions_loaded.Dashboard',
function(e, d) { that.sessions_loaded(d); });
}
};
NotebookList.prototype.style = function () {
var prefix = '#' + this.element_name
var prefix = '#' + this.element_name;
$(prefix + '_toolbar').addClass('list_toolbar');
$(prefix + '_list_info').addClass('toolbar_info');
$(prefix + '_buttons').addClass('toolbar_buttons');
@ -67,30 +75,27 @@ var IPython = (function (IPython) {
}
for (var i = 0; i < files.length; i++) {
var f = files[i];
var reader = new FileReader();
reader.readAsText(f);
var name_and_ext = utils.splitext(f.name);
var file_ext = name_and_ext[1];
var reader = new FileReader();
if (file_ext === '.ipynb') {
var item = that.new_notebook_item(0);
item.addClass('new-file');
that.add_name_input(f.name, item);
// Store the notebook item in the reader so we can use it later
// to know which item it belongs to.
$(reader).data('item', item);
reader.onload = function (event) {
var nbitem = $(event.target).data('item');
that.add_notebook_data(event.target.result, nbitem);
that.add_upload_button(nbitem);
};
reader.readAsText(f);
} else {
var dialog = 'Uploaded notebooks must be .ipynb files';
IPython.dialog.modal({
title : 'Invalid file type',
body : dialog,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
// read non-notebook files as binary
reader.readAsArrayBuffer(f);
}
var item = that.new_item(0);
item.addClass('new-file');
that.add_name_input(f.name, item);
// Store the list item in the reader so we can use it later
// to know which item it belongs to.
$(reader).data('item', item);
reader.onload = function (event) {
var item = $(event.target).data('item');
that.add_file_data(event.target.result, item);
that.add_upload_button(item);
};
}
// Replace the file input form wth a clone of itself. This is required to
// reset the form. Otherwise, if you upload a file, delete it and try to
@ -114,7 +119,7 @@ var IPython = (function (IPython) {
};
NotebookList.prototype.load_sessions = function(){
IPython.session_list.load_sessions();
this.session_list.load_sessions();
};
@ -140,7 +145,7 @@ var IPython = (function (IPython) {
var url = utils.url_join_encode(
this.base_url,
'api',
'notebooks',
'contents',
this.notebook_path
);
$.ajax(url, settings);
@ -153,10 +158,12 @@ var IPython = (function (IPython) {
message = param.msg;
}
var item = null;
var len = data.length;
var model = null;
var list = data.content;
var len = list.length;
this.clear_list();
if (len === 0) {
item = this.new_notebook_item(0);
item = this.new_item(0);
var span12 = item.children().first();
span12.empty();
span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
@ -164,31 +171,24 @@ var IPython = (function (IPython) {
var path = this.notebook_path;
var offset = 0;
if (path !== '') {
item = this.new_notebook_item(0);
this.add_dir(path, '..', item);
item = this.new_item(0);
model = {
type: 'directory',
name: '..',
path: path,
};
this.add_link(model, item);
offset = 1;
}
for (var i=0; i<len; i++) {
if (data[i].type === 'directory') {
var name = data[i].name;
item = this.new_notebook_item(i+offset);
this.add_dir(path, name, item);
} else {
var name = data[i].name;
item = this.new_notebook_item(i+offset);
this.add_link(path, name, item);
name = utils.url_path_join(path, name);
if(this.sessions[name] === undefined){
this.add_delete_button(item);
} else {
this.add_shutdown_button(item,this.sessions[name]);
}
}
model = list[i];
item = this.new_item(i+offset);
this.add_link(model, item);
}
};
NotebookList.prototype.new_notebook_item = function (index) {
NotebookList.prototype.new_item = function (index) {
var item = $('<div/>').addClass("list_item").addClass("row");
// item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
// item.css('border-top-style','none');
@ -211,55 +211,70 @@ var IPython = (function (IPython) {
};
NotebookList.prototype.add_dir = function (path, name, item) {
NotebookList.icons = {
directory: 'folder_icon',
notebook: 'notebook_icon',
file: 'file_icon',
};
NotebookList.uri_prefixes = {
directory: 'tree',
notebook: 'notebooks',
file: 'files',
};
NotebookList.prototype.add_link = function (model, item) {
var path = model.path,
name = model.name;
item.data('name', name);
item.data('path', path);
item.find(".item_name").text(name);
item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
item.find("a.item_link")
var icon = NotebookList.icons[model.type];
var uri_prefix = NotebookList.uri_prefixes[model.type];
item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
var link = item.find("a.item_link")
.attr('href',
utils.url_join_encode(
this.base_url,
"tree",
uri_prefix,
path,
name
)
);
// directory nav doesn't open new tabs
// files, notebooks do
if (model.type !== "directory") {
link.attr('target','_blank');
}
var path_name = utils.url_path_join(path, name);
if (model.type == 'file') {
this.add_delete_button(item);
} else if (model.type == 'notebook') {
if(this.sessions[path_name] === undefined){
this.add_delete_button(item);
} else {
this.add_shutdown_button(item, this.sessions[path_name]);
}
}
};
NotebookList.prototype.add_link = function (path, nbname, item) {
item.data('nbname', nbname);
item.data('path', path);
item.find(".item_name").text(nbname);
item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
item.find("a.item_link")
.attr('href',
utils.url_join_encode(
this.base_url,
"notebooks",
path,
nbname
)
).attr('target','_blank');
};
NotebookList.prototype.add_name_input = function (nbname, item) {
item.data('nbname', nbname);
NotebookList.prototype.add_name_input = function (name, item) {
item.data('name', name);
item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
item.find(".item_name").empty().append(
$('<input/>')
.addClass("nbname_input")
.attr('value', utils.splitext(nbname)[0])
.addClass("filename_input")
.attr('value', name)
.attr('size', '30')
.attr('type', 'text')
);
};
NotebookList.prototype.add_notebook_data = function (data, item) {
item.data('nbdata', data);
NotebookList.prototype.add_file_data = function (data, item) {
item.data('filedata', data);
};
@ -296,13 +311,13 @@ var IPython = (function (IPython) {
click(function (e) {
// $(this) is the button that was clicked.
var that = $(this);
// We use the nbname and notebook_id from the parent notebook_item element's
// data because the outer scopes values change as we iterate through the loop.
// We use the filename from the parent list_item element's
// data because the outer scope's values change as we iterate through the loop.
var parent_item = that.parents('div.list_item');
var nbname = parent_item.data('nbname');
var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
IPython.dialog.modal({
title : "Delete notebook",
var name = parent_item.data('name');
var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
dialog.modal({
title : "Delete file",
body : message,
buttons : {
Delete : {
@ -320,9 +335,9 @@ var IPython = (function (IPython) {
};
var url = utils.url_join_encode(
notebooklist.base_url,
'api/notebooks',
'api/contents',
notebooklist.notebook_path,
nbname
name
);
$.ajax(url, settings);
}
@ -336,30 +351,69 @@ var IPython = (function (IPython) {
};
NotebookList.prototype.add_upload_button = function (item) {
NotebookList.prototype.add_upload_button = function (item, type) {
var that = this;
var upload_button = $('<button/>').text("Upload")
.addClass('btn btn-primary btn-xs upload_button')
.click(function (e) {
var nbname = item.find('.item_name > input').val();
if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
nbname = nbname + ".ipynb";
}
var path = that.notebook_path;
var nbdata = item.data('nbdata');
var content_type = 'application/json';
var filename = item.find('.item_name > input').val();
var filedata = item.data('filedata');
var format = 'text';
if (filedata instanceof ArrayBuffer) {
// base64-encode binary file data
var bytes = '';
var buf = new Uint8Array(filedata);
var nbytes = buf.byteLength;
for (var i=0; i<nbytes; i++) {
bytes += String.fromCharCode(buf[i]);
}
filedata = btoa(bytes);
format = 'base64';
}
var model = {
content : JSON.parse(nbdata),
path: path,
name: filename
};
var name_and_ext = utils.splitext(filename);
var file_ext = name_and_ext[1];
var content_type;
if (file_ext === '.ipynb') {
model.type = 'notebook';
model.format = 'json';
try {
model.content = JSON.parse(filedata);
} catch (e) {
dialog.modal({
title : 'Cannot upload invalid Notebook',
body : "The error was: " + e,
buttons : {'OK' : {
'class' : 'btn-primary',
click: function () {
item.remove();
}
}}
});
}
content_type = 'application/json';
} else {
model.type = 'file';
model.format = format;
model.content = filedata;
content_type = 'application/octet-stream';
}
var filedata = item.data('filedata');
var settings = {
processData : false,
cache : false,
type : 'PUT',
dataType : 'json',
data : JSON.stringify(model),
headers : {'Content-Type': content_type},
success : function (data, status, xhr) {
that.add_link(path, nbname, item);
item.removeClass('new-file');
that.add_link(model, item);
that.add_delete_button(item);
},
error : utils.log_ajax_error,
@ -367,9 +421,9 @@ var IPython = (function (IPython) {
var url = utils.url_join_encode(
that.base_url,
'api/notebooks',
'api/contents',
that.notebook_path,
nbname
filename
);
$.ajax(url, settings);
return false;
@ -377,7 +431,6 @@ var IPython = (function (IPython) {
var cancel_button = $('<button/>').text("Cancel")
.addClass("btn btn-default btn-xs")
.click(function (e) {
console.log('cancel click');
item.remove();
return false;
});
@ -411,7 +464,7 @@ var IPython = (function (IPython) {
};
var url = utils.url_join_encode(
base_url,
'api/notebooks',
'api/contents',
path
);
$.ajax(url, settings);
@ -426,16 +479,16 @@ var IPython = (function (IPython) {
} else {
msg = xhr.statusText;
}
IPython.dialog.modal({
dialog.modal({
title : 'Creating Notebook Failed',
body : "The error was: " + msg,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}
};
IPython.NotebookList = NotebookList;
return IPython;
// Backwards compatability.
IPython.NotebookList = NotebookList;
}(IPython));
return {'NotebookList': NotebookList};
});

@ -1,20 +1,22 @@
//----------------------------------------------------------------------------
// Copyright (C) 2014 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//============================================================================
// Running Kernels List
//============================================================================
var IPython = (function (IPython) {
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var utils = IPython.utils;
var SesssionList = function (options) {
// Constructor
//
// Parameters:
// options: dictionary
// Dictionary of keyword arguments.
// events: $(Events) instance
// base_url : string
this.events = options.events;
this.sessions = {};
this.base_url = options.base_url || utils.get_body_data("baseUrl");
};
@ -44,10 +46,11 @@ var IPython = (function (IPython) {
);
this.sessions[nb_path] = data[i].id;
}
$([IPython.events]).trigger('sessions_loaded.Dashboard', this.sessions);
this.events.trigger('sessions_loaded.Dashboard', this.sessions);
};
IPython.SesssionList = SesssionList;
return IPython;
// Backwards compatability.
IPython.SesssionList = SesssionList;
}(IPython));
return {'SesssionList': SesssionList};
});

@ -1,2 +1,7 @@
/*!
*
* IPython tree view
*
*/
@import "altuploadform.less";
@import "tree.less";

@ -141,9 +141,13 @@ input.engine_num_input {
}
.folder_icon:before {
.icon(@folder-close-alt)
.icon(@fa-var-folder-o)
}
.notebook_icon:before {
.icon(@book)
.icon(@fa-var-book)
}
.file_icon:before {
.icon(@fa-var-file-o)
}

@ -1,22 +1,27 @@
//----------------------------------------------------------------------------
// Copyright (C) 2013 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Basic Widgets
//============================================================================
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
"widgets/js/manager",
"widgets/js/widget_bool",
"widgets/js/widget_button",
"widgets/js/widget_container",
"widgets/js/widget_box",
"widgets/js/widget_float",
"widgets/js/widget_image",
"widgets/js/widget_int",
"widgets/js/widget_selection",
"widgets/js/widget_selectioncontainer",
"widgets/js/widget_string",
], function(){ return true; });
], function(widgetmanager) {
// Register all of the loaded views with the widget manager.
for (var i = 1; i < arguments.length; i++) {
for (var target_name in arguments[i]) {
if (arguments[i].hasOwnProperty(target_name)) {
widgetmanager.WidgetManager.register_widget_view(target_name, arguments[i][target_name]);
}
}
}
return {'WidgetManager': widgetmanager.WidgetManager};
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save