Resolving Conflicts

pull/2357/head
samarsultan 9 years ago
commit 57da7fa019

3
.gitignore vendored

@ -33,3 +33,6 @@ src
Read the Docs
config.rst
/.project
/.pydevproject

@ -8,7 +8,7 @@ python:
- 3.5.1 # Set to 3.5.1 since travis has not yet included as default for 3.5
- 2.7
sudo: false
sudo: required
env:
global:
@ -37,6 +37,8 @@ before_install:
install:
- pip install -f travis-wheels/wheelhouse file://$PWD#egg=notebook[test]
- wget https://github.com/jgm/pandoc/releases/download/1.19.1/pandoc-1.19.1-1-amd64.deb && sudo dpkg -i pandoc-1.19.1-1-amd64.deb
script:
- 'if [[ $GROUP == js* ]]; then travis_retry python -m notebook.jstest ${GROUP:3}; fi'

@ -24,22 +24,20 @@ Installing Node.js and npm
^^^^^^^^^^^^^^^^^^^^^^^^^^
Building the Notebook from its GitHub source code requires some tools to
create and minify JavaScript components and the CSS.
Namely, that's Node.js and Node's package manager, ``npm``.
create and minify JavaScript components and the CSS,
specifically Node.js and Node's package manager, ``npm``.
It should be node version ≥ 6.0.
If you use ``conda``, you can get them with::
conda install -c javascript nodejs
conda install -c conda-forge nodejs
If you use `Homebrew <http://brew.sh/>`_ on Mac OS X::
brew install node
For Debian/Ubuntu systems, you should use the ``nodejs-legacy`` package instead
of the ``node`` package::
sudo apt-get update
sudo apt-get install nodejs-legacy npm
Installation on Linux may vary, but be aware that the `nodejs` or `npm` packages
included in the system package repository may be too old to work properly.
You can also use the installer from the `Node.js website <https://nodejs.org>`_.
@ -50,14 +48,13 @@ Installing the Jupyter Notebook
Once you have installed the dependencies mentioned above, use the following
steps::
pip install setuptools pip --upgrade --user
pip install --upgrade setuptools pip
git clone https://github.com/jupyter/notebook
cd notebook
pip install -e . --user
pip install -e .
If you want the development environment to be available for all users of your
system (assuming you have the necessary rights) or if you are installing in a
virtual environment, just drop the ``--user`` option.
If you are using a system-wide Python installation and you only want to install the notebook for you,
you can add ``--user`` to the install commands.
Once you have done this, you can launch the master branch of Jupyter notebook
from any directory in your system with::
@ -108,7 +105,7 @@ Python Tests
Install dependencies::
pip install -e .[test] --user
pip install -e .[test]
To run the Python tests, use::
@ -124,7 +121,7 @@ JavaScript Tests
To run the JavaScript tests, you will need to have PhantomJS and CasperJS
installed::
npm install -g casperjs phantomjs@1.9.18
npm install -g casperjs phantomjs-prebuilt
Then, to run the JavaScript tests::
@ -157,10 +154,9 @@ containing all the necessary packages (except pandoc), use::
.. _conda environment:
http://conda.pydata.org/docs/using/envs.html#use-environment-from-file
If you want to install the necessary packages with ``pip`` instead, use
(omitting --user if working in a virtual environment)::
If you want to install the necessary packages with ``pip`` instead::
pip install -r docs/doc-requirements.txt --user
pip install -r docs/doc-requirements.txt
Once you have installed the required packages, you can build the docs with::

@ -1,5 +1,5 @@
include COPYING.md
include CONTRIBUTING.md
include CONTRIBUTING.rst
include README.md
include package.json
include bower.json

@ -5,19 +5,25 @@
"backbone": "components/backbone#~1.2",
"bootstrap": "components/bootstrap#~3.3",
"bootstrap-tour": "0.9.0",
"codemirror": "components/codemirror#~5.22.2",
"codemirror": "components/codemirror#~5.27",
"es6-promise": "~1.0",
"font-awesome": "components/font-awesome#~4.2.0",
"font-awesome": "components/font-awesome#~4.7.0",
"google-caja": "5669",
"jed": "~1.1.1",
"jquery": "components/jquery#~2.0",
"jquery-typeahead": "~2.0.0",
"jquery-ui": "components/jqueryui#~1.10",
"marked": "~0.3",
"MathJax": "components/MathJax#~2.6",
"moment": "~2.8.4",
"preact": "https://unpkg.com/preact@^7.2.0/dist/preact.min.js",
"preact-compat": "https://unpkg.com/preact-compat@^3.14.3/dist/preact-compat.min.js",
"proptypes": "https://unpkg.com/proptypes@^0.14.4/index.js",
"requirejs": "~2.1",
"requirejs-text": "~2.0.15",
"requirejs-plugins": "~1.0.3",
"text-encoding": "~0.1",
"underscore": "components/underscore#~1.8.3",
"xterm.js": "sourcelair/xterm.js#~2.3.2"
"xterm.js": "sourcelair/xterm.js#~2.8.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

@ -12,8 +12,8 @@ For more detailed information, see
Use ``pip install notebook --upgrade`` or ``conda upgrade notebook`` to
upgrade to the latest release.
.. we push for pip 9+ or it will break for Python 2 users when IPython 6 get out.
Upgrade to version 9+ of pip before upgrading ``notebook`` is strongly recommended.
.. we push for pip 9+ or it will break for Python 2 users when IPython 6 is out.
We strongly recommend that you upgrade to version 9+ of pip before upgrading ``notebook``.
.. tip::
@ -32,9 +32,17 @@ created by the "Big Split" of IPython and Jupyter.
We encourage users to start trying JupyterLab in preparation for a future
transition.
We have merged more than 200 pull requests since the 4.x series. Some of the
We have merged more than 300 pull requests since 4.0. Some of the
major user-facing changes are described here.
File sorting in the dashboard
*****************************
Files in the dashboard may now be sorted by last modified date or name (:ghpull:`943`):
.. image:: /_static/images/dashboard-sort.png
:align: center
Cell tags
*********
@ -46,7 +54,8 @@ There is a new cell toolbar for adding *cell tags* (:ghpull:`2048`):
Cell tags are a lightweight way to customise the behaviour of tools working with
notebooks; we're working on building support for them into tools like `nbconvert
<http://nbconvert.readthedocs.io/en/latest/>`__ and `nbval
<https://github.com/computationalmodelling/nbval>`__.
<https://github.com/computationalmodelling/nbval>`__. To start using tags,
select ``Tags`` in the ``View > Cell Toolbar`` menu in a notebook.
The UI for editing cell tags is basic for now; we hope to improve it in future
releases.
@ -54,15 +63,22 @@ releases.
Table style
***********
The default styling for tables in the notebook has been updated (:ghpull:`1776`):
The default styling for tables in the notebook has been updated (:ghpull:`1776`).
Before:
.. image:: /_static/images/table-style-change.png
.. image:: /_static/images/table-style-before.png
:align: center
After:
.. image:: /_static/images/table-style-after.png
:align: center
Customise keyboard shortcuts
****************************
You can now edit keyboard shortcuts for command mode within the UI
You can now edit keyboard shortcuts for *Command Mode* within the UI
(:ghpull:`1347`):
.. image:: /_static/images/shortcut-editor.png
@ -107,8 +123,23 @@ Other additions
- Add more visible *Trusted* and *Untrusted* notifications (:ghpull:`1658`).
- The tab icon in the browser now changes to indicate when the kernel is busy
- The favicon (browser shortcut icon) now changes to indicate when the kernel is busy
(:ghpull:`1837`).
- Header and toolbar visibility is now persisted in nbconfig and across sessions
(:ghpull:`1769`).
- Load server extensions with ConfigManager so that merge happens recursively,
unlike normal config values, to make it load more consistently with frontend
extensions(:ghpull:`2108`).
- The notebook server now supports the `bundler API
<http://jupyter-notebook.readthedocs.io/en/latest/extending/bundler_extensions.html>`__
from the `jupyter_cms incubator project
<https://github.com/jupyter-incubator/contentmanagement>`__ (:ghpull:`1579`).
- The notebook server now provides information about kernel activity in
its kernel resource API (:ghpull:`1827`).
Remember that upgrading ``notebook`` only affects the user
interface. Upgrading kernels and libraries may also provide new features,

@ -344,7 +344,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"So my notebook has a heading cell and some code cells,\n",
"So my notebook has some code cells,\n",
"one of which contains some IPython syntax.\n",
"\n",
"Let's see what happens when we import it"

@ -20,7 +20,7 @@
"source": [
"When you first start the notebook server, your browser will open to the notebook dashboard. The dashboard serves as a home page for the notebook. Its main purpose is to display the notebooks and files in the current directory. For example, here is a screenshot of the dashboard page for the `examples` directory in the Jupyter repository:\n",
"\n",
"<img src=\"images/dashboard_files_tab.png\" width=\"791px\"/>"
"![Jupyter dashboard showing files tab](images/dashboard_files_tab.png)"
]
},
{
@ -31,7 +31,7 @@
"\n",
"To create a new notebook, click on the \"New\" button at the top of the list and select a kernel from the dropdown (as seen below). Which kernels are listed depend on what's installed on the server. Some of the kernels in the screenshot below may not exist as an option to you.\n",
"\n",
"<img src=\"images/dashboard_files_tab_new.png\" width=\"202px\" />"
"![Jupyter \"New\" menu](images/dashboard_files_tab_new.png)"
]
},
{
@ -42,7 +42,8 @@
"\n",
"The notebook list shows green \"Running\" text and a green notebook icon next to running notebooks (as seen below). Notebooks remain running until you explicitly shut them down; closing the notebook's page is not sufficient.\n",
"\n",
"<img src=\"images/dashboard_files_tab_run.png\" width=\"777px\"/>"
"\n",
"![Jupyter dashboard showing one notebook with a running kernel](images/dashboard_files_tab_run.png)"
]
},
{
@ -51,7 +52,7 @@
"source": [
"To shutdown, delete, duplicate, or rename a notebook check the checkbox next to it and an array of controls will appear at the top of the notebook list (as seen below). You can also use the same operations on directories and files when applicable.\n",
"\n",
"<img src=\"images/dashboard_files_tab_btns.png\" width=\"301px\" />"
"![Buttons: Duplicate, rename, shutdown, delete, new, refresh](images/dashboard_files_tab_btns.png)"
]
},
{
@ -60,7 +61,7 @@
"source": [
"To see all of your running notebooks along with their directories, click on the \"Running\" tab:\n",
"\n",
"<img src=\"images/dashboard_running_tab.png\" width=\"786px\" />\n",
"![Jupyter dashboard running tab](images/dashboard_running_tab.png)\n",
"\n",
"This view provides a convenient way to track notebooks that you start as you navigate the file system in a long running notebook server."
]
@ -112,7 +113,7 @@
"source": [
"Edit mode is indicated by a green cell border and a prompt showing in the editor area:\n",
"\n",
"<img src=\"images/edit_mode.png\">\n",
"![Jupyter cell with green border](images/edit_mode.png)\n",
"\n",
"When a cell is in edit mode, you can type into the cell, like a normal text editor."
]
@ -139,7 +140,7 @@
"source": [
"Command mode is indicated by a grey cell border with a blue left margin:\n",
"\n",
"<img src=\"images/command_mode.png\">\n",
"![Jupyter cell with blue & grey border](images/command_mode.png)\n",
"\n",
"When you are in command mode, you are able to edit the notebook as a whole, but not type into individual cells. Most importantly, in command mode, the keyboard is mapped to a set of shortcuts that let you perform notebook and cell actions efficiently. For example, if you are in command mode and you press `c`, you will copy the current cell - no modifier is needed."
]
@ -175,7 +176,7 @@
"source": [
"All navigation and actions in the Notebook are available using the mouse through the menubar and toolbar, which are both above the main Notebook area:\n",
"\n",
"<img src=\"images/menubar_toolbar.png\" width=\"786px\" />"
"![Jupyter notebook menus and toolbar](images/menubar_toolbar.png)"
]
},
{
@ -193,7 +194,7 @@
"source": [
"The second idea of mouse based navigation is that **cell actions usually apply to the currently selected cell**. Thus if you want to run the code in a cell, you would select it and click the <button class='btn btn-default btn-xs'><i class=\"fa fa-step-forward icon-step-forward\"></i></button> button in the toolbar or the \"Cell:Run\" menu item. Similarly, to copy a cell you would select it and click the <button class='btn btn-default btn-xs'><i class=\"fa fa-copy icon-copy\"></i></button> button in the toolbar or the \"Edit:Copy\" menu item. With this simple pattern, you should be able to do most everything you need with the mouse.\n",
"\n",
"Markdown and heading cells have one other state that can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, you will see a nice formatted representation of the cell's contents. When they are unrendered, you will see the raw text source of the cell. To render the selected cell with the mouse, click the <button class='btn btn-default btn-xs'><i class=\"fa fa-step-forward icon-step-forward\"></i></button> button in the toolbar or the \"Cell:Run\" menu item. To unrender the selected cell, double click on the cell."
"Markdown cells have one other state that can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, you will see a nice formatted representation of the cell's contents. When they are unrendered, you will see the raw text source of the cell. To render the selected cell with the mouse, click the <button class='btn btn-default btn-xs'><i class=\"fa fa-step-forward icon-step-forward\"></i></button> button in the toolbar or the \"Cell:Run\" menu item. To unrender the selected cell, double click on the cell."
]
},
{
@ -245,9 +246,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.1"
"version": "3.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 0
"nbformat_minor": 1
}

@ -79,7 +79,6 @@
"* See the results of computations with **rich media representations**, such as HTML, LaTeX, PNG, SVG, PDF, etc.\n",
"* Create and use **interactive JavaScript widgets**, which bind interactive user interface controls and visualizations to reactive kernel side computations.\n",
"* Author **narrative text** using the [Markdown](https://daringfireball.net/projects/markdown/) markup language.\n",
"* Build **hierarchical documents** that are organized into sections with different levels of headings.\n",
"* Include mathematical equations using **LaTeX syntax in Markdown**, which are rendered in-browser by [MathJax](http://www.mathjax.org/)."
]
},
@ -111,7 +110,7 @@
"\n",
"The default kernel runs Python code. The notebook provides a simple way for users to pick which of these kernels is used for a given notebook. \n",
"\n",
"Each of these kernels communicate with the notebook web application and web browser using a JSON over ZeroMQ/WebSockets message protocol that is described [here](http://ipython.org/ipython-doc/dev/development/messaging.html). Most users don't need to know about these details, but it helps to understand that \"kernels run code.\""
"Each of these kernels communicate with the notebook web application and web browser using a JSON over ZeroMQ/WebSockets message protocol that is described [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging). Most users don't need to know about these details, but it helps to understand that \"kernels run code.\""
]
},
{

@ -189,17 +189,13 @@
"source": [
"The Notebook webapp supports Github flavored markdown meaning that you can use triple backticks for code blocks:\n",
"\n",
" <pre>\n",
" ```python\n",
" print \"Hello World\"\n",
" ```\n",
" </pre>\n",
"\n",
" <pre>\n",
" ```javascript\n",
" console.log(\"Hello World\")\n",
" ```\n",
" </pre>\n",
"\n",
"Gives:\n",
"\n",
@ -213,16 +209,10 @@
"\n",
"And a table like this: \n",
"\n",
" <pre>\n",
" ```\n",
"\n",
" | This | is |\n",
" |------|------|\n",
" | a | table| \n",
"\n",
" ```\n",
" </pre>\n",
"\n",
"A nice HTML Table:\n",
"\n",
"| This | is |\n",
@ -278,7 +268,7 @@
"\n",
" <img src=\"../images/python_logo.svg\" />\n",
"\n",
"<img src=\"images/python_logo.svg\" />\n",
"<img src=\"../images/python_logo.svg\" />\n",
"\n",
"and a video with the HTML5 video tag:\n",
"\n",
@ -328,7 +318,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
"version": "3.6.0"
}
},
"nbformat": 4,

@ -31,33 +31,33 @@ when the extension is loaded.
To get the notebook server to load your custom extension, you'll need to
add it to the list of extensions to be loaded. You can do this using the
config system. ``NotebookApp.server_extensions`` is a config variable
which is an array of strings, each a Python module to be imported.
config system. ``NotebookApp.nbserver_extensions`` is a config variable
which is a dictionary of strings, each a Python module to be imported, mapping
to ``True`` to enable or ``False`` to disable each extension.
Because this variable is notebook config, you can set it two different
ways, using config files or via the command line.
For example, to get your extension to load via the command line add a
double dash before the variable name, and put the Python array in
double dash before the variable name, and put the Python dictionary in
double quotes. If your package is "mypackage" and module is
"mymodule", this would look like
``jupyter notebook --NotebookApp.server_extensions="['mypackage.mymodule']"``
``jupyter notebook --NotebookApp.nbserver_extensions="{'mypackage.mymodule':True}"``
.
Basically the string should be Python importable.
Alternatively, you can have your extension loaded regardless of the
command line args by setting the variable in the Jupyter config file.
The default location of the Jupyter config file is
``~/.jupyter/profile_default/jupyter_notebook_config.py``. Then, inside
``~/.jupyter/jupyter_notebook_config.py`` (see :doc:`/config_overview`). Inside
the config file, you can use Python to set the variable. For example,
the following config does the same as the previous command line example
[1].
the following config does the same as the previous command line example.
.. code:: python
c = get_config()
c.NotebookApp.server_extensions = [
'mypackage.mymodule'
]
c.NotebookApp.nbserver_extensions = {
'mypackage.mymodule': True,
}
Before continuing, it's a good idea to verify that your extension is
being loaded. Use a print statement to print something unique. Launch

@ -61,7 +61,7 @@ four spaces. Enter the following code snippet in your JavaScript console::
var config = cell.config;
var patch = {
CodeCell:{
cm_config:{indentUnit: null} # only change here.
cm_config:{indentUnit: null} // only change here.
}
}
config.update(patch)

@ -208,15 +208,14 @@ operations within the notebook, by clicking on an icon.
Structure of a notebook document
--------------------------------
The notebook consists of a sequence of cells. A cell is a multiline
text input field, and its contents can be executed by using
:kbd:`Shift-Enter`, or by clicking either the "Play" button the toolbar, or
`Cell | Run` in the menu bar. The execution behavior of a cell is determined
the cell's type. There are four types of cells: **code cells**, **markdown
cells**, **raw cells** and **heading cells**. Every cell starts off
being a **code cell**, but its type can be changed by using a drop-down on the
toolbar (which will be "Code", initially), or via :ref:`keyboard shortcuts
<keyboard-shortcuts>`.
The notebook consists of a sequence of cells. A cell is a multiline text input
field, and its contents can be executed by using :kbd:`Shift-Enter`, or by
clicking either the "Play" button the toolbar, or `Cell | Run` in the menu bar.
The execution behavior of a cell is determined the cell's type. There are four
types of cells: **code cells**, **markdown cells**, and **raw cells**. Every
cell starts off being a **code cell**, but its type can be changed by using a
drop-down on the toolbar (which will be "Code", initially), or via
:ref:`keyboard shortcuts <keyboard-shortcuts>`.
For more information on the different things you can do in a notebook,
see the `collection of examples
@ -250,6 +249,11 @@ called *Markdown cells*. The Markdown language provides a simple way to
perform this text markup, that is, to specify which parts of the text should
be emphasized (italics), bold, form lists, etc.
If you want to provide structure for your document, you can use markdown
headings. Markdown headings consist of 1 to 6 hash # signs ``#`` followed by a
space and the title of your section. The markdown heading will be converted
to a clickable link for a section of the notebook. It is also used as a hint
when exporting to other document formats, like PDF.
When a Markdown cell is executed, the Markdown code is converted into
the corresponding formatted rich text. Markdown allows arbitrary HTML code for
@ -286,17 +290,6 @@ destination format unmodified. For example, this allows you to type full LaTeX
into a raw cell, which will only be rendered by LaTeX after conversion by
nbconvert.
Heading cells
~~~~~~~~~~~~~
If you want to provide structure for your document, you can use markdown
headings. Markdown headings consist of 1 to 6 hash # signs ``#`` followed by a
space and the title of your section. The markdown heading will be converted
to a clickable link for a section of the notebook. It is also used as a hint
when exporting to other document formats, like PDF.
We recommend using only one markdown header in a cell and limit the cell's
content to the header text. For flexibility of text format conversion, we
suggest placing additional text in the next notebook cell.
Basic workflow
--------------

@ -289,7 +289,7 @@ with the following configuration setting in
c.NotebookApp.tornado_settings = {
'headers': {
'Content-Security-Policy': "frame-ancestors 'https://mywebsite.example.com' 'self' "
'Content-Security-Policy': "frame-ancestors https://mywebsite.example.com 'self' "
}
}

@ -12,7 +12,7 @@ For this reason, notebook 4.3 introduces token-based authentication that is **on
If you enable a password for your notebook server,
token authentication is not enabled by default,
and the behavior of the notebook server is unchanged from from versions earlier than 4.3.
and the behavior of the notebook server is unchanged from versions earlier than 4.3.
When token authentication is enabled, the notebook uses a token to authenticate requests.
This token can be provided to login to the notebook server in three ways:
@ -31,7 +31,8 @@ When you start a notebook server with token authentication enabled (default),
a token is generated to use for authentication.
This token is logged to the terminal, so that you can copy/paste the URL into your browser::
[I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01
[I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at:
http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01
If the notebook server is going to open your browser automatically

@ -46,11 +46,15 @@ def pkg_commit_hash(pkg_path):
while cur_path != par_path:
cur_path = par_path
if p.exists(p.join(cur_path, '.git')):
proc = subprocess.Popen('git rev-parse --short HEAD',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=pkg_path, shell=True)
repo_commit, _ = proc.communicate()
try:
proc = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=pkg_path)
repo_commit, _ = proc.communicate()
except OSError:
repo_commit = None
if repo_commit:
return 'repository', repo_commit.strip().decode('ascii')
else:

@ -9,5 +9,5 @@ store the current version info of the notebook.
# Next beta/alpha/rc release: The version number for beta is X.Y.ZbN **without dots**.
version_info = (5, 0, 0, '.dev')
version_info = (5, 1, 0, '.dev')
__version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:])

@ -1,311 +0,0 @@
"""WebsocketProtocol76 from tornado 3.2.2 for tornado >= 4.0
The contents of this file are Copyright (c) Tornado
Used under the Apache 2.0 license
"""
from __future__ import absolute_import, division, print_function, with_statement
# Author: Jacob Kristhammar, 2010
import functools
import hashlib
import struct
import time
import tornado.escape
import tornado.web
from tornado.log import gen_log, app_log
from tornado.util import bytes_type, unicode_type
from tornado.websocket import WebSocketHandler, WebSocketProtocol13
class AllowDraftWebSocketHandler(WebSocketHandler):
"""Restore Draft76 support for tornado 4
Remove when we can run tests without phantomjs + qt4
"""
# get is unmodified except between the BEGIN/END PATCH lines
@tornado.web.asynchronous
def get(self, *args, **kwargs):
self.open_args = args
self.open_kwargs = kwargs
# Upgrade header should be present and should be equal to WebSocket
if self.request.headers.get("Upgrade", "").lower() != 'websocket':
self.set_status(400)
self.finish("Can \"Upgrade\" only to \"WebSocket\".")
return
# Connection header should be upgrade. Some proxy servers/load balancers
# might mess with it.
headers = self.request.headers
connection = map(lambda s: s.strip().lower(), headers.get("Connection", "").split(","))
if 'upgrade' not in connection:
self.set_status(400)
self.finish("\"Connection\" must be \"Upgrade\".")
return
# 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)
# If there was an origin header, check to make sure it matches
# according to check_origin. When the origin is None, we assume it
# did not come from a browser and that it can be passed on.
if origin is not None and not self.check_origin(origin):
self.set_status(403)
self.finish("Cross origin websockets not allowed")
return
self.stream = self.request.connection.detach()
self.stream.set_close_callback(self.on_connection_close)
if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
self.ws_connection = WebSocketProtocol13(self)
self.ws_connection.accept_connection()
#--------------- BEGIN PATCH ----------------
elif (self.allow_draft76() and
"Sec-WebSocket-Version" not in self.request.headers):
self.ws_connection = WebSocketProtocol76(self)
self.ws_connection.accept_connection()
#--------------- END PATCH ----------------
else:
if not self.stream.closed():
self.stream.write(tornado.escape.utf8(
"HTTP/1.1 426 Upgrade Required\r\n"
"Sec-WebSocket-Version: 8\r\n\r\n"))
self.stream.close()
# 3.2 methods removed in 4.0:
def allow_draft76(self):
"""Using this class allows draft76 connections by default"""
return True
def get_websocket_scheme(self):
"""Return the url scheme used for this request, either "ws" or "wss".
This is normally decided by HTTPServer, but applications
may wish to override this if they are using an SSL proxy
that does not provide the X-Scheme header as understood
by HTTPServer.
Note that this is only used by the draft76 protocol.
"""
return "wss" if self.request.protocol == "https" else "ws"
# No modifications from tornado-3.2.2 below this line
class WebSocketProtocol(object):
"""Base class for WebSocket protocol versions.
"""
def __init__(self, handler):
self.handler = handler
self.request = handler.request
self.stream = handler.stream
self.client_terminated = False
self.server_terminated = False
def async_callback(self, callback, *args, **kwargs):
"""Wrap callbacks with this if they are used on asynchronous requests.
Catches exceptions properly and closes this WebSocket if an exception
is uncaught.
"""
if args or kwargs:
callback = functools.partial(callback, *args, **kwargs)
def wrapper(*args, **kwargs):
try:
return callback(*args, **kwargs)
except Exception:
app_log.error("Uncaught exception in %s",
self.request.path, exc_info=True)
self._abort()
return wrapper
def on_connection_close(self):
self._abort()
def _abort(self):
"""Instantly aborts the WebSocket connection by closing the socket"""
self.client_terminated = True
self.server_terminated = True
self.stream.close() # forcibly tear down the connection
self.close() # let the subclass cleanup
class WebSocketProtocol76(WebSocketProtocol):
"""Implementation of the WebSockets protocol, version hixie-76.
This class provides basic functionality to process WebSockets requests as
specified in
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
"""
def __init__(self, handler):
WebSocketProtocol.__init__(self, handler)
self.challenge = None
self._waiting = None
def accept_connection(self):
try:
self._handle_websocket_headers()
except ValueError:
gen_log.debug("Malformed WebSocket request received")
self._abort()
return
scheme = self.handler.get_websocket_scheme()
# draft76 only allows a single subprotocol
subprotocol_header = ''
subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None)
if subprotocol:
selected = self.handler.select_subprotocol([subprotocol])
if selected:
assert selected == subprotocol
subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected
# Write the initial headers before attempting to read the challenge.
# This is necessary when using proxies (such as HAProxy), which
# need to see the Upgrade headers before passing through the
# non-HTTP traffic that follows.
self.stream.write(tornado.escape.utf8(
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Server: TornadoServer/%(version)s\r\n"
"Sec-WebSocket-Origin: %(origin)s\r\n"
"Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n"
"%(subprotocol)s"
"\r\n" % (dict(
version=tornado.version,
origin=self.request.headers["Origin"],
scheme=scheme,
host=self.request.host,
uri=self.request.uri,
subprotocol=subprotocol_header))))
self.stream.read_bytes(8, self._handle_challenge)
def challenge_response(self, challenge):
"""Generates the challenge response that's needed in the handshake
The challenge parameter should be the raw bytes as sent from the
client.
"""
key_1 = self.request.headers.get("Sec-Websocket-Key1")
key_2 = self.request.headers.get("Sec-Websocket-Key2")
try:
part_1 = self._calculate_part(key_1)
part_2 = self._calculate_part(key_2)
except ValueError:
raise ValueError("Invalid Keys/Challenge")
return self._generate_challenge_response(part_1, part_2, challenge)
def _handle_challenge(self, challenge):
try:
challenge_response = self.challenge_response(challenge)
except ValueError:
gen_log.debug("Malformed key data in WebSocket request")
self._abort()
return
self._write_response(challenge_response)
def _write_response(self, challenge):
self.stream.write(challenge)
self.async_callback(self.handler.open)(*self.handler.open_args, **self.handler.open_kwargs)
self._receive_message()
def _handle_websocket_headers(self):
"""Verifies all invariant- and required headers
If a header is missing or have an incorrect value ValueError will be
raised
"""
fields = ("Origin", "Host", "Sec-Websocket-Key1",
"Sec-Websocket-Key2")
if not all(map(lambda f: self.request.headers.get(f), fields)):
raise ValueError("Missing/Invalid WebSocket headers")
def _calculate_part(self, key):
"""Processes the key headers and calculates their key value.
Raises ValueError when feed invalid key."""
# pyflakes complains about variable reuse if both of these lines use 'c'
number = int(''.join(c for c in key if c.isdigit()))
spaces = len([c2 for c2 in key if c2.isspace()])
try:
key_number = number // spaces
except (ValueError, ZeroDivisionError):
raise ValueError
return struct.pack(">I", key_number)
def _generate_challenge_response(self, part_1, part_2, part_3):
m = hashlib.md5()
m.update(part_1)
m.update(part_2)
m.update(part_3)
return m.digest()
def _receive_message(self):
self.stream.read_bytes(1, self._on_frame_type)
def _on_frame_type(self, byte):
frame_type = ord(byte)
if frame_type == 0x00:
self.stream.read_until(b"\xff", self._on_end_delimiter)
elif frame_type == 0xff:
self.stream.read_bytes(1, self._on_length_indicator)
else:
self._abort()
def _on_end_delimiter(self, frame):
if not self.client_terminated:
self.async_callback(self.handler.on_message)(
frame[:-1].decode("utf-8", "replace"))
if not self.client_terminated:
self._receive_message()
def _on_length_indicator(self, byte):
if ord(byte) != 0x00:
self._abort()
return
self.client_terminated = True
self.close()
def write_message(self, message, binary=False):
"""Sends the given message to the client of this Web Socket."""
if binary:
raise ValueError(
"Binary messages not supported by this version of websockets")
if isinstance(message, unicode_type):
message = message.encode("utf-8")
assert isinstance(message, bytes_type)
self.stream.write(b"\x00" + message + b"\xff")
def write_ping(self, data):
"""Send ping frame."""
raise ValueError("Ping messages not supported by this version of websockets")
def close(self):
"""Closes the WebSocket connection."""
if not self.server_terminated:
if not self.stream.closed():
self.stream.write("\xff\x00")
self.server_terminated = True
if self.client_terminated:
if self._waiting is not None:
self.stream.io_loop.remove_timeout(self._waiting)
self._waiting = None
self.stream.close()
elif self._waiting is None:
self._waiting = self.stream.io_loop.add_timeout(
time.time() + 5, self._abort)

@ -5,6 +5,7 @@
import functools
import json
import mimetypes
import os
import re
import sys
@ -39,7 +40,12 @@ from notebook.services.security import csp_report_uri
#-----------------------------------------------------------------------------
non_alphanum = re.compile(r'[^A-Za-z0-9]')
sys_info = json.dumps(get_sys_info())
_sys_info_cache = None
def json_sys_info():
global _sys_info_cache
if _sys_info_cache is None:
_sys_info_cache = json.dumps(get_sys_info())
return _sys_info_cache
def log():
if Application.initialized():
@ -56,20 +62,24 @@ class AuthenticatedHandler(web.RequestHandler):
Can be overridden by defining Content-Security-Policy in settings['headers']
"""
if 'Content-Security-Policy' in self.settings.get('headers', {}):
# user-specified, don't override
return self.settings['headers']['Content-Security-Policy']
return '; '.join([
"frame-ancestors 'self'",
# Make sure the report-uri is relative to the base_url
"report-uri " + url_path_join(self.base_url, csp_report_uri),
"report-uri " + self.settings.get('csp_report_uri', url_path_join(self.base_url, csp_report_uri)),
])
def set_default_headers(self):
headers = self.settings.get('headers', {})
headers = {}
headers.update(self.settings.get('headers', {}))
if "Content-Security-Policy" not in headers:
headers["Content-Security-Policy"] = self.content_security_policy
headers["Content-Security-Policy"] = self.content_security_policy
# Allow for overriding headers
for header_name,value in headers.items() :
for header_name, value in headers.items():
try:
self.set_header(header_name, value)
except Exception as e:
@ -357,7 +367,7 @@ class IPythonHandler(AuthenticatedHandler):
login_available=self.login_available,
token_available=bool(self.token or self.one_time_token),
static_url=self.static_url,
sys_info=sys_info,
sys_info=json_sys_info(),
contents_js_source=self.contents_js_source,
version_hash=self.version_hash,
ignore_minified_js=self.ignore_minified_js,
@ -466,13 +476,27 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
@web.authenticated
def get(self, path):
if os.path.splitext(path)[1] == '.ipynb':
if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False):
name = path.rsplit('/', 1)[-1]
self.set_header('Content-Type', 'application/json')
self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name))
return web.StaticFileHandler.get(self, path)
def get_content_type(self):
path = self.absolute_path.strip('/')
if '/' in path:
_, name = path.rsplit('/', 1)
else:
name = path
if name.endswith('.ipynb'):
return 'application/x-ipynb+json'
else:
cur_mime = mimetypes.guess_type(name)[0]
if cur_mime == 'text/plain':
return 'text/plain; charset=UTF-8'
else:
return super(AuthenticatedFileHandler, self).get_content_type()
def set_headers(self):
super(AuthenticatedFileHandler, self).set_headers()
# disable browser caching, rely on 304 replies for savings
@ -684,5 +708,6 @@ path_regex = r"(?P<path>(?:(?:/[^/]+)+|/?))"
default_handlers = [
(r".*/", TrailingSlashHandler),
(r"api", APIVersionHandler)
(r"api", APIVersionHandler),
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler),
]

@ -87,14 +87,6 @@ def deserialize_binary_message(bmsg):
# ping interval for keeping websockets alive (30 seconds)
WS_PING_INTERVAL = 30000
if os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False):
warnings.warn("""Allowing draft76 websocket connections!
This should only be done for testing with phantomjs!""")
from notebook import allow76
WebSocketHandler = allow76.AllowDraftWebSocketHandler
# draft 76 doesn't support ping
WS_PING_INTERVAL = 0
class WebSocketMixin(object):
"""Mixin for common websocket options"""
@ -296,5 +288,4 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
self.session = Session(config=self.config)
def get_compression_options(self):
# use deflate compress websocket
return {}
return self.settings.get('websocket_compression_options', None)

@ -26,6 +26,13 @@ class FilesHandler(IPythonHandler):
@web.authenticated
def get(self, path, include_body=True):
cm = self.contents_manager
if cm.files_handler_class:
return cm.files_handler_class(self.application, self.request, path=cm.root_dir)._execute(
[t(self.request) for t in self.application.transforms],
path
)
if cm.is_hidden(path):
self.log.info("Refusing to serve hidden file, via 404 Error")
raise web.HTTPError(404)
@ -46,13 +53,15 @@ class FilesHandler(IPythonHandler):
self.set_header('Content-Type', 'application/x-ipynb+json')
else:
cur_mime = mimetypes.guess_type(name)[0]
if cur_mime is not None:
if cur_mime == 'text/plain':
self.set_header('Content-Type', 'text/plain; charset=UTF-8')
elif cur_mime is not None:
self.set_header('Content-Type', cur_mime)
else:
if model['format'] == 'base64':
self.set_header('Content-Type', 'application/octet-stream')
else:
self.set_header('Content-Type', 'text/plain')
self.set_header('Content-Type', 'text/plain; charset=UTF-8')
if include_body:
if model['format'] == 'base64':

@ -0,0 +1,122 @@
# Implementation Notes for Internationalization of Jupyter Notebook
This is a prototype implementation of i18n features for Jupyter notebook, and should not
yet be considered ready for production use. I have tried to focus on the public user
interfaces in the notebook for the first cut, while leaving much of the console messages
behind, as their usefulness in a translated environment is questionable at best.
### Using a prototype translated version
In order to use this preliminary version, you need to do things after installing the
notebook as normal:
1. Set the LANG environment variable in your shell to "xx_XX" or just "xx".
where "xx" is the language code you're wanting to run in. If you're
running on Windows, I've found the easiest way to do this is to use Windows PowerShell,
and run the command:
`${Env:LANG} = "xx_XX"`
2. Set the preferred language for web pages in your browser to YourLanguage (xx). At the moment,
it has to be first in the list.
3. Run the `jupyter notebook` command to start the notebook.
### Message extraction:
I have split out the translatable material for the notebook into 3 POT, as follows:
notebook/i18n/notebook.pot - Console and startup messages, basically anything that is
produced by Python code.
notebook/i18n/nbui.pot - User interface strings, as extracted from the Jinja2 templates
in notebook/templates/*.html
noteook/i18n/nbjs.pot - JavaScript strings and dialogs, which contain much of the visible
user interface for Jupyter notebook.
To extract the messages from the source code whenever new material is added, use the
`pybabel` command to extract messages from the source code as follows:
( assuming you are in the base directory for Jupyter notebook )
`pybabel extract -F notebook/i18n/babel_notebook.cfg -o notebook/i18n/notebook.pot --no-wrap --project Jupyter .`
`pybabel extract -F notebook/i18n/babel_nbui.cfg -o notebook/i18n/nbui.pot --no-wrap --project Jupyter .`
`pybabel extract -F notebook/i18n/babel_nbjs.cfg -o notebook/i18n/nbjs.pot --no-wrap --project Jupyter .`
(Note: there is a '.' at the end of these commands, and it has to be there...)
After this is complete you have 3 POT files that you can give to a translator for your favorite language.
Babel's documentation has instructions on how to integrate this into your setup.py so that eventually
we can just do:
`setup.py extract_messages`
I hope to get this working at some point in the near future.
### Post translation procedures
After the source material has been translated, you should have 3 PO files with the same base names
as the POT files above. Put them in `notebook/i18n/${LANG}/LC_MESSAGES`, where ${LANG} is the language
code for your desired language ( i.e. German = "de", Japanese = "ja", etc. ). The first 2 files then
need to be converted from PO to MO format for use at runtime. There are many different ways to do
this, but pybabel has an option to do this as follows:
`pybabel compile -D notebook -f -l ${LANG} -i notebook/i18n/${LANG}/LC_MESSAGES/notebook.po -o notebook/i18n/${LANG}/notebook.mo`
`pybabel compile -D nbui -f -l ${LANG} -i notebook/i18n/${LANG}/LC_MESSAGES/nbui.po -o notebook/i18n/${LANG}/nbui.mo`
The nbjs.po needs to be converted to JSON for use within the JavaScript code. I'm using po2json for this, as follows:
`po2json -p -F -f jed1.x -d nbjs notebook/i18n/${LANG}/LC_MESSAGES/nbjs.po notebook/i18n/${LANG}/LC_MESSAGES/nbjs.json`
The conversions from PO to MO probably can and should be done during setup.py.
When new languages get added, their language codes should be added to notebook/i18n/nbjs.json
under the "supported_languages" element.
### Tips for Jupyter developers
The biggest "mistake" I found while doing i18n enablement was the habit of constructing UI messages
from English "piece parts". For example, code like:
`var msg = "Enter a new " + type + "name:"`
where "type" is either "file", "directory", or "notebook"....
is problematic when doing translations, because the surrounding text may need to vary
depending on the inserted word. In this case, you need to switch it and use complete phrases,
as follows:
```javascript
var rename_msg = function (type) {
switch(type) {
case 'file': return _("Enter a new file name:");
case 'directory': return _("Enter a new directory name:");
case 'notebook': return _("Enter a new notebook name:");
default: return _("Enter a new name:");
}
}
```
Also you need to remember that adding an "s" or "es" to an English word to
create the plural form doesn't translate well. Some languages have as many as 5 or 6 different
plural forms for differing numbers, so using an API such as ngettext() is necessary in order
to handle these cases properly.
### Known issues
1. Right now there are two different places where the desired language is set. At startup time, the Jupyter console's messages pay attention to the setting of the ${LANG} environment variable
as set in the shell at startup time. Unfortunately, this is also the time where the Jinja2
environment is set up, which means that the template stuff will always come from this setting.
We really want to be paying attention to the browser's settings for the stuff that happens in the
browser, so we need to be able to retrieve this information after the browser is started and somehow
communicate this back to Jinja2. So far, I haven't yet figured out how to do this, which means that if the ${LANG} at startup doesn't match the browser's settings, you could potentially get a mix
of languages in the UI ( never a good thing ).
2. We will need to decide if console messages should be translatable, and enable them if desired.
3. The keyboard shorcut editor was implemented after the i18n work was completed, so that portion
does not have translation support at this time.
Any questions or comments please let me know @JCEmmons on github (emmo@us.ibm.com)

@ -0,0 +1,11 @@
[javascript: notebook/static/base/js/*.js]
extract_messages = $._, i18n.msg._
[javascript: notebook/static/notebook/js/*.js]
extract_messages = $._, i18n.msg._
[javascript: notebook/static/notebook/js/celltoolbarpresets/*.js]
extract_messages = $._, i18n.msg._
[javascript: notebook/static/tree/js/*.js]
extract_messages = $._, i18n.msg._

@ -0,0 +1,4 @@
[jinja2: notebook/templates/**.html]
encoding = utf-8
[extractors]
jinja2 = jinja2.ext:babel_extract

@ -0,0 +1,2 @@
[python: notebook/*.py]
[python: notebook/services/contents/*.py]

@ -0,0 +1,12 @@
{
"domain": "nbjs",
"supported_languages": [
],
"locale_data": {
"nbjs": {
"": {
"domain": "nbjs"
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,731 @@
# Translations template for Jupyter.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the Jupyter project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Jupyter VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-07-07 12:48-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: notebook/templates/404.html:3
msgid "You are requesting a page that does not exist!"
msgstr ""
#: notebook/templates/edit.html:37
msgid "current mode"
msgstr ""
#: notebook/templates/edit.html:48 notebook/templates/notebook.html:78
msgid "File"
msgstr ""
#: notebook/templates/edit.html:50 notebook/templates/tree.html:57
msgid "New"
msgstr ""
#: notebook/templates/edit.html:51
msgid "Save"
msgstr ""
#: notebook/templates/edit.html:52 notebook/templates/tree.html:36
msgid "Rename"
msgstr ""
#: notebook/templates/edit.html:53 notebook/templates/tree.html:38
msgid "Download"
msgstr ""
#: notebook/templates/edit.html:56 notebook/templates/notebook.html:131
#: notebook/templates/tree.html:41
msgid "Edit"
msgstr ""
#: notebook/templates/edit.html:58
msgid "Find"
msgstr ""
#: notebook/templates/edit.html:59
msgid "Find &amp; Replace"
msgstr ""
#: notebook/templates/edit.html:61
msgid "Key Map"
msgstr ""
#: notebook/templates/edit.html:62
msgid "Default"
msgstr ""
#: notebook/templates/edit.html:63
msgid "Sublime Text"
msgstr ""
#: notebook/templates/edit.html:68 notebook/templates/notebook.html:159
#: notebook/templates/tree.html:40
msgid "View"
msgstr ""
#: notebook/templates/edit.html:70 notebook/templates/notebook.html:162
msgid "Show/Hide the logo and notebook title (above menu bar)"
msgstr ""
#: notebook/templates/edit.html:71 notebook/templates/notebook.html:163
msgid "Toggle Header"
msgstr ""
#: notebook/templates/edit.html:72 notebook/templates/notebook.html:171
msgid "Toggle Line Numbers"
msgstr ""
#: notebook/templates/edit.html:75
msgid "Language"
msgstr ""
#: notebook/templates/error.html:23
msgid "The error was:"
msgstr ""
#: notebook/templates/login.html:24
msgid "Password or token:"
msgstr ""
#: notebook/templates/login.html:26
msgid "Password:"
msgstr ""
#: notebook/templates/login.html:31
msgid "Log in"
msgstr ""
#: notebook/templates/login.html:39
msgid "No login available, you shouldn't be seeing this page."
msgstr ""
#: notebook/templates/logout.html:24
#, python-format
msgid "Proceed to the <a href=\"%(base_url)s\">dashboard"
msgstr ""
#: notebook/templates/logout.html:26
#, python-format
msgid "Proceed to the <a href=\"%(base_url)slogin\">login page"
msgstr ""
#: notebook/templates/notebook.html:62
msgid "Menu"
msgstr ""
#: notebook/templates/notebook.html:65 notebook/templates/notebook.html:254
msgid "Kernel"
msgstr ""
#: notebook/templates/notebook.html:68
msgid "This notebook is read-only"
msgstr ""
#: notebook/templates/notebook.html:81
msgid "New Notebook"
msgstr ""
#: notebook/templates/notebook.html:85
msgid "Opens a new window with the Dashboard view"
msgstr ""
#: notebook/templates/notebook.html:86
msgid "Open..."
msgstr ""
#: notebook/templates/notebook.html:90
msgid "Open a copy of this notebook's contents and start a new kernel"
msgstr ""
#: notebook/templates/notebook.html:91
msgid "Make a Copy..."
msgstr ""
#: notebook/templates/notebook.html:92
msgid "Rename..."
msgstr ""
#: notebook/templates/notebook.html:93
msgid "Save and Checkpoint"
msgstr ""
#: notebook/templates/notebook.html:96
msgid "Revert to Checkpoint"
msgstr ""
#: notebook/templates/notebook.html:106
msgid "Print Preview"
msgstr ""
#: notebook/templates/notebook.html:107
msgid "Download as"
msgstr ""
#: notebook/templates/notebook.html:109
msgid "Notebook (.ipynb)"
msgstr ""
#: notebook/templates/notebook.html:110
msgid "Script"
msgstr ""
#: notebook/templates/notebook.html:111
msgid "HTML (.html)"
msgstr ""
#: notebook/templates/notebook.html:112
msgid "Markdown (.md)"
msgstr ""
#: notebook/templates/notebook.html:113
msgid "reST (.rst)"
msgstr ""
#: notebook/templates/notebook.html:114
msgid "LaTeX (.tex)"
msgstr ""
#: notebook/templates/notebook.html:115
msgid "PDF via LaTeX (.pdf)"
msgstr ""
#: notebook/templates/notebook.html:118
msgid "Deploy as"
msgstr ""
#: notebook/templates/notebook.html:123
msgid "Trust the output of this notebook"
msgstr ""
#: notebook/templates/notebook.html:124
msgid "Trust Notebook"
msgstr ""
#: notebook/templates/notebook.html:127
msgid "Shutdown this notebook's kernel, and close this window"
msgstr ""
#: notebook/templates/notebook.html:128
msgid "Close and Halt"
msgstr ""
#: notebook/templates/notebook.html:133
msgid "Cut Cells"
msgstr ""
#: notebook/templates/notebook.html:134
msgid "Copy Cells"
msgstr ""
#: notebook/templates/notebook.html:135
msgid "Paste Cells Above"
msgstr ""
#: notebook/templates/notebook.html:136
msgid "Paste Cells Below"
msgstr ""
#: notebook/templates/notebook.html:137
msgid "Paste Cells &amp; Replace"
msgstr ""
#: notebook/templates/notebook.html:138
msgid "Delete Cells"
msgstr ""
#: notebook/templates/notebook.html:139
msgid "Undo Delete Cells"
msgstr ""
#: notebook/templates/notebook.html:141
msgid "Split Cell"
msgstr ""
#: notebook/templates/notebook.html:142
msgid "Merge Cell Above"
msgstr ""
#: notebook/templates/notebook.html:143
msgid "Merge Cell Below"
msgstr ""
#: notebook/templates/notebook.html:145
msgid "Move Cell Up"
msgstr ""
#: notebook/templates/notebook.html:146
msgid "Move Cell Down"
msgstr ""
#: notebook/templates/notebook.html:148
msgid "Edit Notebook Metadata"
msgstr ""
#: notebook/templates/notebook.html:150
msgid "Find and Replace"
msgstr ""
#: notebook/templates/notebook.html:152
msgid "Cut Cell Attachments"
msgstr ""
#: notebook/templates/notebook.html:153
msgid "Copy Cell Attachments"
msgstr ""
#: notebook/templates/notebook.html:154
msgid "Paste Cell Attachments"
msgstr ""
#: notebook/templates/notebook.html:156
msgid "Insert Image"
msgstr ""
#: notebook/templates/notebook.html:166
msgid "Show/Hide the action icons (below menu bar)"
msgstr ""
#: notebook/templates/notebook.html:167
msgid "Toggle Toolbar"
msgstr ""
#: notebook/templates/notebook.html:170
msgid "Show/Hide line numbers in cells"
msgstr ""
#: notebook/templates/notebook.html:174
msgid "Cell Toolbar"
msgstr ""
#: notebook/templates/notebook.html:179
msgid "Insert"
msgstr ""
#: notebook/templates/notebook.html:182
msgid "Insert an empty Code cell above the currently active cell"
msgstr ""
#: notebook/templates/notebook.html:183
msgid "Insert Cell Above"
msgstr ""
#: notebook/templates/notebook.html:185
msgid "Insert an empty Code cell below the currently active cell"
msgstr ""
#: notebook/templates/notebook.html:186
msgid "Insert Cell Below"
msgstr ""
#: notebook/templates/notebook.html:189
msgid "Cell"
msgstr ""
#: notebook/templates/notebook.html:191
msgid "Run this cell, and move cursor to the next one"
msgstr ""
#: notebook/templates/notebook.html:192
msgid "Run Cells"
msgstr ""
#: notebook/templates/notebook.html:193
msgid "Run this cell, select below"
msgstr ""
#: notebook/templates/notebook.html:194
msgid "Run Cells and Select Below"
msgstr ""
#: notebook/templates/notebook.html:195
msgid "Run this cell, insert below"
msgstr ""
#: notebook/templates/notebook.html:196
msgid "Run Cells and Insert Below"
msgstr ""
#: notebook/templates/notebook.html:197
msgid "Run all cells in the notebook"
msgstr ""
#: notebook/templates/notebook.html:198
msgid "Run All"
msgstr ""
#: notebook/templates/notebook.html:199
msgid "Run all cells above (but not including) this cell"
msgstr ""
#: notebook/templates/notebook.html:200
msgid "Run All Above"
msgstr ""
#: notebook/templates/notebook.html:201
msgid "Run this cell and all cells below it"
msgstr ""
#: notebook/templates/notebook.html:202
msgid "Run All Below"
msgstr ""
#: notebook/templates/notebook.html:205
msgid "All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells"
msgstr ""
#: notebook/templates/notebook.html:206
msgid "Cell Type"
msgstr ""
#: notebook/templates/notebook.html:209
msgid "Contents will be sent to the kernel for execution, and output will display in the footer of cell"
msgstr ""
#: notebook/templates/notebook.html:212
msgid "Contents will be rendered as HTML and serve as explanatory text"
msgstr ""
#: notebook/templates/notebook.html:213 notebook/templates/notebook.html:298
msgid "Markdown"
msgstr ""
#: notebook/templates/notebook.html:215
msgid "Contents will pass through nbconvert unmodified"
msgstr ""
#: notebook/templates/notebook.html:216
msgid "Raw NBConvert"
msgstr ""
#: notebook/templates/notebook.html:220
msgid "Current Outputs"
msgstr ""
#: notebook/templates/notebook.html:223
msgid "Hide/Show the output of the current cell"
msgstr ""
#: notebook/templates/notebook.html:224 notebook/templates/notebook.html:240
msgid "Toggle"
msgstr ""
#: notebook/templates/notebook.html:227
msgid "Scroll the output of the current cell"
msgstr ""
#: notebook/templates/notebook.html:228 notebook/templates/notebook.html:244
msgid "Toggle Scrolling"
msgstr ""
#: notebook/templates/notebook.html:231
msgid "Clear the output of the current cell"
msgstr ""
#: notebook/templates/notebook.html:232 notebook/templates/notebook.html:248
msgid "Clear"
msgstr ""
#: notebook/templates/notebook.html:236
msgid "All Output"
msgstr ""
#: notebook/templates/notebook.html:239
msgid "Hide/Show the output of all cells"
msgstr ""
#: notebook/templates/notebook.html:243
msgid "Scroll the output of all cells"
msgstr ""
#: notebook/templates/notebook.html:247
msgid "Clear the output of all cells"
msgstr ""
#: notebook/templates/notebook.html:257
msgid "Send Keyboard Interrupt (CTRL-C) to the Kernel"
msgstr ""
#: notebook/templates/notebook.html:258
msgid "Interrupt"
msgstr ""
#: notebook/templates/notebook.html:261
msgid "Restart the Kernel"
msgstr ""
#: notebook/templates/notebook.html:262
msgid "Restart"
msgstr ""
#: notebook/templates/notebook.html:265
msgid "Restart the Kernel and clear all output"
msgstr ""
#: notebook/templates/notebook.html:266
msgid "Restart &amp; Clear Output"
msgstr ""
#: notebook/templates/notebook.html:269
msgid "Restart the Kernel and re-run the notebook"
msgstr ""
#: notebook/templates/notebook.html:270
msgid "Restart &amp; Run All"
msgstr ""
#: notebook/templates/notebook.html:273
msgid "Reconnect to the Kernel"
msgstr ""
#: notebook/templates/notebook.html:274
msgid "Reconnect"
msgstr ""
#: notebook/templates/notebook.html:282
msgid "Change kernel"
msgstr ""
#: notebook/templates/notebook.html:287
msgid "Help"
msgstr ""
#: notebook/templates/notebook.html:290
msgid "A quick tour of the notebook user interface"
msgstr ""
#: notebook/templates/notebook.html:290
msgid "User Interface Tour"
msgstr ""
#: notebook/templates/notebook.html:291
msgid "Opens a tooltip with all keyboard shortcuts"
msgstr ""
#: notebook/templates/notebook.html:291
msgid "Keyboard Shortcuts"
msgstr ""
#: notebook/templates/notebook.html:292
msgid "Opens a dialog allowing you to edit Keyboard shortcuts"
msgstr ""
#: notebook/templates/notebook.html:292
msgid "Edit Keyboard Shortcuts"
msgstr ""
#: notebook/templates/notebook.html:297
msgid "Notebook Help"
msgstr ""
#: notebook/templates/notebook.html:303
msgid "Opens in a new window"
msgstr ""
#: notebook/templates/notebook.html:319
msgid "About Jupyter Notebook"
msgstr ""
#: notebook/templates/notebook.html:319
msgid "About"
msgstr ""
#: notebook/templates/page.html:114
msgid "Jupyter Notebook requires JavaScript."
msgstr ""
#: notebook/templates/page.html:115
msgid "Please enable it to proceed. "
msgstr ""
#: notebook/templates/page.html:121
msgid "dashboard"
msgstr ""
#: notebook/templates/page.html:132
msgid "Logout"
msgstr ""
#: notebook/templates/page.html:134
msgid "Login"
msgstr ""
#: notebook/templates/tree.html:23
msgid "Files"
msgstr ""
#: notebook/templates/tree.html:24
msgid "Running"
msgstr ""
#: notebook/templates/tree.html:25
msgid "Clusters"
msgstr ""
#: notebook/templates/tree.html:32
msgid "Select items to perform actions on them."
msgstr ""
#: notebook/templates/tree.html:35
msgid "Duplicate selected"
msgstr ""
#: notebook/templates/tree.html:35
msgid "Duplicate"
msgstr ""
#: notebook/templates/tree.html:36
msgid "Rename selected"
msgstr ""
#: notebook/templates/tree.html:37
msgid "Move selected"
msgstr ""
#: notebook/templates/tree.html:37
msgid "Move"
msgstr ""
#: notebook/templates/tree.html:38
msgid "Download selected"
msgstr ""
#: notebook/templates/tree.html:39
msgid "Shutdown selected notebook(s)"
msgstr ""
#: notebook/templates/tree.html:39
msgid "Shutdown"
msgstr ""
#: notebook/templates/tree.html:40
msgid "View selected"
msgstr ""
#: notebook/templates/tree.html:41
msgid "Edit selected"
msgstr ""
#: notebook/templates/tree.html:42
msgid "Delete selected"
msgstr ""
#: notebook/templates/tree.html:50
msgid "Click to browse for a file to upload."
msgstr ""
#: notebook/templates/tree.html:51
msgid "Upload"
msgstr ""
#: notebook/templates/tree.html:65
msgid "Text File"
msgstr ""
#: notebook/templates/tree.html:68
msgid "Folder"
msgstr ""
#: notebook/templates/tree.html:72
msgid "Terminal"
msgstr ""
#: notebook/templates/tree.html:76
msgid "Terminals Unavailable"
msgstr ""
#: notebook/templates/tree.html:82
msgid "Refresh notebook list"
msgstr ""
#: notebook/templates/tree.html:90
msgid "Select All / None"
msgstr ""
#: notebook/templates/tree.html:93
msgid "Select..."
msgstr ""
#: notebook/templates/tree.html:98
msgid "Select All Folders"
msgstr ""
#: notebook/templates/tree.html:98
msgid " Folders"
msgstr ""
#: notebook/templates/tree.html:99
msgid "Select All Notebooks"
msgstr ""
#: notebook/templates/tree.html:99
msgid " All Notebooks"
msgstr ""
#: notebook/templates/tree.html:100
msgid "Select Running Notebooks"
msgstr ""
#: notebook/templates/tree.html:100
msgid " Running"
msgstr ""
#: notebook/templates/tree.html:101
msgid "Select All Files"
msgstr ""
#: notebook/templates/tree.html:101
msgid " Files"
msgstr ""
#: notebook/templates/tree.html:114
msgid "Last Modified"
msgstr ""
#: notebook/templates/tree.html:120
msgid "Name"
msgstr ""
#: notebook/templates/tree.html:130
msgid "Currently running Jupyter processes"
msgstr ""
#: notebook/templates/tree.html:134
msgid "Refresh running list"
msgstr ""
#: notebook/templates/tree.html:150
msgid "There are no terminals running."
msgstr ""
#: notebook/templates/tree.html:152
msgid "Terminals are unavailable."
msgstr ""
#: notebook/templates/tree.html:162
msgid "Notebooks"
msgstr ""
#: notebook/templates/tree.html:169
msgid "There are no notebooks running."
msgstr ""
#: notebook/templates/tree.html:178
msgid "Clusters tab is now provided by IPython parallel."
msgstr ""
#: notebook/templates/tree.html:179
msgid "See '<a href=\"https://github.com/ipython/ipyparallel\">IPython parallel</a>' for installation details."
msgstr ""

@ -0,0 +1,480 @@
# Translations template for Jupyter.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the Jupyter project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Jupyter VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-07-08 21:52-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: notebook/notebookapp.py:53
msgid "The Jupyter Notebook requires tornado >= 4.0"
msgstr ""
#: notebook/notebookapp.py:57
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0"
msgstr ""
#: notebook/notebookapp.py:59
#, python-format
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have %s"
msgstr ""
#: notebook/notebookapp.py:209
msgid "The `ignore_minified_js` flag is deprecated and no longer works."
msgstr ""
#: notebook/notebookapp.py:210
#, python-format
msgid "Alternatively use `%s` when working on the notebook's Javascript and LESS"
msgstr ""
#: notebook/notebookapp.py:211
msgid "The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
msgstr ""
#: notebook/notebookapp.py:389
msgid "List currently running notebook servers."
msgstr ""
#: notebook/notebookapp.py:393
msgid "Produce machine-readable JSON output."
msgstr ""
#: notebook/notebookapp.py:397
msgid "If True, each line of output will be a JSON object with the details from the server info file."
msgstr ""
#: notebook/notebookapp.py:402
msgid "Currently running servers:"
msgstr ""
#: notebook/notebookapp.py:419
msgid "Don't open the notebook in a browser after startup."
msgstr ""
#: notebook/notebookapp.py:423
msgid "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
msgstr ""
#: notebook/notebookapp.py:439
msgid "Allow the notebook to be run from root user."
msgstr ""
#: notebook/notebookapp.py:470
msgid ""
"The Jupyter HTML Notebook.\n"
" \n"
" This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client."
msgstr ""
#: notebook/notebookapp.py:509
msgid "Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation"
msgstr ""
#: notebook/notebookapp.py:540
msgid "Set the Access-Control-Allow-Credentials: true header"
msgstr ""
#: notebook/notebookapp.py:544
msgid "Whether to allow the user to run the notebook as root."
msgstr ""
#: notebook/notebookapp.py:548
msgid "The default URL to redirect to from `/`"
msgstr ""
#: notebook/notebookapp.py:552
msgid "The IP address the notebook server will listen on."
msgstr ""
#: notebook/notebookapp.py:565
#, python-format
msgid ""
"Cannot bind to localhost, using 127.0.0.1 as default ip\n"
"%s"
msgstr ""
#: notebook/notebookapp.py:579
msgid "The port the notebook server will listen on."
msgstr ""
#: notebook/notebookapp.py:583
msgid "The number of additional ports to try if the specified port is not available."
msgstr ""
#: notebook/notebookapp.py:587
msgid "The full path to an SSL/TLS certificate file."
msgstr ""
#: notebook/notebookapp.py:591
msgid "The full path to a private key file for usage with SSL/TLS."
msgstr ""
#: notebook/notebookapp.py:595
msgid "The full path to a certificate authority certificate for SSL/TLS client authentication."
msgstr ""
#: notebook/notebookapp.py:599
msgid "The file where the cookie secret is stored."
msgstr ""
#: notebook/notebookapp.py:628
#, python-format
msgid "Writing notebook server cookie secret to %s"
msgstr ""
#: notebook/notebookapp.py:635
#, python-format
msgid "Could not set permissions on %s"
msgstr ""
#: notebook/notebookapp.py:640
msgid ""
"Token used for authenticating first-time connections to the server.\n"
"\n"
" When no password is enabled,\n"
" the default is to generate a new, random token.\n"
"\n"
" Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.\n"
" "
msgstr ""
#: notebook/notebookapp.py:650
msgid ""
"One-time token used for opening a browser.\n"
" Once used, this token cannot be used again.\n"
" "
msgstr ""
#: notebook/notebookapp.py:726
msgid ""
"Specify Where to open the notebook on startup. This is the\n"
" `new` argument passed to the standard library method `webbrowser.open`.\n"
" The behaviour is not guaranteed, but depends on browser support. Valid\n"
" values are:\n"
" 2 opens a new tab,\n"
" 1 opens a new window,\n"
" 0 opens in an existing window.\n"
" See the `webbrowser.open` documentation for details.\n"
" "
msgstr ""
#: notebook/notebookapp.py:737
msgid "DEPRECATED, use tornado_settings"
msgstr ""
#: notebook/notebookapp.py:742
msgid ""
"\n"
" webapp_settings is deprecated, use tornado_settings.\n"
msgstr ""
#: notebook/notebookapp.py:746
msgid "Supply overrides for the tornado.web.Application that the Jupyter notebook uses."
msgstr ""
#: notebook/notebookapp.py:750
msgid ""
"\n"
" Set the tornado compression options for websocket connections.\n"
"\n"
" This value will be returned from :meth:`WebSocketHandler.get_compression_options`.\n"
" None (default) will disable compression.\n"
" A dict (even an empty one) will enable compression.\n"
"\n"
" See the tornado docs for WebSocketHandler.get_compression_options for details.\n"
" "
msgstr ""
#: notebook/notebookapp.py:761
msgid "Supply overrides for terminado. Currently only supports \"shell_command\"."
msgstr ""
#: notebook/notebookapp.py:764
msgid "Extra keyword arguments to pass to `set_secure_cookie`. See tornado's set_secure_cookie docs for details."
msgstr ""
#: notebook/notebookapp.py:768
msgid ""
"Supply SSL options for the tornado HTTPServer.\n"
" See the tornado docs for details."
msgstr ""
#: notebook/notebookapp.py:772
msgid "Supply extra arguments that will be passed to Jinja environment."
msgstr ""
#: notebook/notebookapp.py:776
msgid "Extra variables to supply to jinja templates when rendering."
msgstr ""
#: notebook/notebookapp.py:812
msgid "DEPRECATED use base_url"
msgstr ""
#: notebook/notebookapp.py:816
msgid "base_project_url is deprecated, use base_url"
msgstr ""
#: notebook/notebookapp.py:832
msgid "Path to search for custom.js, css"
msgstr ""
#: notebook/notebookapp.py:844
msgid ""
"Extra paths to search for serving jinja templates.\n"
"\n"
" Can be used to override templates from notebook.templates."
msgstr ""
#: notebook/notebookapp.py:855
msgid "extra paths to look for Javascript notebook extensions"
msgstr ""
#: notebook/notebookapp.py:900
#, python-format
msgid "Using MathJax: %s"
msgstr ""
#: notebook/notebookapp.py:903
msgid "The MathJax.js configuration file that is to be used."
msgstr ""
#: notebook/notebookapp.py:908
#, python-format
msgid "Using MathJax configuration file: %s"
msgstr ""
#: notebook/notebookapp.py:914
msgid "The notebook manager class to use."
msgstr ""
#: notebook/notebookapp.py:920
msgid "The kernel manager class to use."
msgstr ""
#: notebook/notebookapp.py:926
msgid "The session manager class to use."
msgstr ""
#: notebook/notebookapp.py:932
msgid "The config manager class to use"
msgstr ""
#: notebook/notebookapp.py:953
msgid "The login handler class to use."
msgstr ""
#: notebook/notebookapp.py:960
msgid "The logout handler class to use."
msgstr ""
#: notebook/notebookapp.py:964
msgid "Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headerssent by the upstream reverse proxy. Necessary if the proxy handles SSL"
msgstr ""
#: notebook/notebookapp.py:976
msgid ""
"\n"
" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.\n"
" "
msgstr ""
#: notebook/notebookapp.py:988
msgid "Support for specifying --pylab on the command line has been removed."
msgstr ""
#: notebook/notebookapp.py:990
msgid "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself."
msgstr ""
#: notebook/notebookapp.py:995
msgid "The directory to use for notebooks and kernels."
msgstr ""
#: notebook/notebookapp.py:1018
#, python-format
msgid "No such notebook dir: '%r'"
msgstr ""
#: notebook/notebookapp.py:1031
msgid "DEPRECATED use the nbserver_extensions dict instead"
msgstr ""
#: notebook/notebookapp.py:1036
msgid "server_extensions is deprecated, use nbserver_extensions"
msgstr ""
#: notebook/notebookapp.py:1040
msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order."
msgstr ""
#: notebook/notebookapp.py:1049
msgid "Reraise exceptions encountered loading server extensions?"
msgstr ""
#: notebook/notebookapp.py:1052
msgid ""
"(msgs/sec)\n"
" Maximum rate at which messages can be sent on iopub before they are\n"
" limited."
msgstr ""
#: notebook/notebookapp.py:1056
msgid ""
"(bytes/sec)\n"
" Maximum rate at which stream output can be sent on iopub before they are\n"
" limited."
msgstr ""
#: notebook/notebookapp.py:1060
msgid ""
"(sec) Time window used to \n"
" check the message and data rate limits."
msgstr ""
#: notebook/notebookapp.py:1071
#, python-format
msgid "No such file or directory: %s"
msgstr ""
#: notebook/notebookapp.py:1141
msgid "Notebook servers are configured to only be run with a password."
msgstr ""
#: notebook/notebookapp.py:1142
msgid "Hint: run the following command to set a password"
msgstr ""
#: notebook/notebookapp.py:1143
msgid "\t$ python -m notebook.auth password"
msgstr ""
#: notebook/notebookapp.py:1181
#, python-format
msgid "The port %i is already in use, trying another port."
msgstr ""
#: notebook/notebookapp.py:1184
#, python-format
msgid "Permission to listen on port %i denied"
msgstr ""
#: notebook/notebookapp.py:1193
msgid "ERROR: the notebook server could not be started because no available port could be found."
msgstr ""
#: notebook/notebookapp.py:1199
msgid "[all ip addresses on your system]"
msgstr ""
#: notebook/notebookapp.py:1223
#, python-format
msgid "Terminals not available (error was %s)"
msgstr ""
#: notebook/notebookapp.py:1259
msgid "interrupted"
msgstr ""
#: notebook/notebookapp.py:1261
msgid "y"
msgstr ""
#: notebook/notebookapp.py:1262
msgid "n"
msgstr ""
#: notebook/notebookapp.py:1263
#, python-format
msgid "Shutdown this notebook server (%s/[%s])? "
msgstr ""
#: notebook/notebookapp.py:1269
msgid "Shutdown confirmed"
msgstr ""
#: notebook/notebookapp.py:1273
msgid "No answer for 5s:"
msgstr ""
#: notebook/notebookapp.py:1274
msgid "resuming operation..."
msgstr ""
#: notebook/notebookapp.py:1282
#, python-format
msgid "received signal %s, stopping"
msgstr ""
#: notebook/notebookapp.py:1338
#, python-format
msgid "Error loading server extension %s"
msgstr ""
#: notebook/notebookapp.py:1369
#, python-format
msgid "Shutting down %d kernels"
msgstr ""
#: notebook/notebookapp.py:1375
#, python-format
msgid "%d active kernel"
msgid_plural "%d active kernels"
msgstr[0] ""
msgstr[1] ""
#: notebook/notebookapp.py:1379
#, python-format
msgid ""
"The Jupyter Notebook is running at:\n"
"\r"
"%s"
msgstr ""
#: notebook/notebookapp.py:1426
msgid "Running as root is not recommended. Use --allow-root to bypass."
msgstr ""
#: notebook/notebookapp.py:1432
msgid "Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)."
msgstr ""
#: notebook/notebookapp.py:1434
msgid "Welcome to Project Jupyter! Explore the various tools available and their corresponding documentation. If you are interested in contributing to the platform, please visit the communityresources section at http://jupyter.org/community.html."
msgstr ""
#: notebook/notebookapp.py:1445
#, python-format
msgid "No web browser found: %s."
msgstr ""
#: notebook/notebookapp.py:1450
#, python-format
msgid "%s does not exist"
msgstr ""
#: notebook/notebookapp.py:1484
msgid "Interrupted..."
msgstr ""
#: notebook/services/contents/filemanager.py:506
#, python-format
msgid "Serving notebooks from local directory: %s"
msgstr ""
#: notebook/services/contents/manager.py:68
msgid "Untitled"
msgstr ""

@ -324,8 +324,6 @@ class JSController(TestController):
c.start()
env = os.environ.copy()
env.update(self.env)
if self.engine == 'phantomjs':
env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1'
self.server = subprocess.Popen(command,
stdout = c.writefd,
stderr = subprocess.STDOUT,

@ -10,6 +10,7 @@ import notebook
import binascii
import datetime
import errno
import gettext
import importlib
import io
import json
@ -34,23 +35,28 @@ except ImportError: #PY2
from jinja2 import Environment, FileSystemLoader
# Set up message catalog access
base_dir = os.path.realpath(os.path.join(__file__, '..', '..'))
trans = gettext.translation('notebook', localedir=os.path.join(base_dir, 'notebook/i18n'), fallback=True)
trans.install()
_ = trans.gettext
# Install the pyzmq ioloop. This has to be done before anything else from
# tornado is imported.
from zmq.eventloop import ioloop
ioloop.install()
# check for tornado 3.1.0
msg = "The Jupyter Notebook requires tornado >= 4.0"
try:
import tornado
except ImportError:
raise ImportError(msg)
raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0"))
try:
version_info = tornado.version_info
except AttributeError:
raise ImportError(msg + ", but you have < 1.1.0")
raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0"))
if version_info < (4,0):
raise ImportError(msg + ", but you have %s" % tornado.version)
raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0, but you have %s") % tornado.version)
from tornado import httpserver
from tornado import web
@ -75,6 +81,7 @@ from .services.kernels.kernelmanager import MappingKernelManager
from .services.config import ConfigManager
from .services.contents.manager import ContentsManager
from .services.contents.filemanager import FileContentsManager
from .services.contents.largefilemanager import LargeFileManager
from .services.sessions.sessionmanager import SessionManager
from .auth.login import LoginHandler
@ -92,7 +99,7 @@ from jupyter_client.kernelspec import KernelSpecManager, NoSuchKernel, NATIVE_KE
from jupyter_client.session import Session
from nbformat.sign import NotebookNotary
from traitlets import (
Dict, Unicode, Integer, List, Bool, Bytes, Instance,
Any, Dict, Unicode, Integer, List, Bool, Bytes, Instance,
TraitError, Type, Float, observe, default, validate
)
from ipython_genutils import py3compat
@ -112,15 +119,6 @@ jupyter notebook --certfile=mycert.pem # use SSL/TLS certificate
jupyter notebook password # enter a password to protect the server
"""
DEV_NOTE_NPM = """It looks like you're running the notebook from source.
If you're working on the Javascript of the notebook, try running
npm run build:watch
in another terminal window to have the system incrementally
watch and build the notebook's JavaScript for you, as you make changes.
"""
#-----------------------------------------------------------------------------
# Helper functions
#-----------------------------------------------------------------------------
@ -153,17 +151,11 @@ class NotebookWebApplication(web.Application):
config_manager, log,
base_url, default_url, settings_overrides, jinja_env_options):
# If the user is running the notebook in a git directory, make the assumption
# that this is a dev install and suggest to the developer `npm run build:watch`.
base_dir = os.path.realpath(os.path.join(__file__, '..', '..'))
dev_mode = os.path.exists(os.path.join(base_dir, '.git'))
if dev_mode:
log.info(DEV_NOTE_NPM)
settings = self.init_settings(
jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager, config_manager, log, base_url,
default_url, settings_overrides, jinja_env_options)
session_manager, kernel_spec_manager, config_manager, log,
base_url, default_url, settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
@ -185,9 +177,27 @@ class NotebookWebApplication(web.Application):
jenv_opt = {"autoescape": True}
jenv_opt.update(jinja_env_options if jinja_env_options else {})
env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
env = Environment(loader=FileSystemLoader(template_path), extensions=['jinja2.ext.i18n'], **jenv_opt)
sys_info = get_sys_info()
# If the user is running the notebook in a git directory, make the assumption
# that this is a dev install and suggest to the developer `npm run build:watch`.
base_dir = os.path.realpath(os.path.join(__file__, '..', '..'))
dev_mode = os.path.exists(os.path.join(base_dir, '.git'))
nbui = gettext.translation('nbui', localedir=os.path.join(base_dir, 'notebook/i18n'), fallback=True)
env.install_gettext_translations(nbui, newstyle=False)
if dev_mode:
DEV_NOTE_NPM = """It looks like you're running the notebook from source.
If you're working on the Javascript of the notebook, try running
%s
in another terminal window to have the system incrementally
watch and build the notebook's JavaScript for you, as you make changes.""" % 'npm run build:watch'
log.info(DEV_NOTE_NPM)
if sys_info['commit_source'] == 'repository':
# don't cache (rely on 304) when working from master
version_hash = ''
@ -196,12 +206,17 @@ class NotebookWebApplication(web.Application):
version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
if jupyter_app.ignore_minified_js:
log.warning("""The `ignore_minified_js` flag is deprecated and no
longer works. Alternatively use `npm run build:watch` when
working on the notebook's Javascript and LESS""")
warnings.warn("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0", DeprecationWarning)
log.warning(_("""The `ignore_minified_js` flag is deprecated and no longer works."""))
log.warning(_("""Alternatively use `%s` when working on the notebook's Javascript and LESS""") % 'npm run build:watch')
warnings.warn(_("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"), DeprecationWarning)
now = utcnow()
root_dir = contents_manager.root_dir
home = os.path.expanduser('~')
if root_dir.startswith(home + os.path.sep):
# collapse $HOME to ~
root_dir = '~' + root_dir[len(home):]
settings = dict(
# basics
@ -255,6 +270,7 @@ class NotebookWebApplication(web.Application):
mathjax_config=jupyter_app.mathjax_config,
config=jupyter_app.config,
config_dir=jupyter_app.config_dir,
server_root_dir=root_dir,
jinja2_env=env,
terminals_available=False, # Set later if terminals are available
)
@ -286,6 +302,7 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
handlers.extend(load_handlers('services.security.handlers'))
handlers.extend(load_handlers('services.shutdown'))
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {
@ -338,22 +355,51 @@ class NotebookPasswordApp(JupyterApp):
self.log.info("Wrote hashed password to %s" % self.config_file)
class NbserverStopApp(JupyterApp):
version = __version__
description="Stop currently running notebook server for a given port"
port = Integer(8888, config=True,
help="Port of the server to be killed. Default 8888")
def parse_command_line(self, argv=None):
super(NbserverStopApp, self).parse_command_line(argv)
if self.extra_args:
self.port=int(self.extra_args[0])
def start(self):
servers = list(list_running_servers(self.runtime_dir))
if not servers:
self.exit("There are no running servers")
for server in servers:
if server['port'] == self.port:
self.log.debug("Shutting down notebook server with PID: %i", server['pid'])
os.kill(server['pid'], signal.SIGTERM)
return
else:
print("There is currently no server running on port {}".format(self.port), file=sys.stderr)
print("Ports currently in use:", file=sys.stderr)
for server in servers:
print(" - {}".format(server['port']), file=sys.stderr)
self.exit(1)
class NbserverListApp(JupyterApp):
version = __version__
description="List currently running notebook servers."
description=_("List currently running notebook servers.")
flags = dict(
json=({'NbserverListApp': {'json': True}},
"Produce machine-readable JSON output."),
_("Produce machine-readable JSON output.")),
)
json = Bool(False, config=True,
help="If True, each line of output will be a JSON object with the "
"details from the server info file.")
help=_("If True, each line of output will be a JSON object with the "
"details from the server info file."))
def start(self):
if not self.json:
print("Currently running servers:")
print(_("Currently running servers:"))
for serverinfo in list_running_servers(self.runtime_dir):
if self.json:
print(json.dumps(serverinfo))
@ -370,11 +416,11 @@ class NbserverListApp(JupyterApp):
flags = dict(base_flags)
flags['no-browser']=(
{'NotebookApp' : {'open_browser' : False}},
"Don't open the notebook in a browser after startup."
_("Don't open the notebook in a browser after startup.")
)
flags['pylab']=(
{'NotebookApp' : {'pylab' : 'warn'}},
"DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
_("DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.")
)
flags['no-mathjax']=(
{'NotebookApp' : {'enable_mathjax' : False}},
@ -390,7 +436,7 @@ flags['no-mathjax']=(
flags['allow-root']=(
{'NotebookApp' : {'allow_root' : True}},
"Allow the notebook to be run from root user."
_("Allow the notebook to be run from root user.")
)
# Add notebook manager flags
@ -421,12 +467,9 @@ class NotebookApp(JupyterApp):
name = 'jupyter-notebook'
version = __version__
description = """
The Jupyter HTML Notebook.
This launches a Tornado based HTML Notebook Server that serves up an
HTML5/Javascript Notebook client.
"""
description = _("""The Jupyter HTML Notebook.
This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client.""")
examples = _examples
aliases = aliases
flags = flags
@ -441,6 +484,7 @@ class NotebookApp(JupyterApp):
subcommands = dict(
list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
stop=(NbserverStopApp, NbserverStopApp.description.splitlines()[0]),
password=(NotebookPasswordApp, NotebookPasswordApp.description.splitlines()[0]),
)
@ -462,7 +506,7 @@ class NotebookApp(JupyterApp):
ignore_minified_js = Bool(False,
config=True,
help='Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation',
help=_('Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation'),
)
# file to be opened in the notebook server
@ -493,19 +537,19 @@ class NotebookApp(JupyterApp):
)
allow_credentials = Bool(False, config=True,
help="Set the Access-Control-Allow-Credentials: true header"
help=_("Set the Access-Control-Allow-Credentials: true header")
)
allow_root = Bool(False, config=True,
help="Whether to allow the user to run the notebook as root."
help=_("Whether to allow the user to run the notebook as root.")
)
default_url = Unicode('/tree', config=True,
help="The default URL to redirect to from `/`"
help=_("The default URL to redirect to from `/`")
)
ip = Unicode('localhost', config=True,
help="The IP address the notebook server will listen on."
help=_("The IP address the notebook server will listen on.")
)
@default('ip')
@ -518,7 +562,7 @@ class NotebookApp(JupyterApp):
try:
s.bind(('localhost', 0))
except socket.error as e:
self.log.warning("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
self.log.warning(_("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s"), e)
return '127.0.0.1'
else:
s.close()
@ -532,27 +576,27 @@ class NotebookApp(JupyterApp):
return value
port = Integer(8888, config=True,
help="The port the notebook server will listen on."
help=_("The port the notebook server will listen on.")
)
port_retries = Integer(50, config=True,
help="The number of additional ports to try if the specified port is not available."
help=_("The number of additional ports to try if the specified port is not available.")
)
certfile = Unicode(u'', config=True,
help="""The full path to an SSL/TLS certificate file."""
help=_("""The full path to an SSL/TLS certificate file.""")
)
keyfile = Unicode(u'', config=True,
help="""The full path to a private key file for usage with SSL/TLS."""
help=_("""The full path to a private key file for usage with SSL/TLS.""")
)
client_ca = Unicode(u'', config=True,
help="""The full path to a certificate authority certificate for SSL/TLS client authentication."""
help=_("""The full path to a certificate authority certificate for SSL/TLS client authentication.""")
)
cookie_secret_file = Unicode(config=True,
help="""The file where the cookie secret is stored."""
help=_("""The file where the cookie secret is stored.""")
)
@default('cookie_secret_file')
@ -581,32 +625,31 @@ class NotebookApp(JupyterApp):
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)
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.warning(
"Could not set permissions on %s",
_("Could not set permissions on %s"),
self.cookie_secret_file
)
token = Unicode('<generated>',
help="""Token used for authenticating first-time connections to the server.
help=_("""Token used for authenticating first-time connections to the server.
When no password is enabled,
the default is to generate a new, random token.
Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.
"""
""")
).tag(config=True)
one_time_token = Unicode(
help="""One-time token used for opening a browser.
help=_("""One-time token used for opening a browser.
Once used, this token cannot be used again.
"""
""")
)
_token_generated = True
@ -639,7 +682,7 @@ class NotebookApp(JupyterApp):
password_required = Bool(False, config=True,
help="""Forces users to use a password for the Notebook server.
This is useful in a multi user environment, for instance when
everybody in the LAN can access each other's machine though ssh.
everybody in the LAN can access each other's machine through ssh.
In such a case, server the notebook server on localhost is not secure
since any user can connect to the notebook server via ssh.
@ -678,37 +721,59 @@ class NotebookApp(JupyterApp):
standard library module, which allows setting of the
BROWSER environment variable to override it.
""")
webbrowser_open_new = Integer(2, config=True,
help=_("""Specify Where to open the notebook on startup. This is the
`new` argument passed to the standard library method `webbrowser.open`.
The behaviour is not guaranteed, but depends on browser support. Valid
values are:
2 opens a new tab,
1 opens a new window,
0 opens in an existing window.
See the `webbrowser.open` documentation for details.
"""))
webapp_settings = Dict(config=True,
help="DEPRECATED, use tornado_settings"
help=_("DEPRECATED, use tornado_settings")
)
@observe('webapp_settings')
def _update_webapp_settings(self, change):
self.log.warning("\n webapp_settings is deprecated, use tornado_settings.\n")
self.log.warning(_("\n webapp_settings is deprecated, use tornado_settings.\n"))
self.tornado_settings = change['new']
tornado_settings = Dict(config=True,
help="Supply overrides for the tornado.web.Application that the "
"Jupyter notebook uses.")
help=_("Supply overrides for the tornado.web.Application that the "
"Jupyter notebook uses."))
websocket_compression_options = Any(None, config=True,
help=_("""
Set the tornado compression options for websocket connections.
This value will be returned from :meth:`WebSocketHandler.get_compression_options`.
None (default) will disable compression.
A dict (even an empty one) will enable compression.
See the tornado docs for WebSocketHandler.get_compression_options for details.
""")
)
terminado_settings = Dict(config=True,
help='Supply overrides for terminado. Currently only supports "shell_command".')
help=_('Supply overrides for terminado. Currently only supports "shell_command".'))
cookie_options = Dict(config=True,
help="Extra keyword arguments to pass to `set_secure_cookie`."
" See tornado's set_secure_cookie docs for details."
help=_("Extra keyword arguments to pass to `set_secure_cookie`."
" See tornado's set_secure_cookie docs for details.")
)
ssl_options = Dict(config=True,
help="""Supply SSL options for the tornado HTTPServer.
See the tornado docs for details.""")
help=_("""Supply SSL options for the tornado HTTPServer.
See the tornado docs for details."""))
jinja_environment_options = Dict(config=True,
help="Supply extra arguments that will be passed to Jinja environment.")
help=_("Supply extra arguments that will be passed to Jinja environment."))
jinja_template_vars = Dict(
config=True,
help="Extra variables to supply to jinja templates when rendering.",
help=_("Extra variables to supply to jinja templates when rendering."),
)
enable_mathjax = Bool(True, config=True,
@ -740,15 +805,15 @@ class NotebookApp(JupyterApp):
value = proposal['value']
if not value.startswith('/'):
value = '/' + value
elif not value.endswith('/'):
if not value.endswith('/'):
value = value + '/'
return value
base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
base_project_url = Unicode('/', config=True, help=_("""DEPRECATED use base_url"""))
@observe('base_project_url')
def _update_base_project_url(self, change):
self.log.warning("base_project_url is deprecated, use base_url")
self.log.warning(_("base_project_url is deprecated, use base_url"))
self.base_url = change['new']
extra_static_paths = List(Unicode(), config=True,
@ -764,7 +829,7 @@ class NotebookApp(JupyterApp):
return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
static_custom_path = List(Unicode(),
help="""Path to search for custom.js, css"""
help=_("""Path to search for custom.js, css""")
)
@default('static_custom_path')
@ -776,9 +841,9 @@ class NotebookApp(JupyterApp):
]
extra_template_paths = List(Unicode(), config=True,
help="""Extra paths to search for serving jinja templates.
help=_("""Extra paths to search for serving jinja templates.
Can be used to override templates from notebook.templates."""
Can be used to override templates from notebook.templates.""")
)
@property
@ -787,7 +852,7 @@ class NotebookApp(JupyterApp):
return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
extra_nbextensions_path = List(Unicode(), config=True,
help="""extra paths to look for Javascript notebook extensions"""
help=_("""extra paths to look for Javascript notebook extensions""")
)
@property
@ -832,39 +897,39 @@ class NotebookApp(JupyterApp):
# enable_mathjax=False overrides mathjax_url
self.mathjax_url = u''
else:
self.log.info("Using MathJax: %s", new)
self.log.info(_("Using MathJax: %s"), new)
mathjax_config = Unicode("TeX-AMS-MML_HTMLorMML-full,Safe", config=True,
help="""The MathJax.js configuration file that is to be used."""
help=_("""The MathJax.js configuration file that is to be used.""")
)
@observe('mathjax_config')
def _update_mathjax_config(self, change):
self.log.info("Using MathJax configuration file: %s", change['new'])
self.log.info(_("Using MathJax configuration file: %s"), change['new'])
contents_manager_class = Type(
default_value=FileContentsManager,
default_value=LargeFileManager,
klass=ContentsManager,
config=True,
help='The notebook manager class to use.'
help=_('The notebook manager class to use.')
)
kernel_manager_class = Type(
default_value=MappingKernelManager,
config=True,
help='The kernel manager class to use.'
help=_('The kernel manager class to use.')
)
session_manager_class = Type(
default_value=SessionManager,
config=True,
help='The session manager class to use.'
help=_('The session manager class to use.')
)
config_manager_class = Type(
default_value=ConfigManager,
config = True,
help='The config manager class to use'
help=_('The config manager class to use')
)
kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
@ -885,19 +950,19 @@ class NotebookApp(JupyterApp):
default_value=LoginHandler,
klass=web.RequestHandler,
config=True,
help='The login handler class to use.',
help=_('The login handler class to use.'),
)
logout_handler_class = Type(
default_value=LogoutHandler,
klass=web.RequestHandler,
config=True,
help='The logout handler class to use.',
help=_('The logout handler class to use.'),
)
trust_xheaders = Bool(False, config=True,
help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
"sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
help=(_("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
"sent by the upstream reverse proxy. Necessary if the proxy handles SSL"))
)
info_file = Unicode()
@ -908,9 +973,9 @@ class NotebookApp(JupyterApp):
return os.path.join(self.runtime_dir, info_file)
pylab = Unicode('disabled', config=True,
help="""
help=_("""
DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
"""
""")
)
@observe('pylab')
@ -920,14 +985,14 @@ class NotebookApp(JupyterApp):
backend = ' %s' % change['new']
else:
backend = ''
self.log.error("Support for specifying --pylab on the command line has been removed.")
self.log.error(_("Support for specifying --pylab on the command line has been removed."))
self.log.error(
"Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
_("Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.").format(backend)
)
self.exit(1)
notebook_dir = Unicode(config=True,
help="The directory to use for notebooks and kernels."
help=_("The directory to use for notebooks and kernels.")
)
@default('notebook_dir')
@ -950,7 +1015,7 @@ class NotebookApp(JupyterApp):
# If we receive a non-absolute path, make it absolute.
value = os.path.abspath(value)
if not os.path.isdir(value):
raise TraitError("No such notebook dir: %r" % value)
raise TraitError(trans.gettext("No such notebook dir: '%r'") % value)
return value
@observe('notebook_dir')
@ -963,37 +1028,37 @@ class NotebookApp(JupyterApp):
# TODO: Remove me in notebook 5.0
server_extensions = List(Unicode(), config=True,
help=("DEPRECATED use the nbserver_extensions dict instead")
help=(_("DEPRECATED use the nbserver_extensions dict instead"))
)
@observe('server_extensions')
def _update_server_extensions(self, change):
self.log.warning("server_extensions is deprecated, use nbserver_extensions")
self.log.warning(_("server_extensions is deprecated, use nbserver_extensions"))
self.server_extensions = change['new']
nbserver_extensions = Dict({}, config=True,
help=("Dict of Python modules to load as notebook server extensions."
help=(_("Dict of Python modules to load as notebook server extensions."
"Entry values can be used to enable and disable the loading of"
"the extensions. The extensions will be loaded in alphabetical "
"order.")
"order."))
)
reraise_server_extension_failures = Bool(
False,
config=True,
help="Reraise exceptions encountered loading server extensions?",
help=_("Reraise exceptions encountered loading server extensions?"),
)
iopub_msg_rate_limit = Float(1000, config=True, help="""(msgs/sec)
iopub_msg_rate_limit = Float(1000, config=True, help=_("""(msgs/sec)
Maximum rate at which messages can be sent on iopub before they are
limited.""")
limited."""))
iopub_data_rate_limit = Float(1000000, config=True, help="""(bytes/sec)
Maximum rate at which messages can be sent on iopub before they are
limited.""")
iopub_data_rate_limit = Float(1000000, config=True, help=_("""(bytes/sec)
Maximum rate at which stream output can be sent on iopub before they are
limited."""))
rate_limit_window = Float(3, config=True, help="""(sec) Time window used to
check the message and data rate limits.""")
rate_limit_window = Float(3, config=True, help=_("""(sec) Time window used to
check the message and data rate limits."""))
def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)
@ -1003,7 +1068,7 @@ class NotebookApp(JupyterApp):
f = os.path.abspath(arg0)
self.argv.remove(arg0)
if not os.path.exists(f):
self.log.critical("No such file or directory: %s", f)
self.log.critical(_("No such file or directory: %s"), f)
self.exit(1)
# Use config here, to ensure that it takes higher priority than
@ -1038,7 +1103,6 @@ class NotebookApp(JupyterApp):
self.config_manager = self.config_manager_class(
parent=self,
log=self.log,
config_dir=os.path.join(self.config_dir, 'nbconfig'),
)
def init_logging(self):
@ -1059,6 +1123,7 @@ class NotebookApp(JupyterApp):
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
self.tornado_settings['allow_origin'] = self.allow_origin
self.tornado_settings['websocket_compression_options'] = self.websocket_compression_options
if self.allow_origin_pat:
self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
self.tornado_settings['allow_credentials'] = self.allow_credentials
@ -1073,9 +1138,9 @@ class NotebookApp(JupyterApp):
self.default_url = url_path_join(self.base_url, self.default_url)
if self.password_required and (not self.password):
self.log.critical("Notebook servers are configured to only be run with a password.")
self.log.critical("Hint: run the following command to set a password")
self.log.critical("\t$ python -m notebook.auth password")
self.log.critical(_("Notebook servers are configured to only be run with a password."))
self.log.critical(_("Hint: run the following command to set a password"))
self.log.critical(_("\t$ python -m notebook.auth password"))
sys.exit(1)
self.web_app = NotebookWebApplication(
@ -1113,10 +1178,10 @@ class NotebookApp(JupyterApp):
self.http_server.listen(port, self.ip)
except socket.error as e:
if e.errno == errno.EADDRINUSE:
self.log.info('The port %i is already in use, trying another port.' % port)
self.log.info(_('The port %i is already in use, trying another port.') % port)
continue
elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
self.log.warning("Permission to listen on port %i denied" % port)
self.log.warning(_("Permission to listen on port %i denied") % port)
continue
else:
raise
@ -1125,13 +1190,13 @@ class NotebookApp(JupyterApp):
success = True
break
if not success:
self.log.critical('ERROR: the notebook server could not be started because '
'no available port could be found.')
self.log.critical(_('ERROR: the notebook server could not be started because '
'no available port could be found.'))
self.exit(1)
@property
def display_url(self):
ip = self.ip if self.ip else '[all ip addresses on your system]'
ip = self.ip if self.ip else _('[all ip addresses on your system]')
url = self._url(ip)
if self.token:
# Don't log full token if it came from config
@ -1155,10 +1220,10 @@ class NotebookApp(JupyterApp):
self.web_app.settings['terminals_available'] = True
except ImportError as e:
log = self.log.debug if sys.platform == 'win32' else self.log.warning
log("Terminals not available (error was %s)", e)
log(_("Terminals not available (error was %s)"), e)
def init_signal(self):
if not sys.platform.startswith('win') and sys.stdin.isatty():
if not sys.platform.startswith('win') and sys.stdin and sys.stdin.isatty():
signal.signal(signal.SIGINT, self._handle_sigint)
signal.signal(signal.SIGTERM, self._signal_stop)
if hasattr(signal, 'SIGUSR1'):
@ -1191,20 +1256,22 @@ class NotebookApp(JupyterApp):
This doesn't work on Windows.
"""
info = self.log.info
info('interrupted')
info(_('interrupted'))
print(self.notebook_info())
sys.stdout.write("Shutdown this notebook server (y/[n])? ")
yes = _('y')
no = _('n')
sys.stdout.write(_("Shutdown this notebook server (%s/[%s])? ") % (yes, no))
sys.stdout.flush()
r,w,x = select.select([sys.stdin], [], [], 5)
if r:
line = sys.stdin.readline()
if line.lower().startswith('y') and 'n' not in line.lower():
self.log.critical("Shutdown confirmed")
if line.lower().startswith(yes) and no not in line.lower():
self.log.critical(_("Shutdown confirmed"))
ioloop.IOLoop.current().stop()
return
else:
print("No answer for 5s:", end=' ')
print("resuming operation...")
print(_("No answer for 5s:"), end=' ')
print(_("resuming operation..."))
# no answer, or answer is no:
# set it back to original SIGINT handler
# use IOLoop.add_callback because signal.signal must be called
@ -1212,7 +1279,7 @@ class NotebookApp(JupyterApp):
ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
def _signal_stop(self, sig, frame):
self.log.critical("received signal %s, stopping", sig)
self.log.critical(_("received signal %s, stopping"), sig)
ioloop.IOLoop.current().stop()
def _signal_info(self, sig, frame):
@ -1268,7 +1335,7 @@ class NotebookApp(JupyterApp):
except Exception:
if self.reraise_server_extension_failures:
raise
self.log.warning("Error loading server extension %s", modulename,
self.log.warning(_("Error loading server extension %s"), modulename,
exc_info=True)
def init_mime_overrides(self):
@ -1298,14 +1365,21 @@ class NotebookApp(JupyterApp):
The kernels will shutdown themselves when this process no longer exists,
but explicit shutdown allows the KernelManagers to cleanup the connection files.
"""
self.log.info('Shutting down kernels')
n_kernels = len(self.kernel_manager.list_kernel_ids())
kernel_msg = trans.ngettext('Shutting down %d kernel', 'Shutting down %d kernels', n_kernels)
self.log.info(kernel_msg % n_kernels)
self.kernel_manager.shutdown_all()
def notebook_info(self):
"Return the current working directory and the server url information"
info = self.contents_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The Jupyter Notebook is running at: %s" % self.display_url
n_kernels = len(self.kernel_manager.list_kernel_ids())
kernel_msg = trans.ngettext("%d active kernel", "%d active kernels", n_kernels)
info += kernel_msg % n_kernels
info += "\n"
# Format the info so that the URL fits on a single line in 80 char display
info += _("The Jupyter Notebook is running at:\n%s") % self.display_url
return info
def server_info(self):
"""Return a JSONable dict of information about this server."""
@ -1342,6 +1416,8 @@ class NotebookApp(JupyterApp):
This method takes no arguments so all configuration and initialization
must be done prior to calling this method."""
super(NotebookApp, self).start()
if not self.allow_root:
# check if we are running as root, and abort if it's not allowed
try:
@ -1349,20 +1425,18 @@ class NotebookApp(JupyterApp):
except AttributeError:
uid = -1 # anything nonzero here, since we can't check UID assume non-root
if uid == 0:
self.log.critical("Running as root is not recommended. Use --allow-root to bypass.")
self.log.critical(_("Running as root is not recommended. Use --allow-root to bypass."))
self.exit(1)
super(NotebookApp, self).start()
info = self.log.info
for line in self.notebook_info().split("\n"):
info(line)
info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
info(_("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)."))
if 'dev' in notebook.__version__:
info("Welcome to Project Jupyter! Explore the various tools available"
info(_("Welcome to Project Jupyter! Explore the various tools available"
" and their corresponding documentation. If you are interested"
" in contributing to the platform, please visit the community"
"resources section at http://jupyter.org/community.html.")
"resources section at http://jupyter.org/community.html."))
self.write_server_info_file()
@ -1370,12 +1444,12 @@ class NotebookApp(JupyterApp):
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warning('No web browser found: %s.' % e)
self.log.warning(_('No web browser found: %s.') % e)
browser = None
if self.file_to_run:
if not os.path.exists(self.file_to_run):
self.log.critical("%s does not exist" % self.file_to_run)
self.log.critical(_("%s does not exist") % self.file_to_run)
self.exit(1)
relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
@ -1387,7 +1461,7 @@ class NotebookApp(JupyterApp):
uri = url_concat(uri, {'token': self.one_time_token})
if browser:
b = lambda : browser.open(url_path_join(self.connection_url, uri),
new=2)
new=self.webbrowser_open_new)
threading.Thread(target=b).start()
if self.token and self._token_generated:
@ -1409,7 +1483,7 @@ class NotebookApp(JupyterApp):
try:
self.io_loop.start()
except KeyboardInterrupt:
info("Interrupted...")
info(_("Interrupted..."))
finally:
self.remove_server_info_file()
self.cleanup_kernels()

@ -3,12 +3,13 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from datetime import datetime
import errno
import io
import os
import shutil
import stat
import sys
import warnings
import mimetypes
import nbformat
@ -18,6 +19,7 @@ from tornado import web
from .filecheckpoints import FileCheckpoints
from .fileio import FileManagerMixin
from .manager import ContentsManager
from ...utils import exists
from ipython_genutils.importstring import import_item
from traitlets import Any, Unicode, Bool, TraitError, observe, default, validate
@ -28,6 +30,7 @@ from notebook.utils import (
is_hidden, is_file_hidden,
to_api_path,
)
from notebook.base.handlers import AuthenticatedFileHandler
try:
from os.path import samefile
@ -142,9 +145,14 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
raise TraitError("%r is not a directory" % value)
return value
@default('checkpoints_class')
def _checkpoints_class_default(self):
return FileCheckpoints
@default('files_handler_class')
def _files_handler_class_default(self):
return AuthenticatedFileHandler
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
@ -219,14 +227,25 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return os.path.exists(os_path)
return exists(os_path)
def _base_model(self, path):
"""Build the common base of a contents model"""
os_path = self._get_os_path(path)
info = os.stat(os_path)
last_modified = tz.utcfromtimestamp(info.st_mtime)
created = tz.utcfromtimestamp(info.st_ctime)
info = os.lstat(os_path)
try:
last_modified = tz.utcfromtimestamp(info.st_mtime)
except ValueError:
# Files can rarely have an invalid timestamp
# https://github.com/jupyter/notebook/issues/2539
# Use the Unix epoch as a fallback so we don't crash.
last_modified = datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC)
try:
created = tz.utcfromtimestamp(info.st_ctime)
except ValueError: # See above
created = datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC)
# Create the base model.
model = {}
model['name'] = path.rsplit('/', 1)[-1]
@ -274,16 +293,18 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
continue
try:
st = os.stat(os_path)
st = os.lstat(os_path)
except OSError as e:
# skip over broken symlinks in listing
if e.errno == errno.ENOENT:
self.log.warning("%s doesn't exist", os_path)
else:
self.log.warning("Error stat-ing %s: %s", (os_path, e))
self.log.warning("Error stat-ing %s: %s", os_path, e)
continue
if not stat.S_ISREG(st.st_mode) and not stat.S_ISDIR(st.st_mode):
if (not stat.S_ISLNK(st.st_mode)
and not stat.S_ISREG(st.st_mode)
and not stat.S_ISDIR(st.st_mode)):
self.log.debug("%s not a regular file", os_path)
continue
@ -498,7 +519,7 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
def info_string(self):
return "Serving notebooks from local directory: %s" % self.root_dir
return _("Serving notebooks from local directory: %s") % self.root_dir
def get_kernel_path(self, path, model=None):
"""Return the initial API path of a kernel associated with a given notebook"""

@ -18,17 +18,6 @@ from notebook.base.handlers import (
)
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)
def validate_model(model, expect_content):
"""
Validate a model returned by a ContentsManager method.
@ -123,10 +112,6 @@ class ContentsHandler(APIHandler):
model = yield gen.maybe_future(self.contents_manager.get(
path=path, type=type, format=format, content=content,
))
if model['type'] == 'directory' and content:
# group listing by type, then by name (case-insensitive)
# FIXME: sorting should be done in the frontends
model['content'].sort(key=sort_key)
validate_model(model, expect_content=content)
self._finish_model(model, location=False)
@ -176,7 +161,9 @@ class ContentsHandler(APIHandler):
@gen.coroutine
def _save(self, model, path):
"""Save an existing file."""
self.log.info(u"Saving file at %s", path)
chunk = model.get("chunk", None)
if not chunk or chunk == -1: # Avoid tedious log information
self.log.info(u"Saving file at %s", path)
model = yield gen.maybe_future(self.contents_manager.save(model, path))
validate_model(model, expect_content=False)
self._finish_model(model)

@ -0,0 +1,70 @@
from notebook.services.contents.filemanager import FileContentsManager
from contextlib import contextmanager
from tornado import web
import nbformat
import base64
import os, io
class LargeFileManager(FileContentsManager):
"""Handle large file upload."""
def save(self, model, path=''):
"""Save the file model and return the model with no content."""
chunk = model.get('chunk', None)
if chunk is not None:
path = path.strip('/')
if 'type' not in model:
raise web.HTTPError(400, u'No file type provided')
if model['type'] != 'file':
raise web.HTTPError(400, u'File type "{}" is not supported for large file transfer'.format(model['type']))
if 'content' not in model and model['type'] != 'directory':
raise web.HTTPError(400, u'No file content provided')
os_path = self._get_os_path(path)
try:
if chunk == 1:
self.log.debug("Saving %s", os_path)
self.run_pre_save_hook(model=model, path=path)
super(LargeFileManager, self)._save_file(os_path, model['content'], model.get('format'))
else:
self._save_large_file(os_path, model['content'], model.get('format'))
except web.HTTPError:
raise
except Exception as e:
self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
model = self.get(path, content=False)
# Last chunk
if chunk == -1:
self.run_post_save_hook(model=model, os_path=os_path)
return model
else:
return super(LargeFileManager, self).save(model, path)
def _save_large_file(self, os_path, content, format):
"""Save content of a generic file."""
if format not in {'text', 'base64'}:
raise web.HTTPError(
400,
"Must specify format of file contents as 'text' or 'base64'",
)
try:
if format == 'text':
bcontent = content.encode('utf8')
else:
b64_bytes = content.encode('ascii')
bcontent = base64.b64decode(b64_bytes)
except Exception as e:
raise web.HTTPError(
400, u'Encoding error saving %s: %s' % (os_path, e)
)
with self.perm_to_403(os_path):
if os.path.islink(os_path):
os_path = os.path.join(os.path.dirname(os_path), os.readlink(os_path))
with io.open(os_path, 'ab') as f:
f.write(bcontent)

@ -4,6 +4,7 @@
# Distributed under the terms of the Modified BSD License.
from fnmatch import fnmatch
import gettext
import itertools
import json
import os
@ -28,6 +29,7 @@ from traitlets import (
default,
)
from ipython_genutils.py3compat import string_types
from notebook.base.handlers import IPythonHandler
copy_pat = re.compile(r'\-Copy\d*\.')
@ -50,6 +52,8 @@ class ContentsManager(LoggingConfigurable):
indicating the root path.
"""
root_dir = Unicode('/', config=True)
notary = Instance(sign.NotebookNotary)
def _notary_default(self):
@ -62,7 +66,7 @@ class ContentsManager(LoggingConfigurable):
Glob patterns to hide in file and directory listings.
""")
untitled_notebook = Unicode("Untitled", config=True,
untitled_notebook = Unicode(_("Untitled"), config=True,
help="The base name used when creating untitled notebooks."
)
@ -127,6 +131,8 @@ class ContentsManager(LoggingConfigurable):
log=self.log,
)
files_handler_class = Type(IPythonHandler, allow_none=True, config=True)
# ContentsManager API part 1: methods that must be
# implemented in subclasses.

@ -249,8 +249,8 @@ class APITest(NotebookTestBase):
self.assertEqual(nbnames, expected)
nbs = notebooks_only(self.api.list('ordering').json())
nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
nbnames = {n['name'] for n in nbs}
expected = {'A.ipynb', 'b.ipynb', 'C.ipynb'}
self.assertEqual(nbnames, expected)
def test_list_dirs(self):

@ -0,0 +1,113 @@
from unittest import TestCase
from ipython_genutils.tempdir import TemporaryDirectory
from ..largefilemanager import LargeFileManager
import os
from tornado import web
def _make_dir(contents_manager, api_path):
"""
Make a directory.
"""
os_path = contents_manager._get_os_path(api_path)
try:
os.makedirs(os_path)
except OSError:
print("Directory already exists: %r" % os_path)
class TestLargeFileManager(TestCase):
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name
self.contents_manager = LargeFileManager(root_dir=self.td)
def make_dir(self, api_path):
"""make a subdirectory at api_path
override in subclasses if contents are not on the filesystem.
"""
_make_dir(self.contents_manager, api_path)
def test_save(self):
cm = self.contents_manager
# Create a notebook
model = cm.new_untitled(type='notebook')
name = model['name']
path = model['path']
# Get the model with 'content'
full_model = cm.get(path)
# Save the notebook
model = cm.save(full_model, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], name)
self.assertEqual(model['path'], path)
try:
model = {'name': 'test', 'path': 'test', 'chunk': 1}
cm.save(model, model['path'])
except web.HTTPError as e:
self.assertEqual('HTTP 400: Bad Request (No file type provided)', str(e))
try:
model = {'name': 'test', 'path': 'test', 'chunk': 1, 'type': 'notebook'}
cm.save(model, model['path'])
except web.HTTPError as e:
self.assertEqual('HTTP 400: Bad Request (File type "notebook" is not supported for large file transfer)', str(e))
try:
model = {'name': 'test', 'path': 'test', 'chunk': 1, 'type': 'file'}
cm.save(model, model['path'])
except web.HTTPError as e:
self.assertEqual('HTTP 400: Bad Request (No file content provided)', str(e))
try:
model = {'name': 'test', 'path': 'test', 'chunk': 2, 'type': 'file',
'content': u'test', 'format': 'json'}
cm.save(model, model['path'])
except web.HTTPError as e:
self.assertEqual("HTTP 400: Bad Request (Must specify format of file contents as 'text' or 'base64')",
str(e))
# Save model for different chunks
model = {'name': 'test', 'path': 'test', 'type': 'file',
'content': u'test==', 'format': 'text'}
name = model['name']
path = model['path']
cm.save(model, path)
for chunk in (1, 2, -1):
for fm in ('text', 'base64'):
full_model = cm.get(path)
full_model['chunk'] = chunk
full_model['format'] = fm
model_res = cm.save(full_model, path)
assert isinstance(model_res, dict)
self.assertIn('name', model_res)
self.assertIn('path', model_res)
self.assertNotIn('chunk', model_res)
self.assertEqual(model_res['name'], name)
self.assertEqual(model_res['path'], path)
# Test in sub-directory
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir('foo')
model = cm.new_untitled(path=sub_dir, type='notebook')
name = model['name']
path = model['path']
model = cm.get(path)
# Change the name in the model for rename
model = cm.save(model, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'Untitled.ipynb')
self.assertEqual(model['path'], 'foo/Untitled.ipynb')

@ -108,7 +108,7 @@ class TestFileContentsManager(TestCase):
self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name))
self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name))
@dec.skip_win32
@dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3)
def test_bad_symlink(self):
with TemporaryDirectory() as td:
cm = FileContentsManager(root_dir=td)
@ -120,9 +120,16 @@ class TestFileContentsManager(TestCase):
# create a broken symlink
self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink'))
model = cm.get(path)
self.assertEqual(model['content'], [file_model])
@dec.skip_win32
contents = {
content['name']: content for content in model['content']
}
self.assertTrue('untitled.txt' in contents)
self.assertEqual(contents['untitled.txt'], file_model)
# broken symlinks should still be shown in the contents manager
self.assertTrue('bad symlink' in contents)
@dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3)
def test_good_symlink(self):
with TemporaryDirectory() as td:
cm = FileContentsManager(root_dir=td)
@ -224,7 +231,7 @@ class TestContentsManager(TestCase):
self.assertEqual(entry['name'], "nb.ipynb")
complete_path = "/".join([api_path, "nb.ipynb"])
self.assertEqual(entry["path"], complete_path)
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name

@ -336,7 +336,10 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
# Increment the bytes and message count
self._iopub_window_msg_count += 1
byte_count = sum([len(x) for x in msg_list])
if msg_type == 'stream':
byte_count = sum([len(x) for x in msg_list])
else:
byte_count = 0
self._iopub_window_byte_count += byte_count
# Queue a removal of the byte and message count for a time in the
@ -357,7 +360,12 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_msg_rate_limit`."""))
`--NotebookApp.iopub_msg_rate_limit`.
Current values:
NotebookApp.iopub_msg_rate_limit={} (msgs/sec)
NotebookApp.rate_limit_window={} (secs)
""".format(self.iopub_msg_rate_limit, self.rate_limit_window)))
else:
# resume once we've got some headroom below the limit
if self._iopub_msgs_exceeded and msg_rate < (0.8 * self.iopub_msg_rate_limit):
@ -374,7 +382,12 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_data_rate_limit`."""))
`--NotebookApp.iopub_data_rate_limit`.
Current values:
NotebookApp.iopub_data_rate_limit={} (bytes/sec)
NotebookApp.rate_limit_window={} (secs)
""".format(self.iopub_data_rate_limit, self.rate_limit_window)))
else:
# resume once we've got some headroom below the limit
if self._iopub_data_exceeded and data_rate < (0.8 * self.iopub_data_rate_limit):

@ -11,15 +11,18 @@ import os
from tornado import gen, web
from tornado.concurrent import Future
from tornado.ioloop import IOLoop
from tornado.ioloop import IOLoop, PeriodicCallback
from jupyter_client.session import Session
from jupyter_client.multikernelmanager import MultiKernelManager
from traitlets import Dict, List, Unicode, TraitError, default, validate
from traitlets import Bool, Dict, List, Unicode, TraitError, Integer, default, validate
from notebook.utils import to_os_path
from notebook.utils import to_os_path, exists
from notebook._tz import utcnow, isoformat
from ipython_genutils.py3compat import getcwd
from datetime import datetime, timedelta
class MappingKernelManager(MultiKernelManager):
"""A KernelManager that handles notebook mapping and HTTP error handling"""
@ -34,6 +37,10 @@ class MappingKernelManager(MultiKernelManager):
_kernel_connections = Dict()
_culler_callback = None
_initialized_culler = False
@default('root_dir')
def _default_root_dir(self):
try:
@ -48,10 +55,32 @@ class MappingKernelManager(MultiKernelManager):
if not os.path.isabs(value):
# If we receive a non-absolute path, make it absolute.
value = os.path.abspath(value)
if not os.path.exists(value) or not os.path.isdir(value):
if not exists(value) or not os.path.isdir(value):
raise TraitError("kernel root dir %r is not a directory" % value)
return value
cull_idle_timeout_minimum = 300 # 5 minutes
cull_idle_timeout = Integer(0, config=True,
help="""Timeout (in seconds) after which a kernel is considered idle and ready to be culled. Values of 0 or
lower disable culling. The minimum timeout is 300 seconds (5 minutes). Positive values less than the minimum value
will be set to the minimum."""
)
cull_interval_default = 300 # 5 minutes
cull_interval = Integer(cull_interval_default, config=True,
help="""The interval (in seconds) on which to check for idle kernels exceeding the cull timeout value."""
)
cull_connected = Bool(False, config=True,
help="""Whether to consider culling kernels which have one or more connections.
Only effective if cull_idle_timeout is not 0."""
)
cull_busy = Bool(False, config=True,
help="""Whether to consider culling kernels which are busy.
Only effective if cull_idle_timeout is not 0."""
)
#-------------------------------------------------------------------------
# Methods for managing kernels and sessions
#-------------------------------------------------------------------------
@ -105,6 +134,11 @@ class MappingKernelManager(MultiKernelManager):
else:
self._check_kernel_id(kernel_id)
self.log.info("Using existing kernel: %s" % kernel_id)
# Initialize culling if not already
if not self._initialized_culler:
self.initialize_culler()
# py2-compat
raise gen.Return(kernel_id)
@ -211,13 +245,17 @@ class MappingKernelManager(MultiKernelManager):
kernel.execution_state = 'starting'
kernel.last_activity = utcnow()
kernel._activity_stream = kernel.connect_iopub()
session = Session(
config=kernel.session.config,
key=kernel.session.key,
)
def record_activity(msg_list):
"""Record an IOPub message arriving from a kernel"""
kernel.last_activity = utcnow()
idents, fed_msg_list = kernel.session.feed_identities(msg_list)
msg = kernel.session.deserialize(fed_msg_list)
idents, fed_msg_list = session.feed_identities(msg_list)
msg = session.deserialize(fed_msg_list)
msg_type = msg['header']['msg_type']
self.log.debug("activity on %s: %s", kernel_id, msg_type)
if msg_type == 'status':
@ -225,3 +263,60 @@ class MappingKernelManager(MultiKernelManager):
kernel._activity_stream.on_recv(record_activity)
def initialize_culler(self):
"""Start idle culler if 'cull_idle_timeout' is greater than zero.
Regardless of that value, set flag that we've been here.
"""
if not self._initialized_culler and self.cull_idle_timeout > 0:
if self._culler_callback is None:
if self.cull_idle_timeout < self.cull_idle_timeout_minimum:
self.log.warning("'cull_idle_timeout' (%s) is less than the minimum value (%s) and has been set to the minimum.",
self.cull_idle_timeout, self.cull_idle_timeout_minimum)
self.cull_idle_timeout = self.cull_idle_timeout_minimum
loop = IOLoop.current()
if self.cull_interval <= 0: #handle case where user set invalid value
self.log.warning("Invalid value for 'cull_interval' detected (%s) - using default value (%s).",
self.cull_interval, self.cull_interval_default)
self.cull_interval = self.cull_interval_default
self._culler_callback = PeriodicCallback(
self.cull_kernels, 1000*self.cull_interval, loop)
self.log.info("Culling kernels with idle durations > %s seconds at %s second intervals ...",
self.cull_idle_timeout, self.cull_interval)
if self.cull_busy:
self.log.info("Culling kernels even if busy")
if self.cull_connected:
self.log.info("Culling kernels even with connected clients")
self._culler_callback.start()
self._initialized_culler = True
def cull_kernels(self):
self.log.debug("Polling every %s seconds for kernels idle > %s seconds...",
self.cull_interval, self.cull_idle_timeout)
"""Create a separate list of kernels to avoid conflicting updates while iterating"""
for kernel_id in list(self._kernels):
try:
self.cull_kernel_if_idle(kernel_id)
except Exception as e:
self.log.exception("The following exception was encountered while checking the idle duration of kernel %s: %s",
kernel_id, e)
def cull_kernel_if_idle(self, kernel_id):
kernel = self._kernels[kernel_id]
self.log.debug("kernel_id=%s, kernel_name=%s, last_activity=%s", kernel_id, kernel.kernel_name, kernel.last_activity)
if kernel.last_activity is not None:
dt_now = utcnow()
dt_idle = dt_now - kernel.last_activity
# Compute idle properties
is_idle_time = dt_idle > timedelta(seconds=self.cull_idle_timeout)
is_idle_execute = self.cull_busy or (kernel.execution_state != 'busy')
connections = self._kernel_connections.get(kernel_id, 0)
is_idle_connected = self.cull_connected or not connections
# Cull the kernel if all three criteria are met
if (is_idle_time and is_idle_execute and is_idle_connected):
idle_duration = int(dt_idle.total_seconds())
self.log.warning("Culling '%s' kernel '%s' (%s) with %d connections due to %s seconds of inactivity.",
kernel.execution_state, kernel.kernel_name, kernel_id, connections, idle_duration)
self.shutdown_kernel(kernel_id)

@ -13,10 +13,14 @@ class CSPReportHandler(APIHandler):
_track_activity = False
def skip_origin_check(self):
def skip_check_origin(self):
"""Don't check origin when reporting origin-check violations!"""
return True
def check_xsrf_cookie(self):
# don't check XSRF for CSP reports
return
@json_errors
@web.authenticated
def post(self):

@ -0,0 +1,15 @@
"""HTTP handler to shut down the notebook server.
"""
from tornado import web, ioloop
from notebook.base.handlers import IPythonHandler
class ShutdownHandler(IPythonHandler):
@web.authenticated
def post(self):
self.log.info("Shutting down on /api/shutdown request.")
ioloop.IOLoop.current().stop()
default_handlers = [
(r"/api/shutdown", ShutdownHandler),
]

@ -3,7 +3,7 @@
define(['jquery', 'base/js/namespace', 'base/js/page'], function($, IPython, page) {
function login_main() {
var page_instance = new page.Page();
var page_instance = new page.Page('div#header', 'div#site');
$('button#login_submit').addClass("btn btn-default");
page_instance.show();
$('input#password_input').focus();

@ -3,7 +3,7 @@
define(['base/js/namespace', 'base/js/page'], function(IPython, page) {
function logout_main() {
var page_instance = new page.Page();
var page_instance = new page.Page('div#header', 'div#site');
page_instance.show();
IPython.page = page_instance;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,13 +1,13 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
define(function(require) {
define(['jquery',
'codemirror/lib/codemirror',
'bootstrap',
'base/js/i18n'],
function($, CodeMirror, bs, i18n) {
"use strict";
var CodeMirror = require('codemirror/lib/codemirror');
var bs = require('bootstrap');
var $ = require('jquery');
/**
* A wrapper around bootstrap modal for easier use
* Pass it an option dictionary with the following properties:
@ -86,7 +86,7 @@ define(function(require) {
var button = $("<button/>")
.addClass("btn btn-default btn-sm")
.attr("data-dismiss", "modal")
.text(label);
.text(i18n.msg.translate(label).fetch());
if (btn_opts.id) {
button.attr('id', btn_opts.id);
}
@ -157,18 +157,27 @@ define(function(require) {
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 " + options.name + "." +
" We recommend putting custom metadata attributes in an appropriately named substructure," +
" so they don't conflict with those of others.";
var message_cell =
i18n.msg._("Manually edit the JSON below to manipulate the metadata for this cell.");
var message_notebook =
i18n.msg._("Manually edit the JSON below to manipulate the metadata for this notebook.");
var message_end =
i18n.msg._(" We recommend putting custom metadata attributes in an appropriately named substructure," +
" so they don't conflict with those of others.");
var message;
if (options.name === 'Notebook') {
message = message_notebook + message_end;
} else {
message = message_cell + message_end;
}
var textarea = $('<textarea/>')
.attr('rows', '13')
.attr('cols', '80')
.attr('name', 'metadata')
.text(JSON.stringify(options.md || {}, null, 2));
var dialogform = $('<div/>').attr('title', 'Edit the metadata')
var dialogform = $('<div/>').attr('title', i18n.msg._('Edit the metadata'))
.append(
$('<form/>').append(
$('<fieldset/>').append(
@ -188,8 +197,17 @@ define(function(require) {
autoIndent: true,
mode: 'application/json',
});
var title_msg;
if (options.name === "Notebook") {
title_msg = i18n.msg._("Edit Notebook Metadata");
} else {
title_msg = i18n.msg._("Edit Cell Metadata");
}
// This statement is used simply so that message extraction
// will pick up the strings.
var button_labels = [ i18n.msg._("Cancel"), i18n.msg._("Edit"), i18n.msg._("OK"), i18n.msg._("Apply")];
var modal_obj = modal({
title: "Edit " + options.name + " Metadata",
title: title_msg,
body: dialogform,
default_button: "Cancel",
buttons: {
@ -204,7 +222,7 @@ define(function(require) {
new_md = JSON.parse(editor.getValue());
} catch(e) {
console.log(e);
error_div.text('WARNING: Could not save invalid JSON.');
error_div.text(i18n.msg._('WARNING: Could not save invalid JSON.'));
return false;
}
options.callback(new_md);
@ -226,10 +244,10 @@ define(function(require) {
var message;
var attachments_list;
if (Object.keys(options.attachments).length == 0) {
message = "There are no attachments for this cell.";
message = i18n.msg._("There are no attachments for this cell.");
attachments_list = $('<div>');
} else {
message = "Current cell attachments";
message = i18n.msg._("Current cell attachments");
attachments_list = $('<div>')
.addClass('list_container')
@ -238,7 +256,7 @@ define(function(require) {
.addClass('row list_header')
.append(
$('<div>')
.text('Attachments')
.text(i18n.msg._('Attachments'))
)
);
@ -262,7 +280,7 @@ define(function(require) {
.addClass('btn btn-default btn-xs')
.css('display', 'inline-block');
if (deleted) {
btn.attr('title', 'Restore')
btn.attr('title', i18n.msg._('Restore'))
.append(
$('<i>')
.addClass('fa fa-plus')
@ -272,7 +290,7 @@ define(function(require) {
refresh_attachments_list();
});
} else {
btn.attr('title', 'Delete')
btn.attr('title', i18n.msg._('Delete'))
.addClass('btn-danger')
.append(
$('<i>')
@ -321,12 +339,18 @@ define(function(require) {
}
var dialogform = $('<div/>')
.attr('title', 'Edit attachments')
.attr('title', i18n.msg._('Edit attachments'))
.append(message)
.append('<br />')
.append(attachments_list)
.append(attachments_list);
var title_msg;
if ( options.name === "Notebook" ) {
title_msg = i18n.msg._("Edit Notebook Attachments");
} else {
title_msg = i18n.msg._("Edit Cell Attachments");
}
var modal_obj = modal({
title: "Edit " + options.name + " Attachments",
title: title_msg,
body: dialogform,
buttons: {
Apply: { class : "btn-primary",
@ -346,7 +370,7 @@ define(function(require) {
var insert_image = function (options) {
var message =
"Select a file to insert.";
i18n.msg._("Select a file to insert.");
var file_input = $('<input/>')
.attr('type', 'file')
.attr('accept', 'image/*')
@ -359,7 +383,7 @@ define(function(require) {
$btn.addClass('disabled');
}
});
var dialogform = $('<div/>').attr('title', 'Edit attachments')
var dialogform = $('<div/>').attr('title', i18n.msg._('Edit attachments'))
.append(
$('<form id="insert-image-form" />').append(
$('<fieldset/>').append(
@ -372,7 +396,7 @@ define(function(require) {
)
);
var modal_obj = modal({
title: "Pick a file",
title: i18n.msg._("Select a file"),
body: dialogform,
buttons: {
OK: {

@ -0,0 +1,54 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// Module to handle i18n ( Internationalization ) and translated UI
define([
'jed',
'moment',
'json!../../../i18n/nbjs.json',
'base/js/i18nload',
], function(Jed, moment, nbjs, i18nload) {
"use strict";
// Setup language related stuff
var ui_lang = navigator.languages && navigator.languages[0] || // Chrome / Firefox
navigator.language || // All browsers
navigator.userLanguage; // IE <= 10
var init = function() {
var msg_promise;
if (nbjs.supported_languages.indexOf(ui_lang) >= 0) {
moment.locale(ui_lang);
msg_promise = new Promise( function (resolve, reject) {
require([i18nload.id+"!"+ui_lang], function (data) {
var newi18n = new Jed(data);
newi18n._ = newi18n.gettext;
resolve(newi18n);
}, function (error) {
console.log("Error loading translations for language: "+ui_lang);
var newi18n = new Jed(nbjs);
newi18n._ = newi18n.gettext;
resolve(newi18n);
});
});
} else {
msg_promise = new Promise( function (resolve, reject) {
var newi18n = new Jed(nbjs);
newi18n._ = newi18n.gettext;
resolve(newi18n);
});
}
return msg_promise;
}
var i18n = new Jed(nbjs);
i18n._ = i18n.gettext;
i18n.msg = i18n; // Just a place holder until the init promise resolves.
init().then(function (msg) {
i18n.msg = msg;
i18n.msg._ = i18n.msg.gettext;
});
return i18n;
});

@ -0,0 +1,26 @@
/**
* Plugin to load a single locale.
*/
define([
"require",
"module",
// These are only here so that the optimizer knows which ones we MIGHT load.
// We will actually only load the ones we need. There should be one entry
// here for each language you want to support.
// For example, for German....
// "json!base/../../i18n/de/LC_MESSAGES/nbjs.json"
], function (require, module) {
return {
id: module.id,
load: function (locale, callerRequire, onload, loaderConfig) {
var dependencies = "json!base/../../i18n/"+locale+"/LC_MESSAGES/nbjs.json";
// Load the JSON file requested
require([dependencies], function (data) {
onload(data);
});
}
};
});

@ -73,7 +73,7 @@ define(function(){
// tree
jglobal('SessionList','tree/js/sessionlist');
Jupyter.version = "5.0.0.dev";
Jupyter.version = "5.1.0.dev";
Jupyter._target = '_blank';
return Jupyter;
});

@ -7,7 +7,17 @@ define([
], function($, events){
"use strict";
var Page = function () {
var Page = function (header_div_selector, site_div_selector) {
/**
* Constructor
*
* Parameters
* header_div_selector: string
* site_div_selector: string
*/
this.header_div_element = $(header_div_selector);
this.site_div_element = $(site_div_selector);
this.bind_events();
};
@ -38,18 +48,16 @@ define([
/**
* 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');
this.header_div_element.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');
this.site_div_element.css('display', 'block');
this._resize_site();
};

@ -282,6 +282,8 @@ define([
var fg = [];
var bg = [];
var bold = false;
var underline = false;
var inverse = false;
var match;
var out = [];
var numbers = [];
@ -330,6 +332,14 @@ define([
classes.push("ansi-bold");
}
if (underline) {
classes.push("ansi-underline");
}
if (inverse) {
classes.push("ansi-inverse");
}
if (classes.length || styles.length) {
out.push("<span");
if (classes.length) {
@ -353,11 +363,19 @@ define([
case 0:
fg = bg = [];
bold = false;
underline = false;
inverse = false;
break;
case 1:
case 5:
bold = true;
break;
case 4:
underline = true;
break;
case 7:
inverse = true;
break;
case 21:
case 22:
bold = false;
@ -597,7 +615,7 @@ define([
* until we are building an actual request
*/
var val = $('body').data(key);
if (!val)
if (typeof val === 'undefined')
return val;
return decodeURIComponent(val);
};
@ -754,7 +772,13 @@ define([
};
var ajax = function (url, settings) {
// like $.ajax, but ensure Authorization header is set
// like $.ajax, but ensure XSRF or Authorization header is set
if (typeof url === "object") {
// called with single argument: $.ajax({url: '...'})
settings = url;
url = settings.url;
delete settings.url;
}
settings = _add_auth_header(settings);
return $.ajax(url, settings);
};
@ -918,7 +942,10 @@ define([
}
return $el.map(function(){
// MathJax takes a DOM node: $.map makes `this` the context
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]);
return MathJax.Hub.Queue(
["Typeset", MathJax.Hub, this],
["resetEquationNumbers",MathJax.InputJax.TeX]
);
});
};
@ -993,6 +1020,50 @@ define([
}
};
// javascript stores text as utf16 and string indices use "code units",
// which stores high-codepoint characters as "surrogate pairs",
// which occupy two indices in the javascript string.
// We need to translate cursor_pos in the protocol (in characters)
// to js offset (with surrogate pairs taking two spots).
function js_idx_to_char_idx (js_idx, text) {
var char_idx = js_idx;
for (var i = 0; i + 1 < text.length && i < js_idx; i++) {
var char_code = text.charCodeAt(i);
// check for surrogate pair
if (char_code >= 0xD800 && char_code <= 0xDBFF) {
var next_char_code = text.charCodeAt(i+1);
if (next_char_code >= 0xDC00 && next_char_code <= 0xDFFF) {
char_idx--;
i++;
}
}
}
return char_idx;
}
function char_idx_to_js_idx (char_idx, text) {
var js_idx = char_idx;
for (var i = 0; i + 1 < text.length && i < js_idx; i++) {
var char_code = text.charCodeAt(i);
// check for surrogate pair
if (char_code >= 0xD800 && char_code <= 0xDBFF) {
var next_char_code = text.charCodeAt(i+1);
if (next_char_code >= 0xDC00 && next_char_code <= 0xDFFF) {
js_idx++;
i++;
}
}
}
return js_idx;
}
if ('𝐚'.length === 1) {
// If javascript fixes string indices of non-BMP characters,
// don't keep shifting offsets to compensate for surrogate pairs
char_idx_to_js_idx = js_idx_to_char_idx = function (idx, text) { return idx; };
}
// Test if a drag'n'drop event contains a file (as opposed to an HTML
// element/text from the document)
var dnd_contain_file = function(event) {
@ -1027,6 +1098,21 @@ define([
fn();
}
}
var change_favicon = function (src) {
var link = document.createElement('link'),
oldLink = document.getElementById('favicon');
link.id = 'favicon';
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = utils.url_path_join(utils.get_body_data('baseUrl'), src);
if (oldLink && (link.href === oldLink.href)) {
// This favicon is already set, don't modify the DOM.
return;
}
if (oldLink) document.head.removeChild(oldLink);
document.head.appendChild(link);
};
var utils = {
throttle: throttle,
@ -1077,7 +1163,10 @@ define([
format_datetime: format_datetime,
datetime_sort_helper: datetime_sort_helper,
dnd_contain_file: dnd_contain_file,
_ansispan:_ansispan
js_idx_to_char_idx: js_idx_to_char_idx,
char_idx_to_js_idx: char_idx_to_js_idx,
_ansispan:_ansispan,
change_favicon: change_favicon
};
return utils;

@ -27,6 +27,10 @@ body > #header {
z-index: 100;
#header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px;
padding-bottom: 5px;
padding-top: 5px;
.border-box-sizing();
@ -57,9 +61,6 @@ body > #header {
padding-left: 0px;
padding-top: (@navbar-height - @logo_height) / 2;
padding-bottom: (@navbar-height - @logo_height) / 2;
@media (max-width: @screen-sm-max){
margin-left: 10px;
}
}
[dir="rtl"] #ipython_notebook {
@ -71,6 +72,10 @@ body > #header {
.pull-right();
}
.flex-spacer {
flex: 1;
}
#noscript {
width: auto;
padding-top: 16px;
@ -106,6 +111,10 @@ input.ui-button {
padding: 0.3em 0.9em;
}
span#kernel_logo_widget {
margin: 0 10px;
}
span#login_widget {
float: right;
}

@ -63,16 +63,6 @@
* });
* });
*
* __Example 3:__
*
* Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
* to load custom script into the notebook.
*
* // to load the metadata ui extension example.
* $.getScript('/static/notebook/js/celltoolbarpresets/example.js');
* // or
* // to load the metadata ui extension to control slideshow mode / reveal js for nbconvert
* $.getScript('/static/notebook/js/celltoolbarpresets/slideshow.js');
*
*
* @module IPython

@ -32,6 +32,8 @@ function(
this.base_url = options.base_url;
this.file_path = options.file_path;
this.config = options.config;
this.file_extension_modes = options.file_extension_modes || {};
this.codemirror = new CodeMirror($(this.selector)[0]);
this.codemirror.on('changes', function(cm, changes){
that._clean_state();
@ -54,6 +56,16 @@ function(
);
that._set_codemirror_options(cmopts);
that.events.trigger('config_changed.Editor', {config: that.config});
if (cfg.file_extension_modes) {
// check for file extension in user preferences
var modename = cfg.file_extension_modes[that._get_file_extension()];
if (modename) {
var modeinfo = CodeMirror.findModeByName(modename);
if (modeinfo) {
that.set_codemirror_mode(modeinfo);
}
}
}
that._clean_state();
});
this.clean_sel = $('<div/>');
@ -99,7 +111,7 @@ function(
}
);
};
Editor.prototype._set_mode_for_model = function (model) {
/** Set the CodeMirror mode based on the file model */
@ -107,20 +119,28 @@ function(
// first by mime-type, then by file extension
var modeinfo;
// mimetype is unset on file rename
if (model.mimetype) {
modeinfo = CodeMirror.findModeByMIME(model.mimetype);
var ext = this._get_file_extension();
if (ext) {
// check if a mode has been remembered for this extension
var modename = this.file_extension_modes[ext];
if (modename) {
modeinfo = CodeMirror.findModeByName(modename);
}
}
// prioritize CodeMirror's filename identification
if (!modeinfo || modeinfo.mode === "null") {
// find by mime failed, use find by ext
var ext_idx = model.name.lastIndexOf('.');
if (ext_idx > 0) {
// CodeMirror.findModeByExtension wants extension without '.'
modeinfo = CodeMirror.findModeByExtension(
model.name.slice(ext_idx + 1).toLowerCase());
modeinfo = CodeMirror.findModeByFileName(model.name);
// codemirror's filename identification is case-sensitive.
// try once more with lowercase extension
if (!modeinfo && ext) {
// CodeMirror wants lowercase ext without leading '.'
modeinfo = CodeMirror.findModeByExtension(ext.slice(1).toLowerCase());
}
}
if (model.mimetype && (!modeinfo || modeinfo.mode === "null")) {
// mimetype is not set on file rename
modeinfo = CodeMirror.findModeByMIME(model.mimetype);
}
if (modeinfo) {
this.set_codemirror_mode(modeinfo);
}
@ -134,11 +154,41 @@ function(
that.events.trigger("mode_changed.Editor", modeinfo);
});
};
Editor.prototype.save_codemirror_mode = function (modeinfo) {
/** save the selected codemirror mode for the current extension in config */
var update_mode_map = {};
var ext = this._get_file_extension();
// no extension, nothing to save
// TODO: allow remembering no-extension things like Makefile?
if (!ext) return;
update_mode_map[ext] = modeinfo.name;
return this.config.update({
Editor: {
file_extension_modes: update_mode_map,
}
})
};
Editor.prototype.get_filename = function () {
return utils.url_path_split(this.file_path)[1];
};
Editor.prototype._get_file_extension = function () {
/** return file extension *including* .
Returns undefined if no extension is found.
*/
var filename = this.get_filename();
var ext_idx = filename.lastIndexOf('.');
if (ext_idx < 0) {
return;
} else {
return filename.slice(ext_idx);
}
};
Editor.prototype.rename = function (new_name) {
/** rename the file */
var that = this;
@ -203,7 +253,7 @@ function(
});
var that = this;
};
Editor.prototype.update_codemirror_options = function (options) {
/** update codemirror options locally and save changes in config */
var that = this;

@ -39,7 +39,7 @@ require([
console.warn(err);
}
page = new page.Page();
page = new page.Page('div#header', 'div#site');
var base_url = utils.get_body_data('baseUrl');
var file_path = utils.get_body_data('filePath');

@ -146,6 +146,8 @@ define([
function make_set_mode(info) {
return function () {
editor.set_codemirror_mode(info);
// save codemirror mode for extension when explicitly selected
editor.save_codemirror_mode(info);
};
}
for (var i = 0; i < CodeMirror.modeInfo.length; i++) {

@ -80,6 +80,11 @@ define([
class: "btn-primary",
click: function () {
var new_name = d.find('input').val();
if (!new_name) {
// Reset the message
d.find('.rename-message').text("Enter a new filename:");
return false;
}
d.find('.rename-message').text("Renaming...");
d.find('input[type="text"]').prop('disabled', true);
that.editor.rename(new_name).then(

@ -49,3 +49,7 @@
}
}
}
.CodeMirror-dialog {
background-color: @page-color;
}

@ -0,0 +1 @@
base/images/favicon.ico

@ -3,35 +3,39 @@
require([
'jquery',
'base/js/dialog',
'base/js/i18n',
'underscore',
'base/js/namespace'
], function ($, dialog, _, IPython) {
], function ($, dialog, i18n, _, IPython) {
'use strict';
$('#notebook_about').click(function () {
// use underscore template to auto html escape
if (sys_info) {
var text = 'You are using Jupyter notebook.<br/><br/>';
text = text + 'The version of the notebook server is ';
var text = i18n.msg._('You are using Jupyter notebook.');
text = text + '<br/><br/>';
text = text + i18n.msg._('The version of the notebook server is: ');
text = text + _.template('<b><%- version %></b>')({ version: sys_info.notebook_version });
if (sys_info.commit_hash) {
text = text + _.template('-<%- hash %>')({ hash: sys_info.commit_hash });
}
text = text + _.template(' and is running on:<br/><pre>Python <%- pyver %></pre>')({
text = text + '<br/>';
text = text + i18n.msg._('The server is running on this version of Python:');
text = text + _.template('<br/><pre>Python <%- pyver %></pre>')({
pyver: sys_info.sys_version });
var kinfo = $('<div/>').attr('id', '#about-kinfo').text('Waiting for kernel to be available...');
var kinfo = $('<div/>').attr('id', '#about-kinfo').text(i18n.msg._('Waiting for kernel to be available...'));
var body = $('<div/>');
body.append($('<h4/>').text('Server Information:'));
body.append($('<h4/>').text(i18n.msg._('Server Information:')));
body.append($('<p/>').html(text));
body.append($('<h4/>').text('Current Kernel Information:'));
body.append($('<h4/>').text(i18n.msg._('Current Kernel Information:')));
body.append(kinfo);
} else {
var text = 'Could not access sys_info variable for version information.';
var text = i18n.msg._('Could not access sys_info variable for version information.');
var body = $('<div/>');
body.append($('<h4/>').text('Cannot find sys_info!'));
body.append($('<h4/>').text(i18n.msg._('Cannot find sys_info!')));
body.append($('<p/>').html(text));
}
dialog.modal({
title: 'About Jupyter Notebook',
title: i18n.msg._('About Jupyter Notebook'),
body: body,
buttons: { 'OK': {} }
});
@ -40,7 +44,7 @@ require([
kinfo.html($('<pre/>').text(data.content.banner));
});
} catch (e) {
kinfo.html($('<p/>').text('unable to contact kernel'));
kinfo.html($('<p/>').text(i18n.msg._('unable to contact kernel')));
}
});
});

@ -15,7 +15,9 @@
// * For dialogs, use a verb that indicates what the dialog will accomplish, such as "confirm-restart-kernel".
define(function(require){
define([
'base/js/i18n',
], function(i18n){
"use strict";
var warn_bad_name = function(name){
@ -64,13 +66,15 @@ define(function(require){
**/
var _actions = {
'toggle-rtl-layout': {
help: 'Open a dialog to edit the command mode keyboard shortcuts',
cmd: i18n.msg._('toggle rtl layout'),
help: i18n.msg._('Toggle the screen directionality between left-to-right and right-to-left'),
handler: function () {
(document.body.getAttribute('dir')=='rtl') ? document.body.setAttribute('dir','ltr') : document.body.setAttribute('dir','rtl');
}
},
'edit-command-mode-keyboard-shortcuts': {
help: 'Open a dialog to edit the command mode keyboard shortcuts',
cmd: i18n.msg._('edit command mode keyboard shortcuts'),
help: i18n.msg._('Open a dialog to edit the command mode keyboard shortcuts'),
handler: function (env) {
env.notebook.show_shortcuts_editor();
}
@ -90,7 +94,8 @@ define(function(require){
}
},
'restart-kernel': {
help: 'restart the kernel (no confirmation dialog)',
cmd: i18n.msg._('restart kernel'),
help: i18n.msg._('restart the kernel (no confirmation dialog)'),
handler: function (env) {
env.notebook.restart_kernel({confirm: false});
},
@ -98,131 +103,155 @@ define(function(require){
'confirm-restart-kernel':{
icon: 'fa-repeat',
help_index : 'hb',
help: 'restart the kernel (with dialog)',
cmd: i18n.msg._('confirm restart kernel'),
help: i18n.msg._('restart the kernel (with dialog)'),
handler : function (env) {
env.notebook.restart_kernel();
}
},
'restart-kernel-and-run-all-cells': {
help: 'restart the kernel, then re-run the whole notebook (no confirmation dialog)',
cmd: i18n.msg._('restart kernel and run all cells'),
help: i18n.msg._('restart the kernel, then re-run the whole notebook (no confirmation dialog)'),
handler: function (env) {
env.notebook.restart_run_all({confirm: false});
}
},
'confirm-restart-kernel-and-run-all-cells': {
help: 'restart the kernel, then re-run the whole notebook (with dialog)',
cmd: i18n.msg._('confirm restart kernel and run all cells'),
help: i18n.msg._('restart the kernel, then re-run the whole notebook (with dialog)'),
handler: function (env) {
env.notebook.restart_run_all();
}
},
'restart-kernel-and-clear-output': {
help: 'restart the kernel and clear all output (no confirmation dialog)',
cmd: i18n.msg._('restart kernel and clear output'),
help: i18n.msg._('restart the kernel and clear all output (no confirmation dialog)'),
handler: function (env) {
env.notebook.restart_clear_output({confirm: false});
}
},
'confirm-restart-kernel-and-clear-output': {
help: 'restart the kernel and clear all output (with dialog)',
cmd: i18n.msg._('confirm restart kernel and clear output'),
help: i18n.msg._('restart the kernel and clear all output (with dialog)'),
handler: function (env) {
env.notebook.restart_clear_output();
}
},
'interrupt-kernel':{
icon: 'fa-stop',
cmd: i18n.msg._('interrupt the kernel'),
help: i18n.msg._('interrupt the kernel'),
help_index : 'ha',
handler : function (env) {
env.notebook.kernel.interrupt();
}
},
'run-cell-and-select-next': {
cmd: i18n.msg._('run cell and select next'),
icon: 'fa-step-forward',
help : 'run cell, select below',
help: i18n.msg._('run cell, select below'),
help_index : 'ba',
handler : function (env) {
env.notebook.execute_cell_and_select_below();
}
},
'run-cell':{
help : 'run selected cells',
cmd: i18n.msg._('run selected cells'),
help : i18n.msg._('run selected cells'),
help_index : 'bb',
handler : function (env) {
env.notebook.execute_selected_cells();
}
},
'run-cell-and-insert-below':{
help : 'run cell, insert below',
cmd: i18n.msg._('run cell and insert below'),
help : i18n.msg._('run cell and insert below'),
help_index : 'bc',
handler : function (env) {
env.notebook.execute_cell_and_insert_below();
}
},
'run-all-cells': {
help: 'run all cells',
cmd: i18n.msg._('run all cells'),
help: i18n.msg._('run all cells'),
help_index: 'bd',
handler: function (env) {
env.notebook.execute_all_cells();
}
},
'run-all-cells-above':{
cmd: i18n.msg._('run all cells above'),
help: i18n.msg._('run all cells above'),
handler : function (env) {
env.notebook.execute_cells_above();
}
},
'run-all-cells-below':{
cmd: i18n.msg._('run all cells below'),
help: i18n.msg._('run all cells below'),
handler : function (env) {
env.notebook.execute_cells_below();
}
},
'enter-command-mode': {
help : 'command mode',
cmd: i18n.msg._('enter command mode'),
help : i18n.msg._('enter command mode'),
help_index : 'aa',
handler : function (env) {
env.notebook.command_mode();
}
},
'insert-image': {
help : 'insert image',
cmd: i18n.msg._('insert image'),
help : i18n.msg._('insert image'),
help_index : 'dz',
handler : function (env) {
env.notebook.insert_image();
}
},
'cut-cell-attachments': {
help : 'cut cell attachments',
cmd: i18n.msg._('cut cell attachments'),
help : i18n.msg._('cut cell attachments'),
help_index : 'dza',
handler: function (env) {
env.notebook.cut_cell_attachments();
}
},
'copy-cell-attachments': {
help : 'copy cell attachments',
cmd: i18n.msg._('copy cell attachments'),
help : i18n.msg._('copy cell attachments'),
help_index: 'dzb',
handler: function (env) {
env.notebook.copy_cell_attachments();
}
},
'paste-cell-attachments': {
help : 'paste cell attachments',
cmd: i18n.msg._('paste cell attachments'),
help : i18n.msg._('paste cell attachments'),
help_index: 'dzc',
handler: function (env) {
env.notebook.paste_cell_attachments();
}
},
'split-cell-at-cursor': {
help : 'split cell',
cmd: i18n.msg._('split cell at cursor'),
help : i18n.msg._('split cell at cursor'),
help_index : 'ea',
handler : function (env) {
env.notebook.split_cell();
}
},
'enter-edit-mode' : {
cmd: i18n.msg._('enter edit mode'),
help : i18n.msg._('enter edit mode'),
help_index : 'aa',
handler : function (env) {
env.notebook.edit_mode();
}
},
'select-previous-cell' : {
help: 'select cell above',
cmd: i18n.msg._('select previous cell'),
help: i18n.msg._('select cell above'),
help_index : 'da',
handler : function (env) {
var index = env.notebook.get_selected_index();
@ -233,7 +262,8 @@ define(function(require){
}
},
'select-next-cell' : {
help: 'select cell below',
cmd: i18n.msg._('select next cell'),
help: i18n.msg._('select cell below'),
help_index : 'db',
handler : function (env) {
var index = env.notebook.get_selected_index();
@ -244,29 +274,32 @@ define(function(require){
}
},
'extend-selection-above' : {
help: 'extend selected cells above',
cmd: i18n.msg._('extend selection above'),
help: i18n.msg._('extend selected cells above'),
help_index : 'dc',
handler : function (env) {
env.notebook.extend_selection_by(-1);
// scroll into view,
// do not call notebook.focus_cell(), or
// scroll into view,
// do not call notebook.focus_cell(), or
// all the selection get thrown away
env.notebook.get_selected_cell().element.focus();
}
},
'extend-selection-below' : {
help: 'extend selected cells below',
cmd: i18n.msg._('extend selection below'),
help: i18n.msg._('extend selected cells below'),
help_index : 'dd',
handler : function (env) {
env.notebook.extend_selection_by(1);
// scroll into view,
// do not call notebook.focus_cell(), or
// scroll into view,
// do not call notebook.focus_cell(), or
// all the selection get thrown away
env.notebook.get_selected_cell().element.focus();
}
},
'cut-cell' : {
help: 'cut selected cells',
cmd: i18n.msg._('cut selected cells'),
help: i18n.msg._('cut selected cells'),
icon: 'fa-cut',
help_index : 'ee',
handler : function (env) {
@ -276,22 +309,31 @@ define(function(require){
}
},
'copy-cell' : {
help: 'copy selected cells',
cmd: i18n.msg._('copy selected cells'),
help: i18n.msg._('copy selected cells'),
icon: 'fa-copy',
help_index : 'ef',
handler : function (env) {
env.notebook.copy_cell();
}
},
'paste-cell-replace' : {
help: 'paste cells replace',
handler : function (env) {
env.notebook.paste_cell_replace();
}
},
'paste-cell-above' : {
help: 'paste cells above',
cmd: i18n.msg._('paste cells above'),
help: i18n.msg._('paste cells above'),
help_index : 'eg',
handler : function (env) {
env.notebook.paste_cell_above();
}
},
'paste-cell-below' : {
help: 'paste cells below',
cmd: i18n.msg._('paste cells below'),
help: i18n.msg._('paste cells below'),
icon: 'fa-paste',
help_index : 'eh',
handler : function (env) {
@ -299,7 +341,8 @@ define(function(require){
}
},
'insert-cell-above' : {
help: 'insert cell above',
cmd: i18n.msg._('insert cell above'),
help: i18n.msg._('insert cell above'),
help_index : 'ec',
handler : function (env) {
env.notebook.insert_cell_above();
@ -308,7 +351,8 @@ define(function(require){
}
},
'insert-cell-below' : {
help: 'insert cell below',
cmd: i18n.msg._('insert cell below'),
help: i18n.msg._('insert cell below'),
icon : 'fa-plus',
help_index : 'ed',
handler : function (env) {
@ -318,90 +362,103 @@ define(function(require){
}
},
'change-cell-to-code' : {
help : 'to code',
cmd: i18n.msg._('change cell to code'),
help : i18n.msg._('change cell to code'),
help_index : 'ca',
handler : function (env) {
env.notebook.cells_to_code();
}
},
'change-cell-to-markdown' : {
help : 'to markdown',
cmd: i18n.msg._('change cell to markdown'),
help : i18n.msg._('change cell to markdown'),
help_index : 'cb',
handler : function (env) {
env.notebook.cells_to_markdown();
}
},
'change-cell-to-raw' : {
help : 'to raw',
cmd: i18n.msg._('change cell to raw'),
help : i18n.msg._('change cell to raw'),
help_index : 'cc',
handler : function (env) {
env.notebook.cells_to_raw();
}
},
'change-cell-to-heading-1' : {
help : 'to heading 1',
cmd: i18n.msg._('change cell to heading 1'),
help : i18n.msg._('change cell to heading 1'),
help_index : 'cd',
handler : function (env) {
env.notebook.to_heading(undefined, 1);
}
},
'change-cell-to-heading-2' : {
help : 'to heading 2',
cmd: i18n.msg._('change cell to heading 2'),
help : i18n.msg._('change cell to heading 2'),
help_index : 'ce',
handler : function (env) {
env.notebook.to_heading(undefined, 2);
}
},
'change-cell-to-heading-3' : {
help : 'to heading 3',
cmd: i18n.msg._('change cell to heading 3'),
help : i18n.msg._('change cell to heading 3'),
help_index : 'cf',
handler : function (env) {
env.notebook.to_heading(undefined, 3);
}
},
'change-cell-to-heading-4' : {
help : 'to heading 4',
cmd: i18n.msg._('change cell to heading 4'),
help : i18n.msg._('change cell to heading 4'),
help_index : 'cg',
handler : function (env) {
env.notebook.to_heading(undefined, 4);
}
},
'change-cell-to-heading-5' : {
help : 'to heading 5',
cmd: i18n.msg._('change cell to heading 5'),
help : i18n.msg._('change cell to heading 5'),
help_index : 'ch',
handler : function (env) {
env.notebook.to_heading(undefined, 5);
}
},
'change-cell-to-heading-6' : {
help : 'to heading 6',
cmd: i18n.msg._('change cell to heading 6'),
help : i18n.msg._('change cell to heading 6'),
help_index : 'ci',
handler : function (env) {
env.notebook.to_heading(undefined, 6);
}
},
'toggle-cell-output-collapsed' : {
help : 'toggle output of selected cells',
cmd: i18n.msg._('toggle cell output'),
help : i18n.msg._('toggle output of selected cells'),
help_index : 'gb',
handler : function (env) {
env.notebook.toggle_cells_outputs();
}
},
'toggle-cell-output-scrolled' : {
help : 'toggle output scrolling of selected cells',
cmd: i18n.msg._('toggle cell scrolling'),
help : i18n.msg._('toggle output scrolling of selected cells'),
help_index : 'gc',
handler : function (env) {
env.notebook.toggle_cells_outputs_scroll();
}
},
'clear-cell-output' : {
help : 'clear output of selected cells',
cmd: i18n.msg._('clear cell output'),
help : i18n.msg._('clear output of selected cells'),
handler : function (env) {
env.notebook.clear_cells_outputs();
}
},
'move-cell-down' : {
help: 'move selected cells down',
cmd: i18n.msg._('move cells down'),
help: i18n.msg._('move selected cells down'),
icon: 'fa-arrow-down',
help_index : 'eb',
handler : function (env) {
@ -409,7 +466,8 @@ define(function(require){
}
},
'move-cell-up' : {
help: 'move selected cells up',
cmd: i18n.msg._('move cells up'),
help: i18n.msg._('move selected cells up'),
icon: 'fa-arrow-up',
help_index : 'ea',
handler : function (env) {
@ -417,54 +475,65 @@ define(function(require){
}
},
'toggle-cell-line-numbers' : {
help : 'toggle line numbers',
cmd: i18n.msg._('toggle line numbers'),
help : i18n.msg._('toggle line numbers'),
help_index : 'ga',
handler : function (env) {
env.notebook.cell_toggle_line_numbers();
}
},
'show-keyboard-shortcuts' : {
cmd: i18n.msg._('show keyboard shortcuts'),
help : i18n.msg._('show keyboard shortcuts'),
help_index : 'ge',
handler : function (env) {
env.quick_help.show_keyboard_shortcuts();
}
},
'delete-cell': {
help: 'delete selected cells',
cmd: i18n.msg._('delete cells'),
help: i18n.msg._('delete selected cells'),
help_index : 'ej',
handler : function (env) {
env.notebook.delete_cell();
}
},
'undo-cell-deletion' : {
cmd: i18n.msg._('undo cell deletion'),
help: i18n.msg._('undo cell deletion'),
help_index : 'ei',
handler : function (env) {
env.notebook.undelete_cell();
}
},
// TODO reminder
// open an issue, merge with above merge with last cell of notebook if at top.
// open an issue, merge with above merge with last cell of notebook if at top.
'merge-cell-with-previous-cell' : {
cmd: i18n.msg._('merge cell with previous cell'),
help : i18n.msg._('merge cell above'),
handler : function (env) {
env.notebook.merge_cell_above();
}
},
'merge-cell-with-next-cell' : {
help : 'merge cell below',
cmd: i18n.msg._('merge cell with next cell'),
help : i18n.msg._('merge cell below'),
help_index : 'ek',
handler : function (env) {
env.notebook.merge_cell_below();
}
},
'merge-selected-cells' : {
help : 'merge selected cells',
cmd: i18n.msg._('merge selected cells'),
help : i18n.msg._('merge selected cells'),
help_index: 'el',
handler: function(env) {
env.notebook.merge_selected_cells();
}
},
'merge-cells' : {
help : 'merge selected cells, or current cell with cell below if only one cell selected',
cmd: i18n.msg._('merge cells'),
help : i18n.msg._('merge selected cells, or current cell with cell below if only one cell is selected'),
help_index: 'el',
handler: function(env) {
var l = env.notebook.get_selected_cells_indices().length;
@ -477,14 +546,16 @@ define(function(require){
},
'show-command-palette': {
help_index : 'aa',
help: 'open the command palette',
cmd: i18n.msg._('show command pallette'),
help: i18n.msg._('open the command palette'),
icon: 'fa-keyboard-o',
handler : function(env){
env.notebook.show_command_palette();
}
},
'toggle-all-line-numbers': {
help : 'toggles line numbers in all cells, and persist the setting',
cmd: i18n.msg._('toggle all line numbers'),
help : i18n.msg._('toggles line numbers in all cells, and persist the setting'),
icon: 'fa-list-ol',
handler: function(env) {
var value = !env.notebook.line_numbers;
@ -495,7 +566,8 @@ define(function(require){
}
},
'show-all-line-numbers': {
help : 'show line numbers in all cells, and persist the setting',
cmd: i18n.msg._('show all line numbers'),
help : i18n.msg._('show line numbers in all cells, and persist the setting'),
handler: function(env) {
env.notebook.get_cells().map(function(c) {
c.code_mirror.setOption('lineNumbers', true);
@ -504,7 +576,8 @@ define(function(require){
}
},
'hide-all-line-numbers': {
help : 'hide line numbers in all cells, and persist the setting',
cmd: i18n.msg._('hide all line numbers'),
help : i18n.msg._('hide line numbers in all cells, and persist the setting'),
handler: function(env) {
env.notebook.get_cells().map(function(c) {
c.code_mirror.setOption('lineNumbers', false);
@ -513,7 +586,8 @@ define(function(require){
}
},
'toggle-header':{
help: 'hide/show the header',
cmd: i18n.msg._('toggle header'),
help: i18n.msg._('switch between showing and hiding the header'),
handler : function(env) {
var value = !env.notebook.header;
if (value === true) {
@ -528,7 +602,8 @@ define(function(require){
}
},
'show-header':{
help: 'show the header',
cmd: i18n.msg._('show the header'),
help: i18n.msg._('show the header'),
handler : function(env) {
$('#header-container').show();
$('.header-bar').show();
@ -537,7 +612,8 @@ define(function(require){
}
},
'hide-header':{
help: 'hide the header',
cmd: i18n.msg._('hide the header'),
help: i18n.msg._('hide the header'),
handler : function(env) {
$('#header-container').hide();
$('.header-bar').hide();
@ -545,8 +621,30 @@ define(function(require){
env.notebook.header = false;
}
},
'toggle-menubar':{
help: 'hide/show the menu bar',
handler : function(env) {
$('#menubar-container').toggle();
events.trigger('resize-header.Page');
}
},
'show-menubar':{
help: 'show the menu bar',
handler : function(env) {
$('#menubar-container').show();
events.trigger('resize-header.Page');
}
},
'hide-menubar':{
help: 'hide the menu bar',
handler : function(env) {
$('#menubar-container').hide();
events.trigger('resize-header.Page');
}
},
'toggle-toolbar':{
help: 'hide/show the toolbar',
cmd: i18n.msg._('toggle toolbar'),
help: i18n.msg._('switch between showing and hiding the toolbar'),
handler : function(env) {
var value = !env.notebook.toolbar;
if (value === true) {
@ -559,7 +657,8 @@ define(function(require){
}
},
'show-toolbar':{
help: 'show the toolbar',
cmd: i18n.msg._('show the toolbar'),
help: i18n.msg._('show the toolbar'),
handler : function(env) {
$('div#maintoolbar').show();
events.trigger('resize-header.Page');
@ -567,7 +666,8 @@ define(function(require){
}
},
'hide-toolbar':{
help: 'hide the toolbar',
cmd: i18n.msg._('hide the toolbar'),
help: i18n.msg._('hide the toolbar'),
handler : function(env) {
$('div#maintoolbar').hide();
events.trigger('resize-header.Page');
@ -575,7 +675,8 @@ define(function(require){
}
},
'close-pager': {
help : 'close the pager',
cmd: i18n.msg._('close the pager'),
help : i18n.msg._('close the pager'),
handler : function(env) {
// Collapse the page if it is open
if (env.pager && env.pager.expanded) {
@ -600,11 +701,14 @@ define(function(require){
**/
var custom_ignore = {
'ignore':{
cmd: i18n.msg._('ignore'),
handler : function () {
return true;
}
},
'move-cursor-up':{
cmd: i18n.msg._('move cursor up'),
help: i18n.msg._("move cursor up"),
handler : function (env, event) {
var index = env.notebook.get_selected_index();
var cell = env.notebook.get_cell(index);
@ -624,6 +728,8 @@ define(function(require){
}
},
'move-cursor-down':{
cmd: i18n.msg._('move cursor down'),
help: i18n.msg._("move cursor down"),
handler : function (env, event) {
var index = env.notebook.get_selected_index();
var cell = env.notebook.get_cell(index);
@ -641,6 +747,8 @@ define(function(require){
}
},
'scroll-notebook-down': {
cmd: i18n.msg._('scroll notebook down'),
help: i18n.msg._("scroll notebook down"),
handler: function(env, event) {
if(event){
event.preventDefault();
@ -649,6 +757,8 @@ define(function(require){
},
},
'scroll-notebook-up': {
cmd: i18n.msg._('scroll notebook up'),
help: i18n.msg._("scroll notebook up"),
handler: function(env, event) {
if(event){
event.preventDefault();
@ -657,7 +767,8 @@ define(function(require){
},
},
'scroll-cell-center': {
help: "Scroll the current cell to the center",
cmd: i18n.msg._('scroll cell center'),
help: i18n.msg._("Scroll the current cell to the center"),
handler: function (env, event) {
if(event){
event.preventDefault();
@ -667,7 +778,8 @@ define(function(require){
}
},
'scroll-cell-top': {
help: "Scroll the current cell to the top",
cmd: i18n.msg._('scroll cell top'),
help: i18n.msg._("Scroll the current cell to the top"),
handler: function (env, event) {
if(event){
event.preventDefault();
@ -677,44 +789,51 @@ define(function(require){
}
},
'duplicate-notebook':{
help: "Create an open a copy of current notebook",
cmd: i18n.msg._('duplicate notebook'),
help: i18n.msg._("Create and open a copy of the current notebook"),
handler : function (env, event) {
env.notebook.copy_notebook();
}
},
'trust-notebook':{
help: "Trust the current notebook",
cmd: i18n.msg._('trust notebook'),
help: i18n.msg._("Trust the current notebook"),
handler : function (env, event) {
env.notebook.trust_notebook();
}
},
'rename-notebook':{
help: "Rename current notebook",
cmd: i18n.msg._('rename notebook'),
help: i18n.msg._("Rename the current notebook"),
handler : function (env, event) {
env.notebook.save_widget.rename_notebook({notebook: env.notebook});
}
},
'toggle-all-cells-output-collapsed':{
help: "Toggle the hiddens state of all output areas",
cmd: i18n.msg._('toggle all cells output collapsed'),
help: i18n.msg._("Toggle the hidden state of all output areas"),
handler : function (env, event) {
env.notebook.toggle_all_output();
}
},
'toggle-all-cells-output-scrolled':{
help: "Toggle the scrolling state of all output areas",
cmd: i18n.msg._('toggle all cells output scrolled'),
help: i18n.msg._("Toggle the scrolling state of all output areas"),
handler : function (env, event) {
env.notebook.toggle_all_output_scroll();
}
},
'clear-all-cells-output':{
help: "Clear the content of all the outputs",
cmd: i18n.msg._('clear all cells output'),
help: i18n.msg._("Clear the content of all the outputs"),
handler : function (env, event) {
env.notebook.clear_all_output();
}
},
'save-notebook':{
help: "Save and Checkpoint",
cmd: i18n.msg._('save notebook'),
help: i18n.msg._("Save and Checkpoint"),
help_index : 'fb',
icon: 'fa-save',
handler : function (env, event) {
@ -731,6 +850,7 @@ define(function(require){
// and uniformize/fill in missing pieces in of an action.
var _prepare_handler = function(registry, subkey, source){
registry['jupyter-notebook:'+subkey] = {};
registry['jupyter-notebook:'+subkey].cmd = source[subkey].cmd;
registry['jupyter-notebook:'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
registry['jupyter-notebook:'+subkey].help_index = source[subkey].help_index;
registry['jupyter-notebook:'+subkey].icon = source[subkey].icon;

@ -12,12 +12,13 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'codemirror/lib/codemirror',
'codemirror/addon/edit/matchbrackets',
'codemirror/addon/edit/closebrackets',
'codemirror/addon/comment/comment',
'services/config',
], function($, utils, CodeMirror, cm_match, cm_closeb, cm_comment, configmod) {
], function($, utils, i18n, CodeMirror, cm_match, cm_closeb, cm_comment, configmod) {
"use strict";
var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
@ -75,7 +76,7 @@ define([
// backward compat.
Object.defineProperty(this, 'cm_config', {
get: function() {
console.warn("Warning: accessing Cell.cm_config directly is deprecated.");
console.warn(i18n.msg._("Warning: accessing Cell.cm_config directly is deprecated."));
return that._options.cm_config;
},
});
@ -133,8 +134,8 @@ define([
"Cmd-Left": "goLineLeft",
"Tab": "indentMore",
"Shift-Tab" : "indentLess",
"Cmd-Alt-[" : "indentAuto",
"Ctrl-Alt-[" : "indentAuto",
// "Cmd-Alt-[" : "indentAuto",
// "Ctrl-Alt-[" : "indentAuto",
"Cmd-/" : "toggleComment",
"Ctrl-/" : "toggleComment",
}
@ -759,7 +760,7 @@ define([
} else {
data.metadata = this.metadata;
}
this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
this.element.find('.inner_cell').find("a").text(i18n.msg.sprintf(i18n.msg._("Unrecognized cell type: %s"), data.cell_type));
};
UnrecognizedCell.prototype.create_element = function () {
@ -773,7 +774,7 @@ define([
inner_cell.append(
$("<a>")
.attr("href", "#")
.text("Unrecognized cell type")
.text(i18n.msg._("Unrecognized cell type"))
);
cell.append(inner_cell);
this.element = cell;

@ -4,8 +4,9 @@
define([
'jquery',
'base/js/namespace',
'base/js/events'
], function($, IPython, events) {
'base/js/events',
'base/js/i18n'
], function($, IPython, events, i18n) {
"use strict";
var CellToolbar = function (options) {
@ -292,7 +293,7 @@ define([
callback(local_div, this.cell, this);
this.ui_controls_list.push(key);
} catch (e) {
console.log("Error in cell toolbar callback " + key, e);
console.log(i18n.msg.sprintf(i18n.msg._("Error in cell toolbar callback %s"), key), e);
continue;
}
// only append if callback succeeded.

@ -4,7 +4,8 @@
define([
'notebook/js/celltoolbar',
'base/js/dialog',
], function(celltoolbar, dialog) {
'base/js/i18n'
], function(celltoolbar, dialog, i18n) {
"use strict";
var CellToolbar = celltoolbar.CellToolbar;
@ -18,7 +19,7 @@ define([
cell.unrender();
cell.render();
},
name: 'cell',
name: 'Cell',
notebook: cell.notebook,
keyboard_manager: cell.keyboard_manager
});
@ -28,7 +29,7 @@ define([
var button_container = $(div);
var button = $('<button />')
.addClass('btn btn-default btn-xs')
.text('Edit Attachments')
.text(i18n.msg._('Edit Attachments'))
.click( function() {
edit_attachments_dialog(cell);
return false;
@ -42,7 +43,7 @@ define([
var attachments_preset = [];
attachments_preset.push('attachments.edit');
CellToolbar.register_preset('Attachments', attachments_preset, notebook);
CellToolbar.register_preset(i18n.msg._('Attachments'), attachments_preset, notebook);
};
return {'register' : register};

@ -4,7 +4,8 @@
define([
'notebook/js/celltoolbar',
'base/js/dialog',
], function(celltoolbar, dialog) {
'base/js/i18n'
], function(celltoolbar, dialog, i18n) {
"use strict";
var CellToolbar = celltoolbar.CellToolbar;
@ -15,7 +16,7 @@ define([
callback: function (md) {
cell.metadata = md;
},
name: 'Cell',
name: i18n.msg._('Cell'),
notebook: this.notebook,
keyboard_manager: this.keyboard_manager
});
@ -25,7 +26,7 @@ define([
var button_container = $(div);
var button = $('<button/>')
.addClass("btn btn-default btn-xs")
.text("Edit Metadata")
.text(i18n.msg._("Edit Metadata"))
.click( function () {
raw_edit(cell);
return false;
@ -43,7 +44,7 @@ define([
var example_preset = [];
example_preset.push('default.rawedit');
CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
CellToolbar.register_preset(i18n.msg._('Edit Metadata'), example_preset, notebook);
};
return {'register': register};
});

@ -1,13 +1,6 @@
// Copyright (c) Jupyter 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([
'notebook/js/celltoolbar',
], function(celltoolbar) {

@ -5,20 +5,21 @@ define([
'notebook/js/celltoolbar',
'base/js/dialog',
'base/js/keyboard',
], function(celltoolbar, dialog, keyboard) {
'base/js/i18n'
], function(celltoolbar, dialog, keyboard, i18n) {
"use strict";
var CellToolbar = celltoolbar.CellToolbar;
var raw_cell_preset = [];
var select_type = CellToolbar.utils.select_ui_generator([
["None", "-"],
[i18n.msg._("None"), "-"],
["LaTeX", "text/latex"],
["reST", "text/restructuredtext"],
["HTML", "text/html"],
["Markdown", "text/markdown"],
["Python", "text/x-python"],
["Custom", "dialog"],
[i18n.msg._("Custom"), "dialog"],
],
// setter
@ -28,7 +29,7 @@ define([
} else if (value === 'dialog'){
var dialog = $('<div/>').append(
$("<p/>")
.text("Set the MIME type of the raw cell:")
.text(i18n.msg._("Set the MIME type of the raw cell:"))
).append(
$("<br/>")
).append(
@ -36,7 +37,7 @@ define([
.val(cell.metadata.raw_mimetype || "-")
);
dialog.modal({
title: "Raw Cell MIME Type",
title: i18n.msg._("Raw Cell MIME Type"),
body: dialog,
buttons : {
"Cancel": {},
@ -70,14 +71,14 @@ define([
return cell.metadata.raw_mimetype || "";
},
// name
"Raw NBConvert Format"
i18n.msg._("Raw NBConvert Format")
);
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, notebook);
CellToolbar.register_preset(i18n.msg._('Raw Cell Format'), raw_cell_preset, notebook);
};
return {'register': register};

@ -3,20 +3,20 @@
define([
'notebook/js/celltoolbar',
], function(celltoolbar) {
'base/js/i18n'
], function(celltoolbar, i18n) {
"use strict";
var CellToolbar = celltoolbar.CellToolbar;
var slideshow_preset = [];
var select_type = CellToolbar.utils.select_ui_generator([
["-" ,"-" ],
["Slide" ,"slide" ],
["Sub-Slide" ,"subslide" ],
["Fragment" ,"fragment" ],
["Skip" ,"skip" ],
["Notes" ,"notes" ],
[i18n.msg._("Slide") ,"slide" ],
[i18n.msg._("Sub-Slide") ,"subslide" ],
[i18n.msg._("Fragment") ,"fragment" ],
[i18n.msg._("Skip") ,"skip" ],
[i18n.msg._("Notes") ,"notes" ],
],
// setter
function(cell, value){
@ -32,13 +32,13 @@ define([
// return the value
return (ns === undefined)? undefined: ns.slide_type;
},
"Slide Type");
i18n.msg._("Slide Type"));
var register = function (notebook) {
CellToolbar.register_callback('slideshow.select',select_type);
slideshow_preset.push('slideshow.select');
CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
CellToolbar.register_preset(i18n.msg._('Slideshow'),slideshow_preset, notebook);
};
return {'register': register};
});

@ -4,7 +4,8 @@
define([
'notebook/js/celltoolbar',
'base/js/dialog',
], function(celltoolbar, dialog) {
'base/js/i18n'
], function(celltoolbar, dialog, i18n) {
"use strict";
var CellToolbar = celltoolbar.CellToolbar;
@ -13,7 +14,7 @@ define([
return a.filter(function(n) {
return b.indexOf(n) === -1;
});
}
};
var write_tag = function(cell, name, add) {
if (add) {
@ -43,12 +44,12 @@ define([
}
cell.events.trigger('set_dirty.Notebook', {value: true});
return true;
}
};
var preprocess_input = function(input) {
// Split on whitespace:
return input.split(/\s/);
}
// Split on whitespace + commas:
return input.split(/[,\s]+/)
};
var add_tag = function(cell, tag_container, on_remove) {
return function(name) {
@ -111,14 +112,12 @@ define([
.addClass('cell-tag')
.text(name);
var remove_button = $('<a/>')
var remove_button = $('<i/>')
.addClass('remove-tag-btn')
.text('X')
.click( function(ev) {
if (ev.button === 0) {
on_remove(name);
return false;
}
.addClass('fa fa-times')
.click(function () {
on_remove(name);
return false;
});
tag_UI.append(remove_button);
return tag_UI;
@ -131,7 +130,7 @@ define([
var text = $('<input/>').attr('type', 'text');
var button = $('<button />')
.addClass('btn btn-default btn-xs')
.text('Add tag')
.text(i18n.msg._('Add tag'))
.click(function() {
var tags = preprocess_input(text[0].value);
for (var i=0; i < tags.length; ++i) {
@ -148,7 +147,7 @@ define([
}
});
var input_container = $('<span/>')
.addClass('tags-input')
.addClass('tags-input');
add_dialog_button(input_container, cell, on_add, on_remove);
button_container.append(input_container
.append(text)
@ -161,8 +160,8 @@ define([
var tag_list = cell.metadata.tags || [];
var message =
"Edit the list of tags below. All whitespace " +
"is treated as tag separators.";
i18n.msg._("Edit the list of tags below. All whitespace " +
"is treated as tag separators.");
var textarea = $('<textarea/>')
.attr('rows', '13')
@ -170,7 +169,7 @@ define([
.attr('name', 'tags')
.text(tag_list.join('\n'));
var dialogform = $('<div/>').attr('title', 'Edit the tags')
var dialogform = $('<div/>').attr('title', i18n.msg._('Edit the tags'))
.append(
$('<form/>').append(
$('<fieldset/>').append(
@ -184,7 +183,7 @@ define([
);
var modal_obj = dialog.modal({
title: "Edit Tags",
title: i18n.msg._("Edit Tags"),
body: dialogform,
default_button: "Cancel",
buttons: {

@ -5,8 +5,9 @@ define([
'jquery',
'base/js/namespace',
'base/js/utils',
'base/js/i18n',
'base/js/dialog'
], function($, Jupyter, utils, dialog) {
], function($, Jupyter, utils, i18n, dialog) {
var jcbprefix = '<pre class="jupyter-nb-cells-json">';
var jcbsuffix = '</pre>';
@ -49,7 +50,7 @@ function paste(event) {
if (Jupyter.notebook.mode !== 'command') {
return;
}
console.log('Clipboard types: ' + event.clipboardData.types);
console.log(i18n.msg.sprintf(i18n.msg._('Clipboard types: %s'),event.clipboardData.types));
cells = load_json(event.clipboardData);
// console.log(cells);
// Does this JSON look like cells?
@ -92,7 +93,7 @@ function setup_paste_dialog() {
// second Ctrl-V
var action = {
icon: 'fa-clipboard', // a font-awesome class used on buttons, etc
help : 'Dialog for paste from system clipboard',
help : i18n.msg._('Dialog for paste from system clipboard'),
help_index : 'zz',
handler : function () {
var entry_box = $('<input type="text"/>');
@ -105,20 +106,23 @@ function setup_paste_dialog() {
document.removeEventListener('paste', paste_close_dlg);
}
document.addEventListener('paste', paste_close_dlg);
var cmdtrl = 'Ctrl';
var cmdtrl = i18n.msg._('Ctrl-V');
if (utils.platform === 'MacOS') {
cmdtrl = 'Cmd';
cmdtrl = i18n.msg._('Cmd-V');
}
var dialog_body = $("<div/>").append("<p>Press "+cmdtrl+"-V again to paste")
var dialog_body = $("<div/>").append("<p>").append(i18n.msg.sprintf(i18n.msg._("Press %s again to paste"),cmdtrl))
.append("<br/>")
.append("<p><b>Why is this needed?</b> We can't get paste events in this browser without a text box. "+
"There's an invisible text box focused in this dialog.")
.append("<p><b>")
.append(i18n.msg._("Why is this needed? "))
.append("</b>")
.append(i18n.msg._("We can't get paste events in this browser without a text box. "))
.append(i18n.msg._("There's an invisible text box focused in this dialog."))
.append($("<form/>").append(entry_box));
var paste_dlg = dialog.modal({
notebook: Jupyter.notebook,
keyboard_manager: Jupyter.keyboard_manager,
title : cmdtrl+"-V to paste",
title : i18n.msg.sprintf(i18n.msg._("%s to paste"),cmdtrl),
body : dialog_body,
open: function() {
entry_box.focus();

@ -13,6 +13,7 @@ define([
'jquery',
'base/js/namespace',
'base/js/utils',
'base/js/i18n',
'base/js/keyboard',
'services/config',
'notebook/js/cell',
@ -26,6 +27,7 @@ define([
$,
IPython,
utils,
i18n,
keyboard,
configmod,
cell,
@ -305,15 +307,24 @@ define([
*/
CodeCell.prototype.execute = function (stop_on_error) {
if (!this.kernel) {
console.log("Can't execute cell since kernel is not set.");
console.log(i18n.msg._("Can't execute cell since kernel is not set."));
return;
}
if (stop_on_error === undefined) {
stop_on_error = true;
if (this.metadata !== undefined &&
this.metadata.tags !== undefined) {
if (this.metadata.tags.indexOf('raises-exception') !== -1) {
stop_on_error = false;
} else {
stop_on_error = true;
}
} else {
stop_on_error = true;
}
}
this.output_area.clear_output(false, true);
this.clear_output(false, true);
var old_msg_id = this.last_msg_id;
if (old_msg_id) {
this.kernel.clear_callbacks_for_msg(old_msg_id);
@ -335,11 +346,13 @@ define([
this.render();
this.events.trigger('execute.CodeCell', {cell: this});
var that = this;
this.events.on('finished_iopub.Kernel', function (evt, data) {
function handleFinished(evt, data) {
if (that.kernel.id === data.kernel.id && that.last_msg_id === data.msg_id) {
that.events.trigger('finished_execute.CodeCell', {cell: that});
}
});
that.events.trigger('finished_execute.CodeCell', {cell: that});
that.events.off('finished_iopub.Kernel', handleFinished);
}
}
this.events.on('finished_iopub.Kernel', handleFinished);
};
/**
@ -465,7 +478,7 @@ define([
} else {
ns = encodeURIComponent(prompt_value);
}
return 'In&nbsp;[' + ns + ']:';
return '<bdi>'+i18n.msg._('In')+'</bdi>&nbsp;[' + ns + ']:';
};
CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
@ -507,8 +520,9 @@ define([
};
CodeCell.prototype.clear_output = function (wait) {
this.output_area.clear_output(wait);
CodeCell.prototype.clear_output = function (wait, ignore_queue) {
this.events.trigger('clear_output.CodeCell', {cell: this});
this.output_area.clear_output(wait, ignore_queue);
this.set_input_prompt();
};

@ -33,14 +33,15 @@
return CodeMirror.multiplexingMode(
gfm_mode,
// By defining the $$ delimiter before the $ delimiter we don't run
// into the problem that $$ is interpreted as two consecutive $.
{
open: "$", close: "$",
open: "$$", close: "$$",
mode: tex_mode,
delimStyle: "delimit"
},
{
// not sure this works as $$ is interpreted at (opening $, closing $, as defined just above)
open: "$$", close: "$$",
open: "$", close: "$",
mode: tex_mode,
delimStyle: "delimit"
},

@ -1,13 +1,14 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
define(function(require){
define([
'jquery',
'typeahead',
'base/js/i18n',
'notebook/js/quickhelp'
],function($, typeahead, i18n, QH){
"use strict";
var $ = require('jquery');
require('typeahead');
var QH = require("notebook/js/quickhelp");
/**
* Humanize the action name to be consumed by user.
* internaly the actions anem are of the form
@ -150,13 +151,20 @@ define(function(require){
short = QH.humanize_sequence(short);
}
var display_text;
if (action.cmd) {
display_text = i18n.msg._(action.cmd);
} else {
display_text = humanize_action_id(action_id);
}
src[group].data.push({
display: humanize_action_id(action_id),
display: display_text,
shortcut: short,
mode_shortcut: get_mode_for_action_id(action_id, notebook),
group: group,
icon: action.icon,
help: action.help,
help: i18n.msg._(action.help),
key: action_id,
});
}
@ -164,7 +172,11 @@ define(function(require){
// now src is the right structure for typeahead
input.typeahead({
emptyTemplate: "No results found for <pre>{{query}}</pre>",
emptyTemplate: function(query) {
return $('<div>').text("No results found for ").append(
$('<code>').text(query)
);
},
maxItem: 1e3,
minLength: 0,
hint: true,

@ -153,6 +153,8 @@ define([
// one kernel completion came back, finish_completing will be called with the results
// we fork here and directly call finish completing if kernel is busy
var cursor_pos = this.editor.indexFromPos(cur);
var text = this.editor.getValue();
cursor_pos = utils.js_idx_to_char_idx(cursor_pos, text);
if (this.skip_kernel_completion) {
this.finish_completing({ content: {
matches: [],
@ -160,7 +162,7 @@ define([
cursor_end: cursor_pos,
}});
} else {
this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
this.cell.kernel.complete(text, cursor_pos,
$.proxy(this.finish_completing, this)
);
}
@ -175,6 +177,7 @@ define([
var start = content.cursor_start;
var end = content.cursor_end;
var matches = content.matches;
console.log(content);
var cur = this.editor.getCursor();
if (end === null) {
@ -187,7 +190,13 @@ define([
} else if (start < 0) {
start = end + start;
}
} else {
// handle surrogate pairs
var text = this.editor.getValue();
end = utils.char_idx_to_js_idx(end, text);
start = utils.char_idx_to_js_idx(start, text);
}
var results = CodeMirror.contextHint(this.editor);
var filtered_results = [];
//remove results from context completion

@ -5,8 +5,9 @@ define([
'jquery',
'base/js/namespace',
'base/js/dialog',
'base/js/utils'
], function($, IPython, dialog, utils) {
'base/js/utils',
'base/js/i18n'
], function($, IPython, dialog, utils, i18n) {
"use strict";
var KernelSelector = function(selector, notebook) {
@ -265,15 +266,19 @@ define([
);
});
var no_kernel_msg = i18n.msg.sprintf(i18n.msg._("Could not find a kernel matching %s. Please select a kernel:"),
(data.selected.display_name || data.selected.name))
var body = $("<form>").addClass("form-inline").append(
$("<span>").text(
"I couldn't find a kernel matching " + (data.selected.display_name || data.selected.name) + "." +
" Please select a kernel:"
)
$("<span>").text(no_kernel_msg)
).append(select);
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("Continue Without Kernel"), i18n.msg._("Set Kernel"), i18n.msg._("OK") ];
dialog.modal({
title : 'Kernel not found',
title : i18n.msg._('Kernel not found'),
body : body,
buttons : {
'Continue Without Kernel' : {
@ -311,8 +316,8 @@ define([
function(error) {
w.close();
dialog.modal({
title : 'Creating Notebook Failed',
body : "The error was: " + error.message,
title : i18n.msg._('Creating Notebook Failed'),
body : i18n.msg.sprintf(i18n.msg._("The error was: %s"), error.message),
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}

@ -103,7 +103,7 @@ require([
// Instantiate the main objects
var page = new page.Page();
var page = new page.Page('div#header', 'div#site');
var pager = new pager.Pager('div#pager', {
events: events});
var acts = new actions.init();

@ -5,8 +5,9 @@ define([
'jquery',
'require',
'./toolbar',
'./celltoolbar'
], function($, require, toolbar, celltoolbar) {
'./celltoolbar',
'base/js/i18n'
], function($, require, toolbar, celltoolbar, i18n) {
"use strict";
var MainToolBar = function (selector, options) {
@ -49,41 +50,20 @@ define([
'jupyter-notebook:move-cell-down'
],
'move_up_down'],
[ ['jupyter-notebook:run-cell-and-select-next',
[ [new toolbar.Button('jupyter-notebook:run-cell-and-select-next',
{label: i18n.msg._('Run')}),
'jupyter-notebook:interrupt-kernel',
'jupyter-notebook:confirm-restart-kernel'
],
'run_int'],
['<add_celltype_list>'],
[['jupyter-notebook:show-command-palette']],
['<add_celltoolbar_reminder>']
[['jupyter-notebook:show-command-palette']]
];
this.construct(grps);
};
MainToolBar.prototype._pseudo_actions = {};
// reminder of where the celltoolbar new menu is, remove for 5.0
MainToolBar.prototype._pseudo_actions.add_celltoolbar_reminder = function () {
var _b = $('<button/>').attr('title','show new celltoolbar selector location').addClass('btn btn-default').text('CellToolbar')
var btn = $('<div/>').addClass('btn-group').append(_b)
_b.on('click', function(){
setTimeout(function(){$('#view_menu').parent().addClass('pulse')},0)
setTimeout(function(){$('#view_menu').parent().addClass('open')},1000)
setTimeout(function(){$('#menu-cell-toolbar').children('a').addClass('pulse')},2000)
setTimeout(function(){$('#menu-cell-toolbar').children('ul').css('display','block')},3000)
setTimeout(function(){$('#menu-cell-toolbar').children('ul').css('display','')},5400)
setTimeout(function(){$('#menu-cell-toolbar').children('a').removeClass('pulse')},5600)
setTimeout(function(){$('#view_menu').parent().removeClass('open')},5800)
setTimeout(function(){$('#view_menu').parent().removeClass('pulse')},6000)
})
return btn;
};
// add a cell type drop down to the maintoolbar.
// triggered when the pseudo action `<add_celltype_list>` is
// encountered when building a toolbar.
@ -93,10 +73,10 @@ define([
var sel = $('<select/>')
.attr('id','cell_type')
.addClass('form-control select-xs')
.append($('<option/>').attr('value','code').text('Code'))
.append($('<option/>').attr('value','markdown').text('Markdown'))
.append($('<option/>').attr('value','raw').text('Raw NBConvert'))
.append($('<option/>').attr('value','heading').text('Heading'))
.append($('<option/>').attr('value','code').text(i18n.msg._('Code')))
.append($('<option/>').attr('value','markdown').text(i18n.msg._('Markdown')))
.append($('<option/>').attr('value','raw').text(i18n.msg._('Raw NBConvert')))
.append($('<option/>').attr('value','heading').text(i18n.msg._('Heading')))
.append(multiselect);
this.notebook.keyboard_manager.register_events(sel);
this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
@ -132,7 +112,7 @@ define([
case 'multiselect':
break;
default:
console.log("unrecognized cell type:", cell_type);
console.log(i18n.msg._("unrecognized cell type:"), cell_type);
}
that.notebook.focus_cell();
});

@ -4,6 +4,7 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'base/js/dialog',
], function($, utils, dialog) {
"use strict";
@ -35,11 +36,15 @@ define([
});
MathJax.Hub.Configured();
} else if (window.mathjax_url !== "") {
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("OK") ];
// Don't have MathJax, but should. Show dialog.
dialog.modal({
title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
title : i18n.msg.sprintf(i18n.msg._("Failed to retrieve MathJax from '%s'",window.mathjax_url)),
body : $("<p/>").addClass('dialog').text(
"Math/LaTeX rendering will be disabled."
i18n.msg._("Math/LaTeX rendering will be disabled.")
),
buttons : {
OK : {class: "btn-danger"}
@ -55,11 +60,9 @@ define([
// Other minor modifications are also due to StackExchange and are used with
// permission.
var inline = "$"; // the inline math delimiter
// MATHSPLIT contains the pattern for math delimiters and special symbols
// needed for searching for math in the text input.
var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[{}$]|[{}]|(?:\n\s*)+|@@\d+@@|\\\\(?:\(|\)|\[|\]))/i;
// The math is in blocks i through j, so
// collect it into one block and clear the others.
@ -173,11 +176,16 @@ define([
// Look for math start delimiters and when
// found, set up the end delimiter.
//
if (block === inline || block === "$$") {
if (block === "$" || block === "$$") {
start = i;
end = block;
braces = 0;
}
else if (block === "\\\\\(" || block === "\\\\\[") {
start = i;
end = block.slice(-1) === "(" ? "\\\\\)" : "\\\\\]";
braces = 0;
}
else if (block.substr(1, 5) === "begin") {
start = i;
end = "\\end" + block.substr(6);
@ -199,9 +207,27 @@ define([
// and clear the math array (no need to keep it around).
//
var replace_math = function (text, math) {
text = text.replace(/@@(\d+)@@/g, function (match, n) {
return math[n];
});
//
// Replaces a math placeholder with its corresponding group.
// The math delimiters "\\(", "\\[", "\\)" and "\\]" are replaced
// removing one backslash in order to be interpreted correctly by MathJax.
//
var math_group_process = function (match, n) {
var math_group = math[n];
if (math_group.substr(0, 3) === "\\\\\(" && math_group.substr(math_group.length - 3) === "\\\\\)") {
math_group = "\\\(" + math_group.substring(3, math_group.length - 3) + "\\\)";
} else if (math_group.substr(0, 3) === "\\\\\[" && math_group.substr(math_group.length - 3) === "\\\\\]") {
math_group = "\\\[" + math_group.substring(3, math_group.length - 3) + "\\\]";
}
return math_group;
};
// Replace all the math group placeholders in the text
// with the saved strings.
text = text.replace(/@@(\d+)@@/g, math_group_process);
return text;
};

@ -6,12 +6,13 @@ define([
'base/js/namespace',
'base/js/dialog',
'base/js/utils',
'base/js/i18n',
'./celltoolbar',
'./tour',
'moment',
], function($, IPython, dialog, utils, celltoolbar, tour, moment) {
], function($, IPython, dialog, utils, i18n, celltoolbar, tour, moment) {
"use strict";
var MenuBar = function (selector, options) {
/**
* Constructor
@ -172,16 +173,15 @@ define([
this.element.find('#download_ipynb').click(function () {
var base_url = that.notebook.base_url;
var notebook_path = utils.encode_uri_components(that.notebook.notebook_path);
var w = window.open('');
var url = utils.url_path_join(
base_url, 'files', notebook_path
) + '?download=1';
if (that.notebook.dirty && that.notebook.writable) {
that.notebook.save_notebook().then(function() {
w.location = url;
that._new_window(url);
});
} else {
w.location = url;
that._new_window(url);
}
});
@ -204,6 +204,10 @@ define([
this.element.find('#download_pdf').click(function () {
that._nbconvert('pdf', true);
});
this.element.find('#download_latex').click(function () {
that._nbconvert('latex', true);
});
this.element.find('#download_script').click(function () {
that._nbconvert('script', true);
@ -213,13 +217,13 @@ define([
if (trusted) {
that.element.find('#trust_notebook')
.addClass("disabled").off('click')
.find("a").text("Trusted Notebook");
.find("a").text(i18n.msg._("Trusted Notebook"));
} else {
that.element.find('#trust_notebook')
.removeClass("disabled").on('click', function () {
that.notebook.trust_notebook();
})
.find("a").text("Trust Notebook");
.find("a").text(i18n.msg._("Trust Notebook"));
}
});
@ -376,7 +380,7 @@ define([
// Setup the existing presets
var presets = celltoolbar.CellToolbar.list_presets();
preset_added(null, {name: "None"});
preset_added(null, {name: i18n.msg._("None")});
presets.map(function (name) {
preset_added(null, {name: name});
});
@ -399,7 +403,7 @@ define([
.addClass("disabled")
.append(
$("<a/>")
.text("No checkpoints")
.text(i18n.msg._("No checkpoints"))
)
);
return;
@ -458,7 +462,7 @@ define([
cursor.after($("<li>")
.append($("<a>")
.attr('target', '_blank')
.attr('title', 'Opens in a new window')
.attr('title', i18n.msg._('Opens in a new window'))
.attr('href', requirejs.toUrl(link.url))
.append($("<i>")
.addClass("fa fa-external-link menu-icon pull-right")

@ -10,6 +10,7 @@ define([
'base/js/namespace',
'underscore',
'base/js/utils',
'base/js/i18n',
'base/js/dialog',
'./cell',
'./textcell',
@ -37,6 +38,7 @@ define([
IPython,
_,
utils,
i18n,
dialog,
cellmod,
textcell,
@ -62,6 +64,7 @@ define([
) {
var ShortcutEditor = shortcuteditor.ShortcutEditor;
var _SOFT_SELECTION_CLASS = 'jupyter-soft-selected';
function soft_selected(cell){
@ -84,6 +87,7 @@ define([
*/
function Notebook(selector, options) {
this.config = options.config;
this.config.loaded.then(this.validate_config.bind(this))
this.class_config = new configmod.ConfigWithDefaults(this.config,
Notebook.options_default, 'Notebook');
this.base_url = options.base_url;
@ -256,6 +260,15 @@ define([
Toolbar: true
};
Notebook.prototype.validate_config = function() {
var code_cell = this.config.data['CodeCell'] || {};
var cm_keymap = (code_cell['cm_config'] || {})['keyMap'];
if (cm_keymap && CodeMirror.keyMap[cm_keymap] === undefined) {
console.warn('CodeMirror keymap not found, ignoring: ' + cm_keymap);
delete code_cell.cm_config.keyMap;
}
};
/**
* Create an HTML and CSS representation of the notebook.
*/
@ -425,14 +438,14 @@ define([
that.save_notebook();
}
}, 1000);
return "Autosave in progress, latest changes may be lost.";
return i18n.msg._("Autosave in progress, latest changes may be lost.");
} else {
return "Unsaved changes will be lost.";
return i18n.msg._("Unsaved changes will be lost.");
}
}
// if the kernel is busy, prompt the user if hes sure
if (that.kernel_busy) {
return "The Kernel is busy, outputs may be lost.";
return i18n.msg._("The Kernel is busy, outputs may be lost.");
}
// IE treats null as a string. Instead just return which will avoid the dialog.
return;
@ -455,14 +468,29 @@ define([
var v = 'v' + this.nbformat + '.';
var orig_vs = v + this.nbformat_minor;
var this_vs = v + this.current_nbformat_minor;
var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
this_vs + ". You can still work with this notebook, but cell and output types " +
"introduced in later notebook versions will not be available.";
var msg = i18n.msg.sprintf(i18n.msg._("This notebook is version %1$s, but we only fully support up to %2$s."),
orig_vs,this_vs) + " " +
i18n.msg._("You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available.");
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [
i18n.msg._("OK"),
i18n.msg._("Restart and Run All Cells"),
i18n.msg._("Restart and Clear All Outputs"),
i18n.msg._("Restart"),
i18n.msg._("Continue Running"),
i18n.msg._("Reload"),
i18n.msg._("Cancel"),
i18n.msg._("Overwrite"),
i18n.msg._("Trust"),
i18n.msg._("Revert")];
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title : "Newer Notebook",
title : i18n.msg._("Newer Notebook"),
body : msg,
buttons : {
OK : {
@ -1527,12 +1555,12 @@ define([
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title : "Use markdown headings",
title : i18n.msg._("Use markdown headings"),
body : $("<p/>").text(
'Jupyter no longer uses special heading cells. ' +
'Instead, write your headings in Markdown cells using # characters:'
i18n.msg._('Jupyter no longer uses special heading cells. ' +
'Instead, write your headings in Markdown cells using # characters:')
).append($('<pre/>').text(
'## This is a level 2 heading'
i18n.msg._('## This is a level 2 heading')
)),
buttons : {
"OK" : {}
@ -1567,11 +1595,14 @@ define([
var that = this;
if (!this.paste_enabled) {
$('#paste_cell_replace').removeClass('disabled')
.on('click', function () {that.paste_cell_replace();});
.on('click', function () {that.keyboard_manager.actions.call(
'jupyter-notebook:paste-cell-replace');});
$('#paste_cell_above').removeClass('disabled')
.on('click', function () {that.paste_cell_above();});
.on('click', function () {that.keyboard_manager.actions.call(
'jupyter-notebook:paste-cell-above');});
$('#paste_cell_below').removeClass('disabled')
.on('click', function () {that.paste_cell_below();});
.on('click', function () {that.keyboard_manager.actions.call(
'jupyter-notebook:paste-cell-below');});
this.paste_enabled = true;
}
};
@ -1697,11 +1728,16 @@ define([
if (cell.is_splittable()) {
var texta = cell.get_pre_cursor();
var textb = cell.get_post_cursor();
// current cell becomes the second one
// so we don't need to worry about selection
cell.set_text(textb);
// create new cell with same type
var new_cell = this.insert_cell_above(cell.cell_type);
// Unrender the new cell so we can call set_text.
new_cell.unrender();
new_cell.set_text(texta);
// duplicate metadata
new_cell.metadata = JSON.parse(JSON.stringify(cell.metadata));
}
};
@ -1756,6 +1792,9 @@ define([
// Delete the other cells
this.delete_cells(indices);
// Reset the target cell's undo history
target.code_mirror.clearHistory();
this.select(this.find_cell_index(target));
};
@ -2219,9 +2258,9 @@ define([
restart_options.dialog = {
notebook: that,
keyboard_manager: that.keyboard_manager,
title : "Restart kernel and re-run the whole notebook?",
title : i18n.msg._("Restart kernel and re-run the whole notebook?"),
body : $("<p/>").text(
'Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.'
i18n.msg._('Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.')
),
buttons : {
"Restart and Run All Cells" : {
@ -2246,9 +2285,9 @@ define([
restart_options.dialog = {
notebook: that,
keyboard_manager: that.keyboard_manager,
title : "Restart kernel and clear all output?",
title : i18n.msg._("Restart kernel and clear all output?"),
body : $("<p/>").text(
'Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.'
i18n.msg._('Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.')
),
buttons : {
"Restart and Clear All Outputs" : {
@ -2293,9 +2332,9 @@ define([
var restart_options = {};
restart_options.confirm = (options || {}).confirm;
restart_options.dialog = {
title : "Restart kernel?",
title : i18n.msg._("Restart kernel?"),
body : $("<p/>").text(
'Do you want to restart the current kernel? All variables will be lost.'
i18n.msg._('Do you want to restart the current kernel? All variables will be lost.')
),
buttons : {
"Restart" : {
@ -2692,7 +2731,10 @@ define([
return this.contents.get(this.notebook_path, {content: false}).then(
function (data) {
var last_modified = new Date(data.last_modified);
if (last_modified > that.last_modified) {
// We want to check last_modified (disk) > that.last_modified (our last save)
// In some cases the filesystem reports an inconsistent time,
// so we allow 0.5 seconds difference before complaining.
if ((last_modified.getTime() - that.last_modified.getTime()) > 500) { // 500 ms
console.warn("Last saving was done on `"+that.last_modified+"`("+that._last_modified+"), "+
"while the current file seem to have been saved on `"+data.last_modified+"`");
if (that._changed_on_disk_dialog !== null) {
@ -2705,10 +2747,10 @@ define([
that._changed_on_disk_dialog = dialog.modal({
notebook: that,
keyboard_manager: that.keyboard_manager,
title: "Notebook changed",
body: "The notebook file has changed on disk since the last time we opened or saved it. "+
"Do you want to overwrite the file on disk with the version open here, or load "+
"the version on disk (reload the page) ?",
title: i18n.msg._("Notebook changed"),
body: i18n.msg._("The notebook file has changed on disk since the last time we opened or saved it. "
+ "Do you want to overwrite the file on disk with the version open here, or load "
+ "the version on disk (reload the page)?"),
buttons: {
Reload: {
class: 'btn-warning',
@ -2753,12 +2795,12 @@ define([
if (data.message) {
// save succeeded, but validation failed.
var body = $("<div>");
var title = "Notebook validation failed";
var title = i18n.msg._("Notebook validation failed");
body.append($("<p>").text(
"The save operation succeeded," +
i18n.msg._("The save operation succeeded," +
" but the notebook does not appear to be valid." +
" The validation error was:"
" The validation error was:")
)).append($("<div>").addClass("validation-error").append(
$("<pre>").text(data.message)
));
@ -2807,25 +2849,19 @@ define([
*/
Notebook.prototype.trust_notebook = function () {
var body = $("<div>").append($("<p>")
.text("A trusted Jupyter notebook may execute hidden malicious code ")
.append($("<strong>")
.append(
$("<em>").text("when you open it")
)
).append(".").append(
" Selecting trust will immediately reload this notebook in a trusted state."
).append(
" For more information, see the "
).append($("<a>").attr("href", "https://jupyter-notebook.readthedocs.io/en/latest/security.html")
.text("Jupyter security documentation")
).append(".")
.text(i18n.msg._("A trusted Jupyter notebook may execute hidden malicious code when you open it. " +
"Selecting trust will immediately reload this notebook in a trusted state. " +
"For more information, see the Jupyter security documentation: "))
.append($("<a>").attr("href", "https://jupyter-notebook.readthedocs.io/en/latest/security.html")
.text(i18n.msg._("here"))
)
);
var nb = this;
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title: "Trust this notebook?",
title: i18n.msg._("Trust this notebook?"),
body: body,
buttons: {
@ -2958,24 +2994,24 @@ define([
var body = $("<div>");
var title;
if (failed) {
title = "Notebook failed to load";
title = i18n.msg._("Notebook failed to load");
body.append($("<p>").text(
"The error was: "
i18n.msg._("The error was: ")
)).append($("<div>").addClass("js-error").text(
failed.toString()
)).append($("<p>").text(
"See the error console for details."
i18n.msg._("See the error console for details.")
));
} else {
title = "Notebook validation failed";
title = i18n.msg._("Notebook validation failed");
}
if (data.message) {
if (failed) {
msg = "The notebook also failed validation:";
msg = i18n.msg._("The notebook also failed validation:");
} else {
msg = "An invalid notebook may not function properly." +
" The validation error was:";
msg = i18n.msg._("An invalid notebook may not function properly." +
" The validation error was:");
}
body.append($("<p>").text(
msg
@ -3013,29 +3049,32 @@ define([
var orig_nbformat = nbmodel.metadata.orig_nbformat;
var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
var src;
var oldmsg = i18n.msg._("This notebook has been converted from an older notebook format" +
" to the current notebook format v(%s).");
var newmsg = i18n.msg._("This notebook has been converted from a newer notebook format" +
" to the current notebook format v(%s).");
if (nbmodel.nbformat > orig_nbformat) {
src = " an older notebook format ";
msg = i18n.msg.sprintf(oldmsg,nbmodel.nbformat);
} else {
src = " a newer notebook format ";
msg = i18n.msg.sprintf(newmsg,nbmodel.nbformat);
}
msg += " ";
msg += i18n.msg._("The next time you save this notebook, the " +
"current notebook format will be used.");
msg = "This notebook has been converted from" + src +
"(v"+orig_nbformat+") to the current notebook " +
"format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
"current notebook format will be used.";
msg += " ";
if (nbmodel.nbformat > orig_nbformat) {
msg += " Older versions of Jupyter may not be able to read the new format.";
msg += i18n.msg._("Older versions of Jupyter may not be able to read the new format.");
} else {
msg += " Some features of the original notebook may not be available.";
msg += i18n.msg._("Some features of the original notebook may not be available.");
}
msg += " To preserve the original version, close the " +
"notebook without saving it.";
msg += " ";
msg += i18n.msg._("To preserve the original version, close the " +
"notebook without saving it.");
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title : "Notebook converted",
title : i18n.msg._("Notebook converted"),
body : msg,
buttons : {
OK : {
@ -3057,7 +3096,7 @@ define([
// compat with IJulia, IHaskell, and other early kernels
// adopters that where setting a language metadata.
this.kernel_selector.set_kernel({
name: "(No name)",
name: i18n.msg._("(No name)"),
language: this.metadata.language
});
// this should be stored in kspec now, delete it.
@ -3105,9 +3144,9 @@ define([
var msg;
if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
msg = "An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " +
"v" + this.nbformat + " or earlier. See the server log for details.";
msg = i18n.msg.sprintf(i18n.msg._("An unknown error occurred while loading this notebook. " +
"This version can load notebook formats %s or earlier. See the server log for details.",
"v" + this.nbformat));
} else {
msg = error.message;
console.warn('Error stack trace while loading notebook was:');
@ -3116,7 +3155,7 @@ define([
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title: "Error loading notebook",
title: i18n.msg._("Error loading notebook"),
body : msg,
buttons : {
"OK": {}
@ -3217,15 +3256,13 @@ define([
}
var body = $('<div/>').append(
$('<p/>').addClass("p-space").text(
"Are you sure you want to revert the notebook to " +
"the latest checkpoint?"
i18n.msg._("Are you sure you want to revert the notebook to " +
"the latest checkpoint?")
).append(
$("<strong/>").text(
" This cannot be undone."
)
$("<strong/>").text(" "+i18n.msg._("This cannot be undone."))
)
).append(
$('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
$('<p/>').addClass("p-space").text(i18n.msg._("The checkpoint was last updated at:"))
).append(
$('<p/>').addClass("p-space").text(
moment(checkpoint.last_modified).format('LLLL') +
@ -3236,7 +3273,7 @@ define([
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
title : "Revert notebook to checkpoint",
title : i18n.msg._("Revert notebook to checkpoint"),
body : body,
default_button: "Cancel",
buttons : {

@ -1,11 +1,13 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'base/js/dialog',
'base/js/notificationarea',
'moment'
], function($, utils, dialog, notificationarea, moment) {
], function($, utils, i18n, dialog, notificationarea, moment) {
"use strict";
var NotificationArea = notificationarea.NotificationArea;
var NotebookNotificationArea = function(selector, options) {
@ -40,6 +42,22 @@ define([
var $modal_ind_icon = $("#modal_indicator");
var $readonly_ind_icon = $('#readonly-indicator');
var $body = $('body');
var busy_favicon_timer = -1;
var set_busy_favicon = function(on) {
if (on) {
// Only show the busy icon if execution lasts > 1s
// This is to avoid rapidly switching icons and making lots of
// HTTP requests.
clearTimeout(busy_favicon_timer);
busy_favicon_timer = setTimeout(function() {
utils.change_favicon('/static/base/images/favicon-busy-1.ico');
}, 1000);
} else {
clearTimeout(busy_favicon_timer);
utils.change_favicon('/static/base/images/favicon-notebook.ico');
}
};
// Listen for the notebook loaded event. Set readonly indicator.
this.events.on('notebook_loaded.Notebook', function() {
@ -55,36 +73,36 @@ define([
that.save_widget.update_document_title();
$body.addClass('edit_mode');
$body.removeClass('command_mode');
$modal_ind_icon.attr('title','Edit Mode');
$modal_ind_icon.attr('title',i18n.msg._('Edit Mode'));
});
this.events.on('command_mode.Notebook', function () {
that.save_widget.update_document_title();
$body.removeClass('edit_mode');
$body.addClass('command_mode');
$modal_ind_icon.attr('title','Command Mode');
$modal_ind_icon.attr('title',i18n.msg._('Command Mode'));
});
// Implicitly start off in Command mode, switching to Edit mode will trigger event
$modal_ind_icon.addClass('modal_indicator').attr('title','Command Mode');
$modal_ind_icon.addClass('modal_indicator').attr('title',i18n.msg._('Command Mode'));
$body.addClass('command_mode');
// Kernel events
// this can be either kernel_created.Kernel or kernel_created.Session
this.events.on('kernel_created.Kernel kernel_created.Session', function () {
knw.info("Kernel Created", 500);
knw.info(i18n.msg._("Kernel Created"), 500);
});
this.events.on('kernel_reconnecting.Kernel', function () {
knw.warning("Connecting to kernel");
knw.warning(i18n.msg._("Connecting to kernel"));
});
this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
knw.danger("Not Connected", undefined, function () {
knw.danger(i18n.msg._("Not Connected"), undefined, function () {
// schedule reconnect a short time in the future, don't reconnect immediately
setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
}, {title: 'click to reconnect'});
}, {title: i18n.msg._('click to reconnect')});
});
this.events.on('kernel_connected.Kernel', function () {
@ -93,7 +111,7 @@ define([
this.events.on('kernel_restarting.Kernel', function () {
that.save_widget.update_document_title();
knw.set_message("Restarting kernel", 2000);
knw.set_message(i18n.msg._("Restarting kernel"), 2000);
});
this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
@ -107,8 +125,8 @@ define([
dialog.kernel_modal({
notebook: that.notebook,
keyboard_manager: that.keyboard_manager,
title: "Kernel Restarting",
body: "The kernel appears to have died. It will restart automatically.",
title: i18n.msg._("Kernel Restarting"),
body: i18n.msg._("The kernel appears to have died. It will restart automatically."),
buttons: {
OK : {
class : "btn-primary"
@ -118,18 +136,18 @@ define([
}
that.save_widget.update_document_title();
knw.danger("Dead kernel");
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
knw.danger(i18n.msg._("Dead kernel"));
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title',i18n.msg._('Kernel Dead'));
});
this.events.on('kernel_interrupting.Kernel', function () {
knw.set_message("Interrupting kernel", 2000);
knw.set_message(i18n.msg._("Interrupting kernel"), 2000);
});
this.events.on('kernel_disconnected.Kernel', function () {
$kernel_ind_icon
.attr('class', 'kernel_disconnected_icon')
.attr('title', 'No Connection to Kernel');
.attr('title', i18n.msg._('No Connection to Kernel'));
});
this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
@ -139,12 +157,12 @@ define([
// with messages
if (info.attempt === 1) {
var msg = "A connection to the notebook server could not be established." +
var msg = i18n.msg._("A connection to the notebook server could not be established." +
" The notebook will continue trying to reconnect. Check your" +
" network connection or notebook server configuration.";
" network connection or notebook server configuration.");
dialog.kernel_modal({
title: "Connection failed",
title: i18n.msg._("Connection failed"),
body: msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
@ -157,22 +175,26 @@ define([
this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
that.save_widget.update_document_title();
knw.warning("No kernel");
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel is not running');
knw.warning(i18n.msg._("No kernel"));
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title',i18n.msg._('Kernel is not running'));
});
this.events.on('kernel_dead.Kernel', function () {
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("Don't Restart"), i18n.msg._("Try Restarting Now"), i18n.msg._("OK")];
var showMsg = 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' +
var msg = i18n.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.';
' is reopened.');
dialog.kernel_modal({
title: "Dead kernel",
title: i18n.msg._("Dead kernel"),
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
@ -192,14 +214,14 @@ define([
};
that.save_widget.update_document_title();
knw.danger("Dead kernel", undefined, showMsg);
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
knw.danger(i18n.msg._("Dead kernel"), undefined, showMsg);
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title',i18n.msg._('Kernel Dead'));
showMsg();
});
this.events.on("no_kernel.Kernel", function (evt, data) {
$("#kernel_indicator").find('.kernel_indicator_name').text("No Kernel");
$("#kernel_indicator").find('.kernel_indicator_name').text(i18n.msg._("No Kernel"));
});
this.events.on('kernel_dead.Session', function (evt, info) {
@ -226,13 +248,13 @@ define([
}
dialog.kernel_modal({
title: "Failed to start the kernel",
title: i18n.msg._("Failed to start the kernel"),
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
open: cm_open,
buttons : {
"Ok": { class: 'btn-primary' }
"OK": { class: 'btn-primary' }
}
});
@ -240,57 +262,47 @@ define([
};
that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title',i18n.msg._('Kernel Dead'));
knw.danger(short, undefined, showMsg);
});
var change_favicon = function (src) {
var link = document.createElement('link'),
oldLink = document.getElementById('favicon');
link.id = 'favicon';
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = src;
if (oldLink) document.head.removeChild(oldLink);
document.head.appendChild(link);
};
this.events.on('kernel_starting.Kernel kernel_created.Session', function () {
// window.document.title='(Starting) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
knw.set_message("Kernel starting, please wait...");
change_favicon('/static/base/images/favicon-busy.ico');
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title',i18n.msg._('Kernel Busy'));
knw.set_message(i18n.msg._("Kernel starting, please wait..."));
set_busy_favicon(true);
});
this.events.on('kernel_ready.Kernel', function () {
// that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
knw.info("Kernel ready", 500);
change_favicon('/static/base/images/favicon.ico');
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title',i18n.msg._('Kernel Idle'));
knw.info(i18n.msg._("Kernel ready"), 500);
set_busy_favicon(false);
});
this.events.on('kernel_idle.Kernel', function () {
// that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
change_favicon('/static/base/images/favicon.ico');
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title',i18n.msg._('Kernel Idle'));
set_busy_favicon(false);
});
this.events.on('kernel_busy.Kernel', function () {
// window.document.title='(Busy) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
change_favicon('/static/base/images/favicon-busy.ico');
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title',i18n.msg._('Kernel Busy'));
set_busy_favicon(true);
});
this.events.on('spec_match_found.Kernel', function (evt, data) {
that.widget('kernelspec').info("Using kernel: " + data.found.spec.display_name, 3000, undefined, {
title: "Only candidate for language: " + data.selected.language + " was " + data.found.spec.display_name
that.widget('kernelspec').info(i18n.msg._("Using kernel: ") + data.found.spec.display_name, 3000, undefined, {
title: i18n.msg.sprintf(i18n.msg._("Only candidate for language: %1$s was %2$s."),
data.selected.language, data.found.spec.display_name)
});
});
// Start the kernel indicator in the busy state, and send a kernel_info request.
// When the kernel_info reply arrives, the kernel is idle.
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title',i18n.msg._('Kernel Busy'));
};
/**
@ -303,27 +315,27 @@ define([
// Notebook events
this.events.on('notebook_loading.Notebook', function () {
nnw.set_message("Loading notebook",500);
nnw.set_message(i18n.msg._("Loading notebook"),500);
});
this.events.on('notebook_loaded.Notebook', function () {
nnw.set_message("Notebook loaded",500);
nnw.set_message(i18n.msg._("Notebook loaded"),500);
});
this.events.on('notebook_saving.Notebook', function () {
nnw.set_message("Saving notebook",500);
nnw.set_message(i18n.msg._("Saving notebook"),500);
});
this.events.on('notebook_saved.Notebook', function () {
nnw.set_message("Notebook saved",2000);
nnw.set_message(i18n.msg._("Notebook saved"),2000);
});
this.events.on('notebook_save_failed.Notebook', function (evt, error) {
nnw.warning(error.message || "Notebook save failed");
nnw.warning(error.message || i18n.msg._("Notebook save failed"));
});
this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
nnw.warning(error.message || "Notebook copy failed");
nnw.warning(error.message || i18n.msg._("Notebook copy failed"));
});
// Checkpoint events
this.events.on('checkpoint_created.Notebook', function (evt, data) {
var msg = "Checkpoint created";
var msg = i18n.msg._("Checkpoint created");
if (data.last_modified) {
var d = new Date(data.last_modified);
msg = msg + ": " + moment(d).format("HH:mm:ss");
@ -331,27 +343,27 @@ define([
nnw.set_message(msg, 2000);
});
this.events.on('checkpoint_failed.Notebook', function () {
nnw.warning("Checkpoint failed");
nnw.warning(i18n.msg._("Checkpoint failed"));
});
this.events.on('checkpoint_deleted.Notebook', function () {
nnw.set_message("Checkpoint deleted", 500);
nnw.set_message(i18n.msg._("Checkpoint deleted"), 500);
});
this.events.on('checkpoint_delete_failed.Notebook', function () {
nnw.warning("Checkpoint delete failed");
nnw.warning(i18n.msg._("Checkpoint delete failed"));
});
this.events.on('checkpoint_restoring.Notebook', function () {
nnw.set_message("Restoring to checkpoint...", 500);
nnw.set_message(i18n.msg._("Restoring to checkpoint..."), 500);
});
this.events.on('checkpoint_restore_failed.Notebook', function () {
nnw.warning("Checkpoint restore failed");
nnw.warning(i18n.msg._("Checkpoint restore failed"));
});
// Autosave events
this.events.on('autosave_disabled.Notebook', function () {
nnw.set_message("Autosave disabled", 2000);
nnw.set_message(i18n.msg._("Autosave disabled"), 2000);
});
this.events.on('autosave_enabled.Notebook', function (evt, interval) {
nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
nnw.set_message(i18n.msg.sprintf(i18n.msg._("Saving every %d sec."), interval / 1000) , 1000);
});
};
@ -367,9 +379,9 @@ define([
// Notebook trust events
this.events.on('trust_changed.Notebook', function (event, trusted) {
if (trusted) {
tnw.set_message("Trusted");
tnw.set_message(i18n.msg._("Trusted"));
} else {
tnw.set_message("Not Trusted", undefined, function() {
tnw.set_message(i18n.msg._("Not Trusted"), undefined, function() {
that.notebook.trust_notebook();
return false;
});

@ -4,12 +4,13 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'base/js/security',
'base/js/keyboard',
'services/config',
'notebook/js/mathjaxutils',
'components/marked/lib/marked',
], function($, utils, security, keyboard, configmod, mathjaxutils, marked) {
], function($, utils, i18n, security, keyboard, configmod, mathjaxutils, marked) {
"use strict";
/**
@ -54,7 +55,18 @@ define([
**/
OutputArea.prototype.create_elements = function () {
this.element = $("<div/>");
var element = this.element = $("<div/>");
// wrap element in safe trigger,
// so that errors (e.g. in widget extensions) are logged instead of
// breaking everything.
this.element._original_trigger = this.element.trigger;
this.element.trigger = function (name, data) {
try {
this._original_trigger.apply(this, arguments);
} catch (e) {
console.error("Exception in event handler for " + name, e, arguments);
}
}
this.collapse_button = $("<div/>");
this.prompt_overlay = $("<div/>");
this.wrapper.append(this.prompt_overlay);
@ -71,13 +83,13 @@ define([
this.element.addClass('output');
this.collapse_button.addClass("btn btn-default output_collapsed");
this.collapse_button.attr('title', 'click to expand output');
this.collapse_button.attr('title', i18n.msg._('click to expand output'));
this.collapse_button.text('. . .');
this.prompt_overlay.addClass('out_prompt_overlay prompt');
this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
this.prompt_overlay.attr('title', i18n.msg._('click to expand output; double click to hide output'));
this.collapse();
this.expand();
};
/**
@ -163,14 +175,14 @@ define([
OutputArea.prototype.scroll_area = function () {
this.element.addClass('output_scroll');
this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
this.prompt_overlay.attr('title', i18n.msg._('click to unscroll output; double click to hide'));
this.scrolled = true;
};
OutputArea.prototype.unscroll_area = function () {
this.element.removeClass('output_scroll');
this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
this.prompt_overlay.attr('title', i18n.msg._('click to scroll output; double click to hide'));
this.scrolled = false;
};
@ -331,10 +343,15 @@ define([
} else {
this.handle_appended();
}
if (record_output) {
this.outputs.push(json);
}
this.events.trigger('output_added.OutputArea', {
output: json,
output_area: this,
});
};
OutputArea.prototype.handle_appended = function () {
@ -343,7 +360,7 @@ define([
this._needs_height_reset = false;
}
this.element.trigger('resizeOutput');
this.element.trigger('resizeOutput', {output_area: this});
};
OutputArea.prototype.create_output_area = function () {
@ -413,12 +430,12 @@ define([
/**
* display a message when a javascript error occurs in display output
*/
var msg = "Javascript error adding output!";
var msg = i18n.msg._("Javascript error adding output!");
if ( element === undefined ) return;
element
.append($('<div/>').text(msg).addClass('js-error'))
.append($('<div/>').text(err.toString()).addClass('js-error'))
.append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
.append($('<div/>').text(i18n.msg._('See your browser Javascript console for more details.')).addClass('js-error'));
};
OutputArea.prototype._safe_append = function (toinsert, toreplace) {
@ -446,7 +463,7 @@ define([
}
// Notify others of changes.
this.element.trigger('changed');
this.element.trigger('changed', {output_area: this});
};
@ -455,7 +472,12 @@ define([
var toinsert = this.create_output_area();
this._record_display_id(json, toinsert);
if (this.prompt_area) {
toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
toinsert.find('div.prompt')
.addClass('output_prompt')
.empty()
.append(
$('<bdi>').text(i18n.msg.sprintf(i18n.msg._('Out[%d]:'),n))
);
}
var inserted = this.append_mime_type(json, toinsert);
if (inserted) {
@ -563,7 +585,7 @@ define([
subarea.append(
$("<a>")
.attr("href", "#")
.text("Unrecognized output: " + json.output_type)
.text(i18n.msg.sprintf(i18n.msg._("Unrecognized output: %s"),json.output_type))
.click(function () {
that.events.trigger('unrecognized_output.OutputArea', {output: json});
})
@ -930,7 +952,7 @@ define([
};
OutputArea.prototype.clear_output = function(wait, ignore_que) {
OutputArea.prototype.clear_output = function(wait, ignore_clear_queue) {
if (wait) {
// If a clear is queued, clear before adding another to the queue.
@ -943,8 +965,9 @@ define([
// Fix the output div's height if the clear_output is waiting for
// new output (it is being used in an animation).
if (!ignore_que && this.clear_queued) {
var height = this.element.height();
if (!ignore_clear_queue && this.clear_queued) {
// this.element.height() rounds the height, so we get the exact value
var height = this.element[0].getBoundingClientRect().height;
this.element.height(height);
this.clear_queued = false;
}
@ -953,15 +976,18 @@ define([
// Remove load event handlers from img tags because we don't want
// them to fire if the image is never added to the page.
this.element.find('img').off('load');
this.element.trigger('clearing', {output_area: this});
this.element.html("");
// Notify others of changes.
this.element.trigger('changed');
this.element.trigger('changed', {output_area: this});
this.element.trigger('cleared', {output_area: this});
this.outputs = [];
this._display_id_targets = {};
this.trusted = true;
this.unscroll_area();
this.expand();
return;
}
};

@ -4,8 +4,9 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'jquery-ui'
], function($, utils) {
], function($, utils, i18n) {
"use strict";
var Pager = function (pager_selector, options) {
@ -32,7 +33,7 @@ define([
var that = this;
this.pager_button_area.append(
$('<a>').attr('role', "button")
.attr('title',"Open the pager in an external window")
.attr('title',i18n.msg._("Open the pager in an external window"))
.addClass('ui-button')
.click(function(){that.detach();})
.append(
@ -41,7 +42,7 @@ define([
);
this.pager_button_area.append(
$('<a>').attr('role', "button")
.attr('title',"Close the pager")
.attr('title',i18n.msg._("Close the pager"))
.addClass('ui-button')
.click(function(){that.collapse();})
.append(
@ -140,11 +141,11 @@ define([
.append(
$('<link>')
.attr('rel',"stylesheet")
.attr('href',"/static/css/notebook.css")
.attr('href', utils.url_path_join(utils.get_body_data('baseUrl'), "static/style/style.min.css"))
.attr('type',"text/css")
)
.append(
$('<title>').text("Jupyter Pager")
$('<title>').text(i18n.msg._("Jupyter Pager"))
);
var pager_body = $(w.document.body);
pager_body.css('overflow','scroll');

@ -4,10 +4,12 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'base/js/dialog',
'underscore'
], function($, utils, dialog, _) {
], function($, utils, i18n, dialog, _) {
"use strict";
var platform = utils.platform;
var QuickHelp = function (options) {
@ -34,36 +36,45 @@ define([
// Mac OS X specific
cmd_ctrl = 'Cmd-';
platform_specific = [
{ shortcut: "Cmd-Up", help:"go to cell start" },
{ shortcut: "Cmd-Down", help:"go to cell end" },
{ shortcut: "Alt-Left", help:"go one word left" },
{ shortcut: "Alt-Right", help:"go one word right" },
{ shortcut: "Alt-Backspace", help:"delete word before" },
{ shortcut: "Alt-Delete", help:"delete word after" },
{ shortcut: "Cmd-Up", help:i18n.msg._("go to cell start") },
{ shortcut: "Cmd-Down", help:i18n.msg._("go to cell end") },
{ shortcut: "Alt-Left", help:i18n.msg._("go one word left") },
{ shortcut: "Alt-Right", help:i18n.msg._("go one word right") },
{ shortcut: "Alt-Backspace", help:i18n.msg._("delete word before") },
{ shortcut: "Alt-Delete", help:i18n.msg._("delete word after") },
{ shortcut: "Cmd-Shift-z", help:i18n.msg._("redo") },
{ shortcut: "Cmd-Shift-u", help:i18n.msg._("redo selection") },
{ shortcut: "Ctrl-k", help:i18n.msg._("emacs-style line kill") },
{ shortcut: "Cmd-Backspace", help:i18n.msg._("delete line left of cursor") },
{ shortcut: "Cmd-Delete", help:i18n.msg._("delete line right of cursor") }
];
} else {
// PC specific
platform_specific = [
{ shortcut: "Ctrl-Home", help:"go to cell start" },
{ shortcut: "Ctrl-Up", help:"go to cell start" },
{ shortcut: "Ctrl-End", help:"go to cell end" },
{ shortcut: "Ctrl-Down", help:"go to cell end" },
{ shortcut: "Ctrl-Left", help:"go one word left" },
{ shortcut: "Ctrl-Right", help:"go one word right" },
{ shortcut: "Ctrl-Backspace", help:"delete word before" },
{ shortcut: "Ctrl-Delete", help:"delete word after" },
{ shortcut: "Ctrl-Home", help:i18n.msg._("go to cell start") },
{ shortcut: "Ctrl-Up", help:i18n.msg._("go to cell start") },
{ shortcut: "Ctrl-End", help:i18n.msg._("go to cell end") },
{ shortcut: "Ctrl-Down", help:i18n.msg._("go to cell end") },
{ shortcut: "Ctrl-Left", help:i18n.msg._("go one word left") },
{ shortcut: "Ctrl-Right", help:i18n.msg._("go one word right") },
{ shortcut: "Ctrl-Backspace", help:i18n.msg._("delete word before")},
{ shortcut: "Ctrl-Delete", help:i18n.msg._("delete word after")},
{ shortcut: "Ctrl-y", help:i18n.msg._("redo")},
{ shortcut: "Alt-u", help:i18n.msg._("redo selection") }
];
}
var cm_shortcuts = [
{ shortcut:"Tab", help:"code completion or indent" },
{ shortcut:"Shift-Tab", help:"tooltip" },
{ shortcut: cmd_ctrl + "]", help:"indent" },
{ shortcut: cmd_ctrl + "[", help:"dedent" },
{ shortcut: cmd_ctrl + "a", help:"select all" },
{ shortcut: cmd_ctrl + "z", help:"undo" },
{ shortcut: cmd_ctrl + "Shift-z", help:"redo" },
{ shortcut: cmd_ctrl + "y", help:"redo" },
{ shortcut:"Tab", help:i18n.msg._("code completion or indent") },
{ shortcut:"Shift-Tab", help:i18n.msg._("tooltip") },
{ shortcut: cmd_ctrl + "]", help:i18n.msg._("indent") },
{ shortcut: cmd_ctrl + "[", help:i18n.msg._("dedent") },
{ shortcut: cmd_ctrl + "a", help:i18n.msg._("select all") },
{ shortcut: cmd_ctrl + "z", help:i18n.msg._("undo") },
{ shortcut: cmd_ctrl + "/", help:i18n.msg._("comment") },
{ shortcut: cmd_ctrl + "d", help:i18n.msg._("delete whole line") },
{ shortcut: cmd_ctrl + "u", help:i18n.msg._("undo selection") },
{ shortcut: "Insert", help:i18n.msg._("toggle overwrite flag") }
].concat( platform_specific );
var mac_humanize_map = {
@ -97,24 +108,24 @@ define([
};
var default_humanize_map = {
'shift':'Shift',
'alt':'Alt',
'up':'Up',
'down':'Down',
'left':'Left',
'right':'Right',
'tab':'Tab',
'capslock':'Caps Lock',
'esc':'Esc',
'ctrl':'Ctrl',
'enter':'Enter',
'pageup':'Page Up',
'pagedown':'Page Down',
'home':'Home',
'end':'End',
'space':'Space',
'backspace':'Backspace',
'-':'Minus'
'shift':i18n.msg._('Shift'),
'alt':i18n.msg._('Alt'),
'up':i18n.msg._('Up'),
'down':i18n.msg._('Down'),
'left':i18n.msg._('Left'),
'right':i18n.msg._('Right'),
'tab':i18n.msg._('Tab'),
'capslock':i18n.msg._('Caps Lock'),
'esc':i18n.msg._('Esc'),
'ctrl':i18n.msg._('Ctrl'),
'enter':i18n.msg._('Enter'),
'pageup':i18n.msg._('Page Up'),
'pagedown':i18n.msg._('Page Down'),
'home':i18n.msg._('Home'),
'end':i18n.msg._('End'),
'space':i18n.msg._('Space'),
'backspace':i18n.msg._('Backspace'),
'-':i18n.msg._('Minus')
};
var humanize_map;
@ -125,7 +136,7 @@ define([
humanize_map = default_humanize_map;
}
var special_case = { pageup: "PageUp", pagedown: "Page Down" };
var special_case = { pageup: i18n.msg._("PageUp"), pagedown: i18n.msg._("Page Down") };
function humanize_key(key){
if (key.length === 1){
@ -192,11 +203,11 @@ define([
// The documentation
var doc = $('<div/>').addClass('alert alert-info');
doc.append(
'The Jupyter Notebook has two different keyboard input modes. <b>Edit mode</b> '+
'allows you to type code/text into a cell and is indicated by a green cell '+
'border. <b>Command mode</b> binds the keyboard to notebook level commands '+
'and is indicated by a grey cell border with a blue left margin.'
doc.append(i18n.msg._('The Jupyter Notebook has two different keyboard input modes.'))
.append(' ')
.append(i18n.msg._('<b>Edit mode</b> allows you to type code or text into a cell and is indicated by a green cell border.'))
.append(' ')
.append(i18n.msg._('<b>Command mode</b> binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin.')
);
element.append(doc);
if (platform === 'MacOS') {
@ -214,8 +225,13 @@ define([
var edit_div = this.build_edit_help(cm_shortcuts);
element.append(edit_div);
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("Close") ];
this.shortcut_dialog = dialog.modal({
title : "Keyboard shortcuts",
title : i18n.msg._("Keyboard shortcuts"),
body : element,
destroy : false,
buttons : {
@ -230,13 +246,13 @@ define([
};
QuickHelp.prototype.build_key_names = function () {
var key_names_mac = [{ shortcut:"⌘", help:"Command" },
{ shortcut:"⌃", help:"Control" },
{ shortcut:"⌥", help:"Option" },
{ shortcut:"⇧", help:"Shift" },
{ shortcut:"↩", help:"Return" },
{ shortcut:"␣", help:"Space" },
{ shortcut:"⇥", help:"Tab" }];
var key_names_mac = [{ shortcut:"⌘", help:i18n.msg._("Command") },
{ shortcut:"⌃", help:i18n.msg._("Control") },
{ shortcut:"⌥", help:i18n.msg._("Option") },
{ shortcut:"⇧", help:i18n.msg._("Shift") },
{ shortcut:"↩", help:i18n.msg._("Return") },
{ shortcut:"␣", help:i18n.msg._("Space") },
{ shortcut:"⇥", help:i18n.msg._("Tab") }];
var i, half, n;
var div = $('<div/>').append('Mac OS X modifier keys:');
var sub_div = $('<div/>').addClass('container-fluid');
@ -259,12 +275,13 @@ define([
QuickHelp.prototype.build_command_help = function () {
var that = this;
var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
var div = build_div('<h4>Command Mode (press <kbd>Esc</kbd> to enable)</h4>', command_shortcuts);
var cmdkey = '<kbd>'+i18n.msg._('Esc')+'</kbd>';
var div = build_div('<h4>'+i18n.msg.sprintf(i18n.msg._('Command Mode (press %s to enable)'),cmdkey)+'</h4>', command_shortcuts);
var edit_button = $('<button/>')
.text("Edit Shortcuts")
.text(i18n.msg._("Edit Shortcuts"))
.addClass('btn btn-xs btn-default pull-right')
.attr('href', '#')
.attr('title', 'edit command-mode keyboard shortcuts')
.attr('title', i18n.msg._('edit command-mode keyboard shortcuts'))
.click(function () {
// close this dialog
$(that.shortcut_dialog).modal("toggle");
@ -279,8 +296,9 @@ define([
QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
var enterkey = '<kbd>'+i18n.msg._('Enter')+'</kbd>';
edit_shortcuts = $.merge($.merge([], cm_shortcuts), edit_shortcuts);
return build_div('<h4>Edit Mode (press <kbd>Enter</kbd> to enable)</h4>', edit_shortcuts);
return build_div('<h4>'+i18n.msg.sprintf(i18n.msg._('Edit Mode (press %s to enable)'),enterkey)+'</h4>', edit_shortcuts);
};
var build_one = function (s) {

@ -4,11 +4,12 @@
define([
'jquery',
'base/js/utils',
'base/js/i18n',
'base/js/dialog',
'base/js/keyboard',
'moment',
'bidi/bidi',
], function($, utils, dialog, keyboard, moment, bidi) {
], function($, utils, i18n, dialog, keyboard, moment, bidi) {
"use strict";
var SaveWidget = function (selector, options) {
@ -46,7 +47,7 @@ define([
that.update_address_bar();
});
this.events.on('notebook_save_failed.Notebook', function () {
that.set_save_status('Autosave Failed!');
that.set_save_status(i18n.msg._('Autosave Failed!'));
});
this.events.on('notebook_read_only.Notebook', function () {
that.set_save_status('(read only)');
@ -65,13 +66,17 @@ define([
});
};
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("Cancel"), i18n.msg._("Rename"), i18n.msg._("OK")];
SaveWidget.prototype.rename_notebook = function (options) {
options = options || {};
var that = this;
var dialog_body = $('<div/>').append(
$("<p/>").addClass("rename-message")
.text('Enter a new notebook name:')
.text(i18n.msg._('Enter a new notebook name:'))
).append(
$("<br/>")
).append(
@ -79,7 +84,7 @@ define([
.val(options.notebook.get_notebook_name())
);
var d = dialog.modal({
title: "Rename Notebook",
title: i18n.msg._("Rename Notebook"),
body: dialog_body,
notebook: options.notebook,
keyboard_manager: this.keyboard_manager,
@ -91,20 +96,18 @@ define([
click: function () {
var new_name = d.find('input').val();
if (!options.notebook.test_notebook_name(new_name)) {
d.find('.rename-message').text(
"Invalid notebook name. Notebook names must "+
"have 1 or more characters and can contain any characters " +
"except :/\\. Please enter a new notebook name:"
d.find('.rename-message').text(i18n.msg._(
"Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:")
);
return false;
} else {
d.find('.rename-message').text("Renaming...");
d.find('.rename-message').text(i18n.msg._("Renaming..."));
d.find('input[type="text"]').prop('disabled', true);
that.notebook.rename(new_name).then(
function () {
d.modal('hide');
}, function (error) {
d.find('.rename-message').text(error.message || 'Unknown error');
d.find('.rename-message').text(error.message || i18n.msg._('Unknown error'));
d.find('input[type="text"]').prop('disabled', false).focus().select();
}
);
@ -174,7 +177,7 @@ define([
this._schedule_render_checkpoint();
var el = this.element.find('span.checkpoint_status');
if (!this._checkpoint_date) {
el.text('').attr('title', 'no checkpoint');
el.text('').attr('title', i18n.msg._('no checkpoint'));
return;
}
var chkd = moment(this._checkpoint_date);
@ -189,8 +192,7 @@ define([
// <Today | yesterday|...> at hh,mm,ss
human_date = chkd.calendar();
}
el.text('Last Checkpoint: ' + human_date).attr('title', long_date);
el.text(i18n.msg.sprintf(i18n.msg._('Last Checkpoint: %s'),human_date)).attr('title', long_date);
};
@ -214,9 +216,9 @@ define([
SaveWidget.prototype.set_autosaved = function (dirty) {
if (dirty) {
this.set_save_status("(unsaved changes)");
this.set_save_status(i18n.msg._("(unsaved changes)"));
} else {
this.set_save_status("(autosaved)");
this.set_save_status(i18n.msg._("(autosaved)"));
}
};

@ -1,9 +1,10 @@
define(function(require){
define([
'jquery',
'base/js/dialog',
'base/js/i18n'
], function($, dialog, i18n){
"use strict";
var $ = require('jquery');
var dialog = require('base/js/dialog');
/**
* escape a Regular expression to act as a pure search string.
* though it will still have the case sensitivity options and all
@ -67,9 +68,11 @@ define(function(require){
var build_preview = function(body, aborted, html, replace){
body.empty();
if(aborted){
body.append($('<p/>').addClass('bg-warning').text("Warning, too many matches ("+html.length+"+), some changes might not be shown or applied"));
var warnmsg = i18n.msg.sprintf(i18n.msg._("Warning: too many matches (%d). Some changes might not be shown or applied."),html.length);
body.append($('<p/>').addClass('bg-warning').text(warnmsg));
} else {
body.append($('<p/>').text(html.length+" match"+(html.length==1?'':'es')));
var matchmsg = i18n.msg.sprintf(i18n.msg.ngettext("%d match","%d matches",html.length),html.length);
body.append($('<p/>').text(matchmsg));
}
for(var rindex=0; rindex<html.length; rindex++){
var pre = $('<pre/>')
@ -135,7 +138,7 @@ define(function(require){
res.push([match.index, match.index+match[0].length]);
escape_hatch++;
if(escape_hatch > 100){
console.warn("More than 100 matches, aborting");
console.warn(i18n.msg._("More than 100 matches, aborting"));
abort = true;
break;
}
@ -155,7 +158,7 @@ define(function(require){
.addClass("btn btn-default btn-sm")
.attr('data-toggle','button')
.css('font-weight', 'bold')
.attr('title', 'Use regex (JavaScript regex syntax)')
.attr('title', i18n.msg._('Use regex (JavaScript regex syntax)'))
.text('.*');
var allCellsButton = $('<button/>')
@ -163,20 +166,20 @@ define(function(require){
.attr('type', 'button')
.addClass("btn btn-default btn-sm")
.attr('data-toggle','button')
.attr('title', 'Replace in all cells');
.attr('title', i18n.msg._('Replace in selected cells'));
var isCaseSensitiveButton = $('<button/>')
.attr('type', 'button')
.addClass("btn btn-default btn-sm")
.attr('data-toggle','button')
.attr('tabindex', '0')
.attr('title', 'Match case')
.attr('title', i18n.msg._('Match case'))
.css('font-weight', 'bold')
.text('Aa');
var search = $("<input/>")
.addClass('form-control input-sm')
.attr('placeholder','Find');
.attr('placeholder',i18n.msg._('Find'));
var findFormGroup = $('<div/>').addClass('form-group');
findFormGroup.append(
@ -192,7 +195,7 @@ define(function(require){
var replace = $("<input/>")
.addClass('form-control input-sm')
.attr('placeholder','Replace');
.attr('placeholder',i18n.msg._('Replace'));
var replaceFormGroup = $('<div/>').addClass('form-group');
replaceFormGroup.append(replace);
@ -238,7 +241,7 @@ define(function(require){
var onError = function(body){
body.empty();
body.append($('<p/>').text('No matches, invalid or empty regular expression'));
body.append($('<p/>').text(i18n.msg._('No matches, invalid or empty regular expression')));
};
var get_cells = function(env){
@ -339,10 +342,14 @@ define(function(require){
search.on('input', onChange);
replace.on('input', onChange);
// This statement is used simply so that message extraction
// will pick up the strings. The actual setting of the text
// for the button is in dialog.js.
var button_labels = [ i18n.msg._("Replace All")];
var mod = dialog.modal({
show: false,
title: "Find and Replace",
title: i18n.msg._("Find and Replace"),
body:form,
keyboard_manager: env.notebook.keyboard_manager,
buttons:{
@ -367,7 +374,8 @@ define(function(require){
var load = function(keyboard_manager){
var action_all = {
help: 'find and replace',
cmd: i18n.msg._('find and replace'),
help: i18n.msg._('find and replace'),
handler: function(env, event){
snr(env, event);
}

@ -5,7 +5,7 @@ define([
"jquery",
"notebook/js/quickhelp",
"base/js/dialog",
'components/marked/lib/marked',
"components/marked/lib/marked"
], function (
$,
QH,
@ -45,14 +45,18 @@ var KeyBinding = createClass({
var that = this;
var available = this.props.available(this.state.shrt);
var empty = (this.state.shrt === '');
return createElement('div', {className:'jupyter-keybindings'},
var binding_setter = function(){
if (available) {
that.props.onAddBindings(that.state.shrt, that.props.ckey);
}
that.state.shrt='';
return false;
};
return createElement('form', {className:'jupyter-keybindings',
onSubmit: binding_setter
},
createElement('i', {className: "pull-right fa fa-plus", alt: 'add-keyboard-shortcut',
onClick: function () {
if (available) {
that.props.onAddBindings(that.state.shrt, that.props.ckey);
}
that.state.shrt='';
}
onClick: binding_setter
}),
createElement('input', {
type:'text',
@ -89,7 +93,7 @@ var KeyBindingList = createClass({
},
render: function() {
var that = this;
var childrens = this.state.data.map(function (binding) {
var children = this.state.data.map(function (binding) {
return createElement(KeyBinding, Object.assign({}, binding, {
onAddBindings: function (shortcut, action) {
that.props.bind(shortcut, action);
@ -102,27 +106,65 @@ var KeyBindingList = createClass({
}
}));
});
childrens.unshift(createElement('div', {className:'well', key:'disclamer', dangerouslySetInnerHTML:
children.unshift(createElement('div', {className:'well', key:'disclamer', id:'short-key-binding-intro', dangerouslySetInnerHTML:
{__html:
marked(
"Here you can modify the keyboard shortcuts available in "+
"command mode. Your changes will be stored for later sessions. "+
"See more [**details of defining keyboard shortcuts**](#long-key-binding-intro) below."
)}
}));
children.push(createElement('div', {className:'well', key:'disclamer', id:'long-key-binding-intro', dangerouslySetInnerHTML:
{__html:
marked(
"This dialog allows you to modify the keymap of the command mode, and persist the changes. "+
"You can define many type of shorctuts and sequences of keys. "+
"\n\n"+
" - Use dashes `-` to represent keys that should be pressed with modifiers, "+
"for example `Shift-a`, or `Ctrl-;`. \n"+
" - Separate by commas if the keys need to be pressed in sequence: `h,a,l,t`.\n"+
"\n\nYou can combine the two: `Ctrl-x,Meta-c,Meta-b,u,t,t,e,r,f,l,y`.\n"+
"Casing will have no effects: (e.g: `;` and `:` are the same on english keyboards)."+
" You need to explicitelty write the `Shift` modifier.\n"+
"Valid modifiers are `Cmd`, `Ctrl`, `Alt` ,`Meta`, `Cmdtrl`. Refer to developer docs "+
"for the corresponding keys depending on the platform."+
"This dialog allows you to modify the keyboard shortcuts available in command mode. "+
"Any changes will be persisted between sessions and across environments. "+
"You can define two kinds of shorctuts: **key combinations** and **key sequences**.\n"+
"\n"+
" - **Key Combinations**:\n"+
" - Use hyphens `-` to represent keys that should be pressed at the same time.\n"+
" - This is designed for use with *modifier* keys: `Cmd`, `Ctrl`, `Alt` ,`Meta`, "+
"`Cmdtrl`, and `Shift`.\n"+
" - `Cmdtrl` acts like `Cmd` on OS X/MacOS and `Ctrl` on Windows/Linux.\n"+
" - At most, one non-modifier key can exist in a key combination.\n"+
" - Multiple non-modifier key can exist in a key combination.\n"+
" - Modifier keys need to precede the non-modifier key in a combination.\n"+
" - *Valid Examples*: `Shift-a`, `Ctrl-;`, or `Ctrl-Shift-a`. \n"+
" - *Invalid Example*s: `a-b` and `a-Ctrl-Shift`. \n"+
" - **Key Sequences**:\n"+
" - Use commas `,` to represent keys that should be pressed in sequence.\n"+
" - The order in which keys must be pressed exactly matches the left-to-right order of "+
"the characters in the sequence, with no interruptions.\n"+
" - E.g., `h,a,l,t` would be triggered by typing <kbd>h</kbd> <kbd>a</kbd> "+
"<kbd>l</kbd> <kbd>t</kbd> but not <kbd>h</kbd> <kbd>a</kbd> <kbd>a</kbd> <kbd>l</kbd> "+
"<kbd>t</kbd> or <kbd>a</kbd> <kbd>h</kbd> <kbd>l</kbd> <kbd>t</kbd>.\n"+
" - Sequences can include the same key multiple times (e.g., `d,d`).\n"+
" - You cannot include any pairs of sequences where one is a 'prefix' the other.\n"+
" - E.g., `d,d,d` cannot be used a the same time as `d,d`.\n"+
" - Key combinations are unique elements that can be used in a sequence.\n"+
" - E.g., `Ctrl-d,d` and `d,d` can exist at the same time and are both valid key sequences.\n"+
"\n"+
"**Additional notes**:\n"+
"\n"+
"The case in which elements are written does not change the binding's meaning. "+
"E.g., `Ctrl-D` and `cTrl-d` are the same key binding. "+
"Thus, `Shift` needs to be explicitly included if it is part of the key binding. "+
"So, for example, if you set a command to be activated by `Shift-D,D`, the second `d` "+
"cannot be pressed at the same time as the `Shift` modifier key.\n"+
"\n"+
"Valid modifiers are specified by writing out their names explicitly: "+
"e.g., `Shift`, `Cmd`, `Ctrl`, `Alt` ,`Meta`, `Cmdtrl`. You cannot use the symbol equivalents "+
"(e.g., `⇧`, `⌘`, `⌃`, `⌥`); refer to developer docs for the corresponding keys "+
"(the mapping of which depends on the platform you are using)."+
"You can hover on the name/description of a command to see its exact internal name and "+
"differentiate from actions defined in various plugins. Changing the "+
"keybindings of edit mode is not yet possible."
"differentiate from actions defined in various plugins. \n"+
"\n"+
"Changing the keybindings of edit mode is not currently available."
)}
}));
return createElement('div',{}, childrens);
return createElement('div',{}, children);
}
});

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

Loading…
Cancel
Save