@ -1,65 +0,0 @@
|
||||
name: Linux JS Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: '*'
|
||||
pull_request:
|
||||
branches: '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu, macos]
|
||||
group: [notebook, base, services]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache pip on Linux
|
||||
uses: actions/cache@v1
|
||||
if: startsWith(runner.os, 'Linux')
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ hashFiles('**/requirements.txt', 'setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-${{ matrix.python }}
|
||||
|
||||
- name: Temporary workaround for sanitizer loading in JS Tests
|
||||
run: |
|
||||
cp tools/security_deprecated.js notebook/static/base/js/security.js
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade setuptools wheel
|
||||
npm install
|
||||
npm install -g casperjs@1.1.3 phantomjs-prebuilt@2.1.7
|
||||
pip install .[test]
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
python -m notebook.jstest ${{ matrix.group }}
|
||||
@ -0,0 +1,2 @@
|
||||
repos: []
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "jupyter-notebook-deps",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"backbone": "components/backbone#~1.2",
|
||||
"bootstrap": "bootstrap#~3.4",
|
||||
"bootstrap-tour": "0.9.0",
|
||||
"codemirror": "components/codemirror#5.56.0+components1",
|
||||
"create-react-class": "https://cdn.jsdelivr.net/npm/create-react-class@15.6.3/create-react-class.min.js",
|
||||
"es6-promise": "~1.0",
|
||||
"font-awesome": "components/font-awesome#~4.7.0",
|
||||
"google-caja": "5669",
|
||||
"jed": "~1.1.1",
|
||||
"jquery": "components/jquery#~3.5.0",
|
||||
"jquery-typeahead": "~2.10.6",
|
||||
"jquery-ui": "components/jqueryui#~1.12",
|
||||
"marked": "~0.7",
|
||||
"MathJax": "^2.7.4",
|
||||
"moment": "~2.19.3",
|
||||
"react": "~16.0.0",
|
||||
"requirejs": "~2.2",
|
||||
"requirejs-text": "~2.0.15",
|
||||
"requirejs-plugins": "~1.0.3",
|
||||
"text-encoding": "~0.1",
|
||||
"underscore": "components/underscore#~1.8.3",
|
||||
"xterm.js": "https://unpkg.com/xterm@~3.1.0/dist/xterm.js",
|
||||
"xterm.js-css": "https://unpkg.com/xterm@~3.1.0/dist/xterm.css",
|
||||
"xterm.js-fit": "https://unpkg.com/xterm@~3.1.0/dist/addons/fit/fit.js"
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
//This is file created for overwriting some of bootstrap element color in order to satisfiy the color contrast greater than 4.5:1.
|
||||
|
||||
.btn-danger{
|
||||
color: #ffffff;
|
||||
background-color: #df0404;
|
||||
border-color: #df0404;
|
||||
}
|
||||
|
||||
.btn-warning{
|
||||
color: #ffffff;
|
||||
background-color:#b46102;
|
||||
border-color: #b46102;
|
||||
}
|
||||
|
||||
@link-color: #296eaa;
|
||||
|
||||
.close {
|
||||
float: right;
|
||||
font-size: (@font-size-base * 1.5);
|
||||
font-weight: @close-font-weight;
|
||||
line-height: 1;
|
||||
color: @close-color;
|
||||
text-shadow: @close-text-shadow;
|
||||
.opacity(.6);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @close-color;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
.opacity(1.0);
|
||||
} button& {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
> li > a {
|
||||
color: @navbar-default-link-color;
|
||||
// To make keyboard focus clearly visible on the Controls(File,Edit,View,Insert,Cell...etc)present on the menubar.
|
||||
//&:hover, //To use browser's hover default value
|
||||
&:focus {
|
||||
/* -webkit-focus-ring-color = '#5B9DD9' */
|
||||
outline: -webkit-focus-ring-color auto 5px;
|
||||
// color: @navbar-default-link-hover-color;
|
||||
// background-color: @navbar-default-link-hover-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu_focus_highlight{
|
||||
a:focus {
|
||||
outline: -webkit-focus-ring-color auto 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
/*This file contains any manual css for this page that needs to override the global styles.
|
||||
This is only required when different pages style the same element differently. This is just
|
||||
a hack to deal with our current css styles and no new styling should be added in this file.*/
|
||||
|
||||
#ipython-main-app {
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['jquery', 'base/js/namespace', 'base/js/page'], function($, IPython, page) {
|
||||
function login_main() {
|
||||
var page_instance = new page.Page('div#header', 'div#site');
|
||||
page_instance.show();
|
||||
$('input#password_input').focus();
|
||||
IPython.page = page_instance;
|
||||
}
|
||||
return login_main;
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
], function($, utils){
|
||||
"use strict";
|
||||
|
||||
var LoginWidget = function (selector, options) {
|
||||
options = options || {};
|
||||
this.base_url = options.base_url || utils.get_body_data("baseUrl");
|
||||
this.selector = selector;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.bind_events();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
LoginWidget.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
this.element.find("button#logout").click(function () {
|
||||
window.location = utils.url_path_join(
|
||||
that.base_url,
|
||||
"logout"
|
||||
);
|
||||
});
|
||||
this.element.find("button#login").click(function () {
|
||||
window.location = utils.url_path_join(
|
||||
that.base_url,
|
||||
"login"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return {'LoginWidget': LoginWidget};
|
||||
});
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['base/js/namespace', 'base/js/page'], function(IPython, page) {
|
||||
function logout_main() {
|
||||
var page_instance = new page.Page('div#header', 'div#site');
|
||||
page_instance.show();
|
||||
|
||||
IPython.page = page_instance;
|
||||
}
|
||||
return logout_main;
|
||||
});
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['./loginmain', './logoutmain', 'bidi/bidi'], function (login_main, logout_main, bidi) {
|
||||
if(bidi.isMirroringEnabled()){
|
||||
$("body").attr("dir","rtl");
|
||||
}
|
||||
return {
|
||||
login_main: login_main,
|
||||
logout_main: logout_main
|
||||
};
|
||||
});
|
||||
@ -1,23 +0,0 @@
|
||||
// Custom styles for login.html
|
||||
.center-nav {
|
||||
display: inline-block;
|
||||
// pull the lower margin back
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .center-nav {
|
||||
form.pull-left {
|
||||
.pull-right();
|
||||
}
|
||||
.navbar-text {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
[dir="rtl"] .navbar-inner {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
[dir="rtl"] div.text-left {
|
||||
.text-right();
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
// Custom styles for logout.html
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
/*!
|
||||
*
|
||||
* IPython auth
|
||||
*
|
||||
*/
|
||||
@import "login.less";
|
||||
@import "logout.less";
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
@ -1,433 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['jquery',
|
||||
'codemirror/lib/codemirror',
|
||||
'bootstrap',
|
||||
'base/js/i18n'],
|
||||
function($, CodeMirror, bs, i18n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A wrapper around bootstrap modal for easier use
|
||||
* Pass it an option dictionary with the following properties:
|
||||
*
|
||||
* - body : <string> or <DOM node>, main content of the dialog
|
||||
* if pass a <string> it will be wrapped in a p tag and
|
||||
* html element escaped, unless you specify sanitize=false
|
||||
* option.
|
||||
* - title : Dialog title, default to empty string.
|
||||
* - buttons : dict of btn_options who keys are button label.
|
||||
* see btn_options below for description
|
||||
* - open : callback to trigger on dialog open.
|
||||
* - destroy:
|
||||
* - notebook : notebook instance
|
||||
* - keyboard_manager: keyboard manager instance.
|
||||
*
|
||||
* Unlike bootstrap modals, the backdrop options is set by default
|
||||
* to 'static'.
|
||||
*
|
||||
* The rest of the options are passed as is to bootstrap modals.
|
||||
*
|
||||
* btn_options: dict with the following property:
|
||||
*
|
||||
* - click : callback to trigger on click
|
||||
* - class : css classes to add to button.
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
var modal = function (options) {
|
||||
|
||||
var modal = $("<div/>")
|
||||
.addClass("modal")
|
||||
.addClass("fade")
|
||||
.attr("role", "dialog");
|
||||
var dialog = $("<div/>")
|
||||
.addClass("modal-dialog")
|
||||
.appendTo(modal);
|
||||
var dialog_content = $("<div/>")
|
||||
.addClass("modal-content")
|
||||
.appendTo(dialog);
|
||||
if(typeof(options.body) === 'string' && options.sanitize !== false){
|
||||
options.body = $("<p/>").text(options.body);
|
||||
}
|
||||
dialog_content.append(
|
||||
$("<div/>")
|
||||
.addClass("modal-header")
|
||||
.mousedown(function() {
|
||||
$(".modal").draggable({handle: '.modal-header'});
|
||||
})
|
||||
.append($("<button>")
|
||||
.attr("type", "button")
|
||||
.attr("aria-label", i18n.msg._("close"))
|
||||
.addClass("close")
|
||||
.attr("data-dismiss", "modal")
|
||||
.attr("aria-hidden", "true")
|
||||
.html("×")
|
||||
).append(
|
||||
$("<h4/>")
|
||||
.addClass('modal-title')
|
||||
.text(options.title || "")
|
||||
)
|
||||
).append(
|
||||
$("<div/>")
|
||||
.addClass("modal-body")
|
||||
.append(
|
||||
options.body || $("<p/>")
|
||||
)
|
||||
);
|
||||
|
||||
var footer = $("<div/>").addClass("modal-footer");
|
||||
|
||||
var default_button;
|
||||
|
||||
for (var label in options.buttons) {
|
||||
var btn_opts = options.buttons[label];
|
||||
var button = $("<button/>")
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr("data-dismiss", "modal")
|
||||
.text(i18n.msg.translate(label).fetch());
|
||||
if (btn_opts.id) {
|
||||
button.attr('id', btn_opts.id);
|
||||
}
|
||||
if (btn_opts.click) {
|
||||
button.click($.proxy(btn_opts.click, dialog_content));
|
||||
}
|
||||
if (btn_opts.class) {
|
||||
button.addClass(btn_opts.class);
|
||||
}
|
||||
footer.append(button);
|
||||
if (options.default_button && label === options.default_button) {
|
||||
default_button = button;
|
||||
}
|
||||
}
|
||||
if (!options.default_button) {
|
||||
default_button = footer.find("button").last();
|
||||
}
|
||||
dialog_content.append(footer);
|
||||
// hook up on-open event
|
||||
modal.on("shown.bs.modal", function () {
|
||||
setTimeout(function () {
|
||||
default_button.focus();
|
||||
if (options.open) {
|
||||
$.proxy(options.open, modal)();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// destroy modal on hide, unless explicitly asked not to
|
||||
if (options.destroy === undefined || options.destroy) {
|
||||
modal.on("hidden.bs.modal", function () {
|
||||
modal.remove();
|
||||
});
|
||||
}
|
||||
modal.on("hidden.bs.modal", function () {
|
||||
if (options.notebook) {
|
||||
var cell = options.notebook.get_selected_cell();
|
||||
if (cell) cell.select();
|
||||
}
|
||||
if (options.keyboard_manager) {
|
||||
options.keyboard_manager.enable();
|
||||
options.keyboard_manager.command_mode();
|
||||
}
|
||||
if (options.focus_button) {
|
||||
$(options.focus_button).focus();
|
||||
}
|
||||
});
|
||||
|
||||
if (options.keyboard_manager) {
|
||||
options.keyboard_manager.disable();
|
||||
}
|
||||
|
||||
if(options.backdrop === undefined){
|
||||
options.backdrop = 'static';
|
||||
}
|
||||
|
||||
return modal.modal(options);
|
||||
};
|
||||
|
||||
var kernel_modal = function (options) {
|
||||
/**
|
||||
* only one kernel dialog should be open at a time -- but
|
||||
* other modal dialogs can still be open
|
||||
*/
|
||||
$('.kernel-modal').modal('hide');
|
||||
var dialog = modal(options);
|
||||
dialog.addClass('kernel-modal');
|
||||
return dialog;
|
||||
};
|
||||
|
||||
var edit_metadata = function (options) {
|
||||
options.name = options.name || "Cell";
|
||||
var error_div = $('<div/>').css('color', 'red');
|
||||
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', i18n.msg._('Edit the metadata'))
|
||||
.append(
|
||||
$('<form/>').append(
|
||||
$('<fieldset/>').append(
|
||||
$('<label/>')
|
||||
.attr('for','metadata')
|
||||
.text(message)
|
||||
)
|
||||
.append(error_div)
|
||||
.append($('<br/>'))
|
||||
.append(textarea)
|
||||
)
|
||||
);
|
||||
var editor = CodeMirror.fromTextArea(textarea[0], {
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
indentUnit: 2,
|
||||
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: title_msg,
|
||||
body: dialogform,
|
||||
default_button: "Cancel",
|
||||
buttons: {
|
||||
Cancel: {},
|
||||
Edit: { class : "btn-primary",
|
||||
click: function() {
|
||||
/**
|
||||
* validate json and set it
|
||||
*/
|
||||
var new_md;
|
||||
try {
|
||||
new_md = JSON.parse(editor.getValue());
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
error_div.text(i18n.msg._('WARNING: Could not save invalid JSON.'));
|
||||
return false;
|
||||
}
|
||||
options.callback(new_md);
|
||||
options.notebook.apply_directionality();
|
||||
}
|
||||
}
|
||||
},
|
||||
notebook: options.notebook,
|
||||
keyboard_manager: options.keyboard_manager,
|
||||
});
|
||||
|
||||
modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
|
||||
modal_obj.on('hide.bs.modal', function(){
|
||||
options.edit_metadata_button ? options.edit_metadata_button.focus() : "";});
|
||||
};
|
||||
|
||||
var edit_attachments = function (options) {
|
||||
// This shows the Edit Attachments dialog. This dialog allows the
|
||||
// user to delete attachments. We show a list of attachments to
|
||||
// the user and he can mark some of them for deletion. The deletion
|
||||
// is applied when the 'Apply' button of this dialog is pressed.
|
||||
var message;
|
||||
var attachments_list;
|
||||
if (Object.keys(options.attachments).length == 0) {
|
||||
message = i18n.msg._("There are no attachments for this cell.");
|
||||
attachments_list = $('<div>');
|
||||
} else {
|
||||
message = i18n.msg._("Current cell attachments");
|
||||
|
||||
attachments_list = $('<div>')
|
||||
.addClass('list_container')
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('row list_header')
|
||||
.append(
|
||||
$('<div>')
|
||||
.text(i18n.msg._('Attachments'))
|
||||
)
|
||||
);
|
||||
|
||||
// This is a set containing keys of attachments to be deleted when
|
||||
// the Apply button is clicked
|
||||
var to_delete = {};
|
||||
|
||||
var refresh_attachments_list = function() {
|
||||
$(attachments_list).find('.row').remove();
|
||||
for (var key in options.attachments) {
|
||||
var mime = Object.keys(options.attachments[key])[0];
|
||||
var deleted = key in to_delete;
|
||||
|
||||
// This ensures the current value of key is captured since
|
||||
// javascript only has function scope
|
||||
var btn;
|
||||
// Trash/restore button
|
||||
(function(){
|
||||
var _key = key;
|
||||
btn = $('<button>')
|
||||
.addClass('btn btn-default btn-xs')
|
||||
.css('display', 'inline-block');
|
||||
if (deleted) {
|
||||
btn.attr('title', i18n.msg._('Restore'))
|
||||
.append(
|
||||
$('<i>')
|
||||
.addClass('fa fa-plus')
|
||||
);
|
||||
btn.click(function() {
|
||||
delete to_delete[_key];
|
||||
refresh_attachments_list();
|
||||
});
|
||||
} else {
|
||||
btn.attr('title', i18n.msg._('Delete'))
|
||||
.addClass('btn-danger')
|
||||
.append(
|
||||
$('<i>')
|
||||
.addClass('fa fa-trash')
|
||||
);
|
||||
btn.click(function() {
|
||||
to_delete[_key] = true;
|
||||
refresh_attachments_list();
|
||||
});
|
||||
}
|
||||
return btn;
|
||||
})();
|
||||
var row = $('<div>')
|
||||
.addClass('col-md-12 att_row')
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('row')
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('att-name col-xs-4')
|
||||
.text(key)
|
||||
)
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('col-xs-4 text-muted')
|
||||
.text(mime)
|
||||
)
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('item-buttons pull-right')
|
||||
.append(btn)
|
||||
)
|
||||
);
|
||||
if (deleted) {
|
||||
row.find('.att-name')
|
||||
.css('text-decoration', 'line-through');
|
||||
}
|
||||
|
||||
attachments_list.append($('<div>')
|
||||
.addClass('list_item row')
|
||||
.append(row)
|
||||
);
|
||||
}
|
||||
};
|
||||
refresh_attachments_list();
|
||||
}
|
||||
|
||||
var dialogform = $('<div/>')
|
||||
.attr('title', i18n.msg._('Edit attachments'))
|
||||
.append(message)
|
||||
.append('<br />')
|
||||
.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: title_msg,
|
||||
body: dialogform,
|
||||
buttons: {
|
||||
Apply: { class : "btn-primary",
|
||||
click: function() {
|
||||
for (var key in to_delete) {
|
||||
delete options.attachments[key];
|
||||
}
|
||||
options.callback(options.attachments);
|
||||
}
|
||||
},
|
||||
Cancel: {}
|
||||
},
|
||||
notebook: options.notebook,
|
||||
keyboard_manager: options.keyboard_manager,
|
||||
});
|
||||
};
|
||||
|
||||
var insert_image = function (options) {
|
||||
var message =
|
||||
i18n.msg._("Select a file to insert.");
|
||||
var file_input = $('<input/>')
|
||||
.attr('type', 'file')
|
||||
.attr('accept', 'image/*')
|
||||
.attr('name', 'file')
|
||||
.on('change', function(file) {
|
||||
var $btn = $(modal_obj).find('#btn_ok');
|
||||
if (this.files.length > 0) {
|
||||
$btn.removeClass('disabled');
|
||||
} else {
|
||||
$btn.addClass('disabled');
|
||||
}
|
||||
});
|
||||
var dialogform = $('<div/>').attr('title', i18n.msg._('Edit attachments'))
|
||||
.append(
|
||||
$('<form id="insert-image-form" />').append(
|
||||
$('<fieldset/>').append(
|
||||
$('<label/>')
|
||||
.attr('for','file')
|
||||
.text(message)
|
||||
)
|
||||
.append($('<br/>'))
|
||||
.append(file_input)
|
||||
)
|
||||
);
|
||||
var modal_obj = modal({
|
||||
title: i18n.msg._("Select a file"),
|
||||
body: dialogform,
|
||||
buttons: {
|
||||
OK: {
|
||||
id : 'btn_ok',
|
||||
class : "btn-primary disabled",
|
||||
click: function() {
|
||||
options.callback(file_input[0].files[0]);
|
||||
}
|
||||
},
|
||||
Cancel: {}
|
||||
},
|
||||
notebook: options.notebook,
|
||||
keyboard_manager: options.keyboard_manager,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var dialog = {
|
||||
modal : modal,
|
||||
kernel_modal : kernel_modal,
|
||||
edit_metadata : edit_metadata,
|
||||
edit_attachments : edit_attachments,
|
||||
insert_image : insert_image
|
||||
};
|
||||
|
||||
return dialog;
|
||||
});
|
||||
@ -1,37 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Give us an object to bind all events to. This object should be created
|
||||
// before all other objects so it exists when others register event handlers.
|
||||
// To register an event handler:
|
||||
//
|
||||
// requirejs(['base/js/events'], function (events) {
|
||||
// events.on("event.Namespace", function () { do_stuff(); });
|
||||
// });
|
||||
|
||||
define(['jquery', 'base/js/namespace'], function($, Jupyter) {
|
||||
"use strict";
|
||||
|
||||
// Events singleton
|
||||
if (!window._Events) {
|
||||
window._Events = function () {};
|
||||
window._events = new window._Events();
|
||||
}
|
||||
|
||||
// Backwards compatibility.
|
||||
Jupyter.Events = window._Events;
|
||||
Jupyter.events = window._events;
|
||||
|
||||
var events = $([window._events]);
|
||||
|
||||
// catch and log errors in triggered events
|
||||
events._original_trigger = events.trigger;
|
||||
events.trigger = function (name, data) {
|
||||
try {
|
||||
this._original_trigger.apply(this, arguments);
|
||||
} catch (e) {
|
||||
console.error("Exception in event handler for " + name, e, arguments);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
});
|
||||
@ -1,16 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Module to handle i18n ( Internationalization ) and translated UI
|
||||
|
||||
define([
|
||||
'jed'
|
||||
], function(Jed) {
|
||||
"use strict";
|
||||
|
||||
var i18n = new Jed(document.nbjs_translations);
|
||||
i18n._ = i18n.gettext;
|
||||
i18n.msg = i18n; // Just a place holder until the init promise resolves.
|
||||
|
||||
return i18n;
|
||||
});
|
||||
@ -1,576 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module keyboard
|
||||
* @namespace keyboard
|
||||
* @class ShortcutManager
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'underscore',
|
||||
], function($, utils, _) {
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Setup global keycodes and inverse keycodes.
|
||||
*
|
||||
* See http://unixpapa.com/js/key.html for a complete description. The short of
|
||||
* it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
|
||||
* and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
|
||||
* but have minor differences.
|
||||
**/
|
||||
|
||||
// These apply to Firefox, (Webkit and IE)
|
||||
// This does work **only** on US keyboard.
|
||||
var _keycodes = {
|
||||
'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
|
||||
'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
|
||||
's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
|
||||
'1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
|
||||
'7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
|
||||
'[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
|
||||
'\\ |': 220, '\' "': 222,
|
||||
'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
|
||||
'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
|
||||
'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
|
||||
'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
|
||||
'f8': 119, 'f9': 120, 'f10': 121, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
|
||||
'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
|
||||
'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
|
||||
'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
|
||||
'insert': 45, 'delete': 46, 'numlock': 144,
|
||||
};
|
||||
|
||||
// These apply to Firefox and Opera
|
||||
var _mozilla_keycodes = {
|
||||
'; :': 59, '= +': 61, '- _': 173, 'meta': 224, 'minus':173
|
||||
};
|
||||
|
||||
// This apply to Webkit and IE
|
||||
var _ie_keycodes = {
|
||||
'; :': 186, '= +': 187, '- _': 189, 'minus':189
|
||||
};
|
||||
|
||||
var browser = utils.browser[0];
|
||||
var platform = utils.platform;
|
||||
|
||||
if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
|
||||
$.extend(_keycodes, _mozilla_keycodes);
|
||||
} else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
|
||||
$.extend(_keycodes, _ie_keycodes);
|
||||
}
|
||||
|
||||
var keycodes = {};
|
||||
var inv_keycodes = {};
|
||||
for (var name in _keycodes) {
|
||||
var names = name.split(' ');
|
||||
if (names.length === 1) {
|
||||
var n = names[0];
|
||||
keycodes[n] = _keycodes[n];
|
||||
inv_keycodes[_keycodes[n]] = n;
|
||||
} else {
|
||||
var primary = names[0];
|
||||
var secondary = names[1];
|
||||
keycodes[primary] = _keycodes[name];
|
||||
keycodes[secondary] = _keycodes[name];
|
||||
inv_keycodes[_keycodes[name]] = primary;
|
||||
}
|
||||
}
|
||||
|
||||
var normalize_key = function (key) {
|
||||
return inv_keycodes[keycodes[key]];
|
||||
};
|
||||
|
||||
var normalize_shortcut = function (shortcut) {
|
||||
/**
|
||||
* @function _normalize_shortcut
|
||||
* @private
|
||||
* return a dict containing the normalized shortcut and the number of time it should be pressed:
|
||||
*
|
||||
* Put a shortcut into normalized form:
|
||||
* 1. Make lowercase
|
||||
* 2. Replace cmd by meta
|
||||
* 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
|
||||
* 4. Normalize keys
|
||||
**/
|
||||
if (platform === 'MacOS') {
|
||||
shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'cmd-');
|
||||
} else {
|
||||
shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'ctrl-');
|
||||
}
|
||||
|
||||
shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
|
||||
shortcut = shortcut.replace(/-$/, 'minus'); // catch shortcuts using '-' key
|
||||
shortcut = shortcut.replace(/,$/, 'comma'); // catch shortcuts using '-' key
|
||||
if(shortcut.indexOf(',') !== -1){
|
||||
var sht = shortcut.split(',');
|
||||
sht = _.map(sht, normalize_shortcut);
|
||||
return shortcut;
|
||||
}
|
||||
shortcut = shortcut.replace(/comma/g, ','); // catch shortcuts using '-' key
|
||||
var values = shortcut.split("-");
|
||||
if (values.length === 1) {
|
||||
return normalize_key(values[0]);
|
||||
} else {
|
||||
var modifiers = values.slice(0,-1);
|
||||
var key = normalize_key(values[values.length-1]);
|
||||
modifiers.sort();
|
||||
return modifiers.join('-') + '-' + key;
|
||||
}
|
||||
};
|
||||
|
||||
var shortcut_to_event = function (shortcut, type) {
|
||||
/**
|
||||
* Convert a shortcut (shift-r) to a jQuery Event object
|
||||
**/
|
||||
type = type || 'keydown';
|
||||
shortcut = normalize_shortcut(shortcut);
|
||||
shortcut = shortcut.replace(/-$/, 'minus'); // catch shortcuts using '-' key
|
||||
var values = shortcut.split("-");
|
||||
var modifiers = values.slice(0,-1);
|
||||
var key = values[values.length-1];
|
||||
var opts = {which: keycodes[key]};
|
||||
if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
|
||||
if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
|
||||
if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
|
||||
if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
|
||||
return $.Event(type, opts);
|
||||
};
|
||||
|
||||
var only_modifier_event = function(event){
|
||||
/**
|
||||
* Return `true` if the event only contains modifiers keys.
|
||||
* false otherwise
|
||||
**/
|
||||
var key = inv_keycodes[event.which];
|
||||
return ((event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) &&
|
||||
(key === 'alt'|| key === 'ctrl'|| key === 'meta'|| key === 'shift'));
|
||||
|
||||
};
|
||||
|
||||
var event_to_shortcut = function (event) {
|
||||
/**
|
||||
* Convert a jQuery Event object to a normalized shortcut string (shift-r)
|
||||
**/
|
||||
var shortcut = '';
|
||||
var key = inv_keycodes[event.which];
|
||||
if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
|
||||
if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
|
||||
if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
|
||||
if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
|
||||
shortcut += key;
|
||||
return shortcut;
|
||||
};
|
||||
|
||||
// Shortcut manager class
|
||||
|
||||
var ShortcutManager = function (delay, events, actions, env, config, mode) {
|
||||
/**
|
||||
* A class to deal with keyboard event and shortcut
|
||||
*
|
||||
* @class ShortcutManager
|
||||
* @constructor
|
||||
*
|
||||
* :config: configobjet on which to call `update(....)` to persist the config.
|
||||
* :mode: mode of this shortcut manager where to persist config.
|
||||
*/
|
||||
mode = mode || 'command';
|
||||
this._shortcuts = {};
|
||||
this._defaults_bindings = [];
|
||||
this.delay = delay || 800; // delay in milliseconds
|
||||
this.events = events;
|
||||
this.actions = actions;
|
||||
this.actions.extend_env(env);
|
||||
this._queue = [];
|
||||
this._cleartimeout = null;
|
||||
this._config = config;
|
||||
this._mode = mode;
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.clearsoon = function(){
|
||||
/**
|
||||
* Clear the pending shortcut soon, and cancel previous clearing
|
||||
* that might be registered.
|
||||
**/
|
||||
var that = this;
|
||||
clearTimeout(this._cleartimeout);
|
||||
this._cleartimeout = setTimeout(function(){that.clearqueue();}, this.delay);
|
||||
};
|
||||
|
||||
|
||||
ShortcutManager.prototype.clearqueue = function(){
|
||||
/**
|
||||
* clear the pending shortcut sequence now.
|
||||
**/
|
||||
this._queue = [];
|
||||
clearTimeout(this._cleartimeout);
|
||||
};
|
||||
|
||||
|
||||
var flatten_shorttree = function(tree){
|
||||
/**
|
||||
* Flatten a tree of shortcut sequences.
|
||||
* use full to iterate over all the key/values of available shortcuts.
|
||||
**/
|
||||
var dct = {};
|
||||
_.forEach(tree, function(value, key) {
|
||||
if(typeof(value) === 'string'){
|
||||
dct[key] = value;
|
||||
} else {
|
||||
var ftree=flatten_shorttree(value);
|
||||
_.forEach(ftree, function(v2, subkey) {
|
||||
dct[key+','+subkey] = ftree[subkey];
|
||||
});
|
||||
}
|
||||
});
|
||||
return dct;
|
||||
};
|
||||
|
||||
|
||||
ShortcutManager.prototype.get_action_shortcuts = function(name){
|
||||
var ftree = flatten_shorttree(this._shortcuts);
|
||||
var res = [];
|
||||
_.forEach(ftree, function(value, key) {
|
||||
if(value === name){
|
||||
res.push(key);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.get_action_shortcut = function(name){
|
||||
var matches = this.get_action_shortcuts(name);
|
||||
if (matches.length > 0) {
|
||||
return matches[0];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.help = function () {
|
||||
var that = this;
|
||||
var help = [];
|
||||
var ftree = flatten_shorttree(this._shortcuts);
|
||||
_.forEach(ftree, function(value, key) {
|
||||
var action = that.actions.get(value);
|
||||
var help_string = action.help||'== no help ==';
|
||||
var help_index = action.help_index;
|
||||
if (help_string) {
|
||||
var shortstring = (action.shortstring||key);
|
||||
help.push({
|
||||
shortcut: shortstring,
|
||||
help: help_string,
|
||||
help_index: help_index}
|
||||
);
|
||||
}
|
||||
});
|
||||
help.sort(function (a, b) {
|
||||
if (a.help_index === b.help_index) {
|
||||
if (a.shortcut === b.shortcut) {
|
||||
return 0;
|
||||
}
|
||||
if (a.shortcut > b.shortcut) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (a.help_index === undefined || a.help_index > b.help_index){
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
return help;
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.clear_shortcuts = function () {
|
||||
this._shortcuts = {};
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.get_shortcut = function (shortcut){
|
||||
/**
|
||||
* return a node of the shortcut tree which an action name (string) if leaf,
|
||||
* and an object with `object.subtree===true`
|
||||
**/
|
||||
if(typeof(shortcut) === 'string'){
|
||||
shortcut = shortcut.split(',');
|
||||
}
|
||||
|
||||
return this._get_leaf(shortcut, this._shortcuts);
|
||||
};
|
||||
|
||||
|
||||
ShortcutManager.prototype._get_leaf = function(shortcut_array, tree){
|
||||
/**
|
||||
* @private
|
||||
* find a leaf/node in a subtree of the keyboard shortcut
|
||||
*
|
||||
**/
|
||||
if(shortcut_array.length === 1){
|
||||
return tree[shortcut_array[0]];
|
||||
} else if( typeof(tree[shortcut_array[0]]) !== 'string'){
|
||||
return this._get_leaf(shortcut_array.slice(1), tree[shortcut_array[0]]);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.set_shortcut = function( shortcut, action_name){
|
||||
if( typeof(action_name) !== 'string'){throw new Error('action is not a string', action_name);}
|
||||
if( typeof(shortcut) === 'string'){
|
||||
shortcut = shortcut.split(',');
|
||||
}
|
||||
return this._set_leaf(shortcut, action_name, this._shortcuts);
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._is_leaf = function(shortcut_array, tree){
|
||||
if(shortcut_array.length === 1){
|
||||
return(typeof(tree[shortcut_array[0]]) === 'string');
|
||||
} else {
|
||||
var subtree = tree[shortcut_array[0]];
|
||||
return this._is_leaf(shortcut_array.slice(1), subtree );
|
||||
}
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._remove_leaf = function(shortcut_array, tree, allow_node){
|
||||
if(shortcut_array.length === 1){
|
||||
var current_node = tree[shortcut_array[0]];
|
||||
if(typeof(current_node) === 'string'){
|
||||
delete tree[shortcut_array[0]];
|
||||
} else {
|
||||
throw new Error('try to delete non-leaf');
|
||||
}
|
||||
} else {
|
||||
this._remove_leaf(shortcut_array.slice(1), tree[shortcut_array[0]], allow_node);
|
||||
if(_.keys(tree[shortcut_array[0]]).length === 0){
|
||||
delete tree[shortcut_array[0]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.is_available_shortcut = function(shortcut){
|
||||
var shortcut_array = shortcut.split(',');
|
||||
return this._is_available_shortcut(shortcut_array, this._shortcuts);
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._is_available_shortcut = function(shortcut_array, tree){
|
||||
var current_node = tree[shortcut_array[0]];
|
||||
if(!shortcut_array[0]){
|
||||
return false;
|
||||
}
|
||||
if(current_node === undefined){
|
||||
return true;
|
||||
} else {
|
||||
if (typeof(current_node) === 'string'){
|
||||
return false;
|
||||
} else { // assume is a sub-shortcut tree
|
||||
return this._is_available_shortcut(shortcut_array.slice(1), current_node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._set_leaf = function(shortcut_array, action_name, tree){
|
||||
var current_node = tree[shortcut_array[0]];
|
||||
|
||||
if(shortcut_array.length === 1){
|
||||
if(current_node !== undefined && typeof(current_node) !== 'string'){
|
||||
console.warn('[warning], you are overriting a long shortcut with a shorter one');
|
||||
}
|
||||
tree[shortcut_array[0]] = action_name;
|
||||
return true;
|
||||
} else {
|
||||
if(typeof(current_node) === 'string'){
|
||||
console.warn('you are trying to set a shortcut that will be shadowed'+
|
||||
'by a more specific one. Aborting for :', action_name, 'the following '+
|
||||
'will take precedence', current_node);
|
||||
return false;
|
||||
} else {
|
||||
tree[shortcut_array[0]] = tree[shortcut_array[0]]||{};
|
||||
}
|
||||
this._set_leaf(shortcut_array.slice(1), action_name, tree[shortcut_array[0]]);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._persist_shortcut = function(shortcut, data) {
|
||||
/**
|
||||
* add a shortcut to this manager and persist it to the config file.
|
||||
**/
|
||||
shortcut = shortcut.toLowerCase();
|
||||
this.add_shortcut(shortcut, data);
|
||||
var patch = {keys:{}};
|
||||
patch.keys[this._mode] = {bind:{}};
|
||||
patch.keys[this._mode].bind[shortcut] = data;
|
||||
this._config.update(patch);
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._persist_remove_shortcut = function(shortcut){
|
||||
/**
|
||||
* Remove a shortcut from this manager and persist its removal.
|
||||
*/
|
||||
|
||||
shortcut = shortcut.toLowerCase();
|
||||
this.remove_shortcut(shortcut);
|
||||
var patch = {keys: {}};
|
||||
patch.keys[this._mode] = {bind:{}};
|
||||
patch.keys[this._mode].bind[shortcut] = null;
|
||||
this._config.update(patch);
|
||||
|
||||
// if the shortcut we unbind is a default one, we add it to the list of
|
||||
// things to unbind at startup
|
||||
if( this._defaults_bindings.indexOf(shortcut) !== -1 ){
|
||||
var cnf = (this._config.data.keys || {})[this._mode];
|
||||
var unbind_array = cnf.unbind || [];
|
||||
|
||||
|
||||
// unless it's already there (like if we have remapped a default
|
||||
// shortcut to another command): unbind it)
|
||||
if(unbind_array.indexOf(shortcut) === -1){
|
||||
var _parray = unbind_array.concat(shortcut);
|
||||
var unbind_patch = {keys:{}};
|
||||
unbind_patch.keys[this._mode] = {unbind:_parray};
|
||||
console.warn('up:', unbind_patch);
|
||||
this._config.update(unbind_patch);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
|
||||
/**
|
||||
* Add an action to be handled by shortcut manager.
|
||||
*
|
||||
* - `shortcut` should be a `Shortcut Sequence` of the for `Ctrl-Alt-C,Meta-X`...
|
||||
* - `data` could be an `action name`, an `action` or a `function`.
|
||||
* if a `function` is passed it will be converted to an anonymous `action`.
|
||||
*
|
||||
**/
|
||||
var action_name = this.actions.get_name(data);
|
||||
if (! action_name){
|
||||
if (typeof data === 'string') {
|
||||
// If we have an action name, allow it to be bound anyway.
|
||||
console.log("Unknown action '" + data + "' for shortcut " + shortcut
|
||||
+ "; it may be defined by an extension which is not yet loaded.");
|
||||
action_name = data;
|
||||
} else {
|
||||
throw new Error('does not know how to deal with : ' + data);
|
||||
}
|
||||
}
|
||||
var _shortcut = normalize_shortcut(shortcut);
|
||||
this.set_shortcut(_shortcut, action_name);
|
||||
|
||||
if (!suppress_help_update) {
|
||||
// update the keyboard shortcuts notebook help
|
||||
this.events.trigger('rebuild.QuickHelp');
|
||||
}
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.add_shortcuts = function (data) {
|
||||
/**
|
||||
* Convenient methods to call `add_shortcut(key, value)` on several items
|
||||
*
|
||||
* data : Dict of the form {key:value, ...}
|
||||
**/
|
||||
var that = this;
|
||||
_.forEach(data, function(value, key) {
|
||||
that.add_shortcut(key, value, true);
|
||||
});
|
||||
// update the keyboard shortcuts notebook help
|
||||
this.events.trigger('rebuild.QuickHelp');
|
||||
};
|
||||
|
||||
ShortcutManager.prototype._add_default_shortcuts = function (data) {
|
||||
/**
|
||||
* same as add_shortcuts, but register them as "default" that if persistently unbound, with
|
||||
* persist_remove_shortcut, need to be on the "unbind" list.
|
||||
**/
|
||||
this._defaults_bindings = this._defaults_bindings.concat(Object.keys(data));
|
||||
this.add_shortcuts(data);
|
||||
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
|
||||
/**
|
||||
* Remove the binding of shortcut `shortcut` with its action.
|
||||
* throw an error if trying to remove a non-exiting shortcut
|
||||
**/
|
||||
if(!shortcut){
|
||||
console.warn('trying to remove empty shortcut');
|
||||
return;
|
||||
}
|
||||
shortcut = normalize_shortcut(shortcut);
|
||||
if( typeof(shortcut) === 'string'){
|
||||
shortcut = shortcut.split(',');
|
||||
}
|
||||
/*
|
||||
* The shortcut error should be explicit here, because it will be
|
||||
* seen by users.
|
||||
*/
|
||||
try {
|
||||
this._remove_leaf(shortcut, this._shortcuts);
|
||||
if (!suppress_help_update) {
|
||||
// update the keyboard shortcuts notebook help
|
||||
this.events.trigger('rebuild.QuickHelp');
|
||||
}
|
||||
} catch (ex) {
|
||||
throw new Error('trying to remove a non-existent shortcut', shortcut, typeof shortcut);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
ShortcutManager.prototype.call_handler = function (event) {
|
||||
/**
|
||||
* Call the corresponding shortcut handler for a keyboard event
|
||||
* @method call_handler
|
||||
* @return {Boolean} `true|false`, `false` if no handler was found, otherwise the value return by the handler.
|
||||
* @param event {event}
|
||||
*
|
||||
* given an event, call the corresponding shortcut.
|
||||
* return false is event wan handled, true otherwise
|
||||
* in any case returning false stop event propagation
|
||||
**/
|
||||
|
||||
|
||||
this.clearsoon();
|
||||
if(only_modifier_event(event)){
|
||||
return true;
|
||||
}
|
||||
var shortcut = event_to_shortcut(event);
|
||||
this._queue.push(shortcut);
|
||||
var action_name = this.get_shortcut(this._queue);
|
||||
|
||||
if (typeof(action_name) === 'undefined'|| action_name === null){
|
||||
this.clearqueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.actions.exists(action_name)) {
|
||||
event.preventDefault();
|
||||
this.clearqueue();
|
||||
return this.actions.call(action_name, event);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
ShortcutManager.prototype.handles = function (event) {
|
||||
var shortcut = event_to_shortcut(event);
|
||||
var action_name = this.get_shortcut(this._queue.concat(shortcut));
|
||||
return (typeof(action_name) !== 'undefined');
|
||||
};
|
||||
|
||||
return {
|
||||
keycodes : keycodes,
|
||||
inv_keycodes : inv_keycodes,
|
||||
ShortcutManager : ShortcutManager,
|
||||
normalize_key : normalize_key,
|
||||
normalize_shortcut : normalize_shortcut,
|
||||
shortcut_to_event : shortcut_to_event,
|
||||
event_to_shortcut : event_to_shortcut,
|
||||
};
|
||||
});
|
||||
@ -1,117 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/mathjaxutils',
|
||||
'base/js/security',
|
||||
'components/marked/lib/marked',
|
||||
'codemirror/lib/codemirror',
|
||||
], function($, utils, mathjaxutils, security, marked, CodeMirror){
|
||||
"use strict";
|
||||
|
||||
marked.setOptions({
|
||||
gfm : true,
|
||||
tables: true,
|
||||
langPrefix: "cm-s-ipython language-",
|
||||
highlight: function(code, lang, callback) {
|
||||
if (!lang) {
|
||||
// no language, no highlight
|
||||
if (callback) {
|
||||
callback(null, code);
|
||||
return;
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
utils.requireCodeMirrorMode(lang, function (spec) {
|
||||
var el = document.createElement("div");
|
||||
var mode = CodeMirror.getMode({}, spec);
|
||||
if (!mode) {
|
||||
console.log("No CodeMirror mode: " + lang);
|
||||
callback(null, code);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CodeMirror.runMode(code, spec, el);
|
||||
callback(null, el.innerHTML);
|
||||
} catch (err) {
|
||||
console.log("Failed to highlight " + lang + " code", err);
|
||||
callback(err, code);
|
||||
}
|
||||
}, function (err) {
|
||||
console.log("No CodeMirror mode: " + lang);
|
||||
console.log("Require CodeMirror mode error: " + err);
|
||||
callback(null, code);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var mathjax_init_done = false;
|
||||
function ensure_mathjax_init() {
|
||||
if(!mathjax_init_done) {
|
||||
mathjax_init_done = true;
|
||||
mathjaxutils.init();
|
||||
}
|
||||
}
|
||||
|
||||
function render(markdown, options, callback) {
|
||||
/**
|
||||
* Find a readme in the current directory. Look for files with
|
||||
* a name like 'readme.md' (case insensitive) or similar and
|
||||
* mimetype 'text/markdown'.
|
||||
*
|
||||
* @param markdown: the markdown text to parse
|
||||
* @param options
|
||||
* Object with parameters:
|
||||
* with_math: the markdown can contain mathematics
|
||||
* clean_tables: prevent default inline styles for table cells
|
||||
* sanitize: remove dangerous html (like <script>)
|
||||
* @param callback
|
||||
* A function with two arguments (err, html)
|
||||
* err: null or the error if there was one
|
||||
* html: the rendered html string, or if {sanitize: true} was used
|
||||
* a sanitized jQuery object
|
||||
*/
|
||||
options = $.extend({
|
||||
// Apply mathjax transformation
|
||||
with_math: false,
|
||||
// Prevent marked from returning inline styles for table cells
|
||||
clean_tables: false,
|
||||
// Apply sanitation, this will return a jQuery object.
|
||||
sanitize: false,
|
||||
}, options);
|
||||
var renderer = new marked.Renderer();
|
||||
if(options.clean_tables) {
|
||||
renderer.tablecell = function (content, flags) {
|
||||
var type = flags.header ? 'th' : 'td';
|
||||
var style = flags.align == null ? '': ' style="text-align: ' + flags.align + '"';
|
||||
var start_tag = '<' + type + style + '>';
|
||||
var end_tag = '</' + type + '>\n';
|
||||
return start_tag + content + end_tag;
|
||||
};
|
||||
}
|
||||
var text = markdown;
|
||||
var math = null;
|
||||
if(options.with_math) {
|
||||
ensure_mathjax_init();
|
||||
var text_and_math = mathjaxutils.remove_math(markdown);
|
||||
text = text_and_math[0];
|
||||
math = text_and_math[1];
|
||||
}
|
||||
marked(text, { renderer: renderer }, function (err, html) {
|
||||
if(!err) {
|
||||
if(options.with_math) {
|
||||
html = mathjaxutils.replace_math(html, math);
|
||||
}
|
||||
if(options.sanitize) {
|
||||
html = $(security.sanitize_html_and_parse(html, true));
|
||||
}
|
||||
}
|
||||
callback(err, html);
|
||||
});
|
||||
}
|
||||
|
||||
return {'render': render};
|
||||
});
|
||||
@ -1,247 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog',
|
||||
], function($, utils, i18n, dialog) {
|
||||
"use strict";
|
||||
|
||||
var init = function () {
|
||||
if (window.MathJax) {
|
||||
// MathJax loaded
|
||||
MathJax.Hub.Config({
|
||||
tex2jax: {
|
||||
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
|
||||
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
|
||||
processEscapes: true,
|
||||
processEnvironments: true
|
||||
},
|
||||
TeX: {
|
||||
extensions: ['newcommand.js', 'begingroup.js'] // For \gdef
|
||||
},
|
||||
MathML: {
|
||||
extensions: ['content-mathml.js']
|
||||
},
|
||||
// Center justify equations in code and markdown cells. Elsewhere
|
||||
// we use CSS to left justify single line equations in code cells.
|
||||
displayAlign: 'center',
|
||||
"HTML-CSS": {
|
||||
availableFonts: [],
|
||||
imageFont: null,
|
||||
preferredFont: null,
|
||||
webFont: "STIX-Web",
|
||||
styles: {'.MathJax_Display': {"margin": 0}},
|
||||
linebreaks: { automatic: true }
|
||||
},
|
||||
});
|
||||
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 : i18n.msg.sprintf(i18n.msg._("Failed to retrieve MathJax from '%s'",window.mathjax_url)),
|
||||
body : $("<p/>").addClass('dialog').text(
|
||||
i18n.msg._("Math/LaTeX rendering will be disabled.")
|
||||
),
|
||||
buttons : {
|
||||
OK : {class: "btn-danger"}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Some magic for deferring mathematical expressions to MathJax
|
||||
// by hiding them from the Markdown parser.
|
||||
// Some of the code here is adapted with permission from Davide Cervone
|
||||
// under the terms of the Apache2 license governing the MathJax project.
|
||||
// Other minor modifications are also due to StackExchange and are used with
|
||||
// permission.
|
||||
|
||||
// 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;
|
||||
|
||||
// The math is in blocks i through j, so
|
||||
// collect it into one block and clear the others.
|
||||
// Replace &, <, and > by named entities.
|
||||
// For IE, put <br> at the ends of comments since IE removes \n.
|
||||
// Clear the current math positions and store the index of the
|
||||
// math, then push the math string onto the storage array.
|
||||
// The preProcess function is called on all blocks if it has been passed in
|
||||
var process_math = function (i, j, pre_process, math, blocks) {
|
||||
var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&") // use HTML entity for &
|
||||
.replace(/</g, "<") // use HTML entity for <
|
||||
.replace(/>/g, ">") // use HTML entity for >
|
||||
;
|
||||
if (utils.browser === 'msie') {
|
||||
block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
|
||||
}
|
||||
while (j > i) {
|
||||
blocks[j] = "";
|
||||
j--;
|
||||
}
|
||||
blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
|
||||
if (pre_process){
|
||||
block = pre_process(block);
|
||||
}
|
||||
math.push(block);
|
||||
return blocks;
|
||||
};
|
||||
|
||||
// Break up the text into its component parts and search
|
||||
// through them for math delimiters, braces, linebreaks, etc.
|
||||
// Math delimiters must match and braces must balance.
|
||||
// Don't allow math to pass through a double linebreak
|
||||
// (which will be a paragraph).
|
||||
//
|
||||
var remove_math = function (text) {
|
||||
var math = []; // stores math strings for later
|
||||
var start;
|
||||
var end;
|
||||
var last;
|
||||
var braces;
|
||||
|
||||
// Except for extreme edge cases, this should catch precisely those pieces of the markdown
|
||||
// source that will later be turned into code spans. While MathJax will not TeXify code spans,
|
||||
// we still have to consider them at this point; the following issue has happened several times:
|
||||
//
|
||||
// `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
|
||||
|
||||
var hasCodeSpans = /`/.test(text),
|
||||
de_tilde;
|
||||
if (hasCodeSpans) {
|
||||
var tilde = function (wholematch) {
|
||||
return wholematch.replace(/\$/g, "~D");
|
||||
}
|
||||
text = text.replace(/~/g, "~T")
|
||||
.replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, tilde)
|
||||
.replace(/^\s{0,3}(`{3,})(.|\n)*?\1/gm, tilde);
|
||||
de_tilde = function (text) {
|
||||
return text.replace(/~([TD])/g, function (wholematch, character) {
|
||||
return { T: "~", D: "$" }[character];
|
||||
});
|
||||
};
|
||||
} else {
|
||||
de_tilde = function (text) { return text; };
|
||||
}
|
||||
|
||||
var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
|
||||
|
||||
for (var i = 1, m = blocks.length; i < m; i += 2) {
|
||||
var block = blocks[i];
|
||||
if (block.charAt(0) === "@") {
|
||||
//
|
||||
// Things that look like our math markers will get
|
||||
// stored and then retrieved along with the math.
|
||||
//
|
||||
blocks[i] = "@@" + math.length + "@@";
|
||||
math.push(block);
|
||||
}
|
||||
else if (start) {
|
||||
//
|
||||
// If we are in math, look for the end delimiter,
|
||||
// but don't go past double line breaks, and
|
||||
// and balance braces within the math.
|
||||
//
|
||||
if (block === end) {
|
||||
if (braces) {
|
||||
last = i;
|
||||
}
|
||||
else {
|
||||
blocks = process_math(start, i, de_tilde, math, blocks);
|
||||
start = null;
|
||||
end = null;
|
||||
last = null;
|
||||
}
|
||||
}
|
||||
else if (block.match(/\n.*\n/)) {
|
||||
if (last) {
|
||||
i = last;
|
||||
blocks = process_math(start, i, de_tilde, math, blocks);
|
||||
}
|
||||
start = null;
|
||||
end = null;
|
||||
last = null;
|
||||
braces = 0;
|
||||
}
|
||||
else if (block === "{") {
|
||||
braces++;
|
||||
}
|
||||
else if (block === "}" && braces) {
|
||||
braces--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//
|
||||
// Look for math start delimiters and when
|
||||
// found, set up the end delimiter.
|
||||
//
|
||||
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);
|
||||
braces = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last) {
|
||||
blocks = process_math(start, last, de_tilde, math, blocks);
|
||||
start = null;
|
||||
end = null;
|
||||
last = null;
|
||||
}
|
||||
return [de_tilde(blocks.join("")), math];
|
||||
};
|
||||
|
||||
//
|
||||
// Put back the math strings that were saved,
|
||||
// and clear the math array (no need to keep it around).
|
||||
//
|
||||
var replace_math = function (text, math) {
|
||||
//
|
||||
// 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;
|
||||
};
|
||||
|
||||
var mathjaxutils = {
|
||||
init : init,
|
||||
remove_math : remove_math,
|
||||
replace_math : replace_math
|
||||
};
|
||||
|
||||
return mathjaxutils;
|
||||
});
|
||||
@ -1,83 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
|
||||
var Jupyter = Jupyter || {};
|
||||
|
||||
var jprop = function(name, module_path){
|
||||
Object.defineProperty(Jupyter, name, {
|
||||
get: function() {
|
||||
console.warn('accessing `'+name+'` is deprecated. Use `requirejs("'+module_path+'")`');
|
||||
return requirejs(module_path);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
}
|
||||
|
||||
var jglobal = function(name, module_path){
|
||||
Object.defineProperty(Jupyter, name, {
|
||||
get: function() {
|
||||
console.warn('accessing `'+name+'` is deprecated. Use `requirejs("'+module_path+'").'+name+'`');
|
||||
return requirejs(module_path)[name];
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
}
|
||||
|
||||
define(function(){
|
||||
"use strict";
|
||||
|
||||
// expose modules
|
||||
|
||||
jprop('utils','base/js/utils')
|
||||
jprop('mathjaxutils','base/js/mathjaxutils');
|
||||
|
||||
//Jupyter.load_extensions = Jupyter.utils.load_extensions;
|
||||
//
|
||||
jprop('security','base/js/security');
|
||||
jprop('keyboard','base/js/keyboard');
|
||||
jprop('dialog','base/js/dialog');
|
||||
|
||||
|
||||
//// exposed constructors
|
||||
jglobal('CommManager','services/kernels/comm')
|
||||
jglobal('Comm','services/kernels/comm')
|
||||
|
||||
jglobal('NotificationWidget','base/js/notificationwidget');
|
||||
jglobal('Kernel','services/kernels/kernel');
|
||||
jglobal('Session','services/sessions/session');
|
||||
jglobal('LoginWidget','auth/js/loginwidget');
|
||||
jglobal('Page','base/js/page');
|
||||
|
||||
// notebook
|
||||
jglobal('TextCell','notebook/js/textcell');
|
||||
jglobal('OutputArea','notebook/js/outputarea');
|
||||
jglobal('KeyboardManager','notebook/js/keyboardmanager');
|
||||
jglobal('Completer','notebook/js/completer');
|
||||
jglobal('Notebook','notebook/js/notebook');
|
||||
jglobal('Tooltip','notebook/js/tooltip');
|
||||
jglobal('Toolbar','notebook/js/toolbar');
|
||||
jglobal('SaveWidget','notebook/js/savewidget');
|
||||
jglobal('Pager','notebook/js/pager');
|
||||
jglobal('QuickHelp','notebook/js/quickhelp');
|
||||
jglobal('MarkdownCell','notebook/js/textcell');
|
||||
jglobal('RawCell','notebook/js/textcell');
|
||||
jglobal('Cell','notebook/js/cell');
|
||||
jglobal('MainToolBar','notebook/js/maintoolbar');
|
||||
jglobal('NotebookNotificationArea','notebook/js/notificationarea');
|
||||
jglobal('NotebookTour', 'notebook/js/tour');
|
||||
jglobal('MenuBar', 'notebook/js/menubar');
|
||||
|
||||
// tree
|
||||
jglobal('SessionList','tree/js/sessionlist');
|
||||
|
||||
Jupyter.version = "6.4.13.dev0";
|
||||
Jupyter._target = '_blank';
|
||||
|
||||
return Jupyter;
|
||||
});
|
||||
|
||||
// deprecated since 4.0, remove in 5+
|
||||
var IPython = Jupyter;
|
||||
@ -1,83 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/notificationwidget',
|
||||
], function($, notificationwidget) {
|
||||
"use strict";
|
||||
|
||||
// store reference to the NotificationWidget class
|
||||
var NotificationWidget = notificationwidget.NotificationWidget;
|
||||
|
||||
/**
|
||||
* Construct the NotificationArea object. Options are:
|
||||
* events: $(Events) instance
|
||||
* save_widget: SaveWidget instance
|
||||
* notebook: Notebook instance
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} selector - a jQuery selector string for the
|
||||
* notification area element
|
||||
* @param {Object} [options] - a dictionary of keyword arguments.
|
||||
*/
|
||||
var NotificationArea = function (selector, options) {
|
||||
this.selector = selector;
|
||||
this.events = options.events;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
}
|
||||
this.widget_dict = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a widget by name, creating it if it doesn't exist.
|
||||
*
|
||||
* @method widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.widget = function (name) {
|
||||
if (this.widget_dict[name] === undefined) {
|
||||
return this.new_notification_widget(name);
|
||||
}
|
||||
return this.get_widget(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a widget by name, throwing an error if it doesn't exist.
|
||||
*
|
||||
* @method get_widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.get_widget = function (name) {
|
||||
if(this.widget_dict[name] === undefined) {
|
||||
throw new Error('no widgets with this name');
|
||||
}
|
||||
return this.widget_dict[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new notification widget with the given name. The
|
||||
* widget must not already exist.
|
||||
*
|
||||
* @method new_notification_widget
|
||||
* @param {string} name - the widget name
|
||||
*/
|
||||
NotificationArea.prototype.new_notification_widget = function (name) {
|
||||
if (this.widget_dict[name] !== undefined) {
|
||||
throw new Error('widget with that name already exists!');
|
||||
}
|
||||
|
||||
// create the element for the notification widget and add it
|
||||
// to the notification aread element
|
||||
var div = $('<div/>').attr('id', 'notification_' + name);
|
||||
$(this.selector).append(div);
|
||||
|
||||
// create the widget object and return it
|
||||
this.widget_dict[name] = new NotificationWidget('#notification_' + name);
|
||||
return this.widget_dict[name];
|
||||
};
|
||||
|
||||
return {'NotificationArea': NotificationArea};
|
||||
});
|
||||
@ -1,168 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['jquery'], function($) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Construct a NotificationWidget object.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} selector - a jQuery selector string for the
|
||||
* notification widget element
|
||||
*/
|
||||
var NotificationWidget = function (selector) {
|
||||
this.selector = selector;
|
||||
this.timeout = null;
|
||||
this.busy = false;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.style();
|
||||
}
|
||||
this.element.hide();
|
||||
this.inner = $('<span/>');
|
||||
this.element.append(this.inner);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the 'notification_widget' CSS class to the widget element.
|
||||
*
|
||||
* @method style
|
||||
*/
|
||||
NotificationWidget.prototype.style = function () {
|
||||
// use explicit bootstrap classes here,
|
||||
// because multiple inheritance in LESS doesn't work
|
||||
// for this particular combination
|
||||
this.element.addClass('notification_widget btn btn-xs navbar-btn');
|
||||
};
|
||||
|
||||
/**
|
||||
* hide the widget and empty the text
|
||||
**/
|
||||
NotificationWidget.prototype.hide = function () {
|
||||
var that = this;
|
||||
this.element.fadeOut(100, function(){that.inner.text('');});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the notification widget message to display for a certain
|
||||
* amount of time (timeout). The widget will be shown forever if
|
||||
* timeout is <= 0 or undefined. If the widget is clicked while it
|
||||
* is still displayed, execute an optional callback
|
||||
* (click_callback). If the callback returns false, it will
|
||||
* prevent the notification from being dismissed.
|
||||
*
|
||||
* Options:
|
||||
* class - CSS class name for styling
|
||||
* icon - CSS class name for the widget icon
|
||||
* title - HTML title attribute for the widget
|
||||
*
|
||||
* @method set_message
|
||||
* @param {string} msg - The notification to display
|
||||
* @param {integer} [timeout] - The amount of time in milliseconds to display the widget
|
||||
* @param {function} [click_callback] - The function to run when the widget is clicked
|
||||
* @param {Object} [options] - Additional options
|
||||
*/
|
||||
NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// unbind potential previous callback
|
||||
this.element.unbind('click');
|
||||
this.inner.attr('class', options.icon);
|
||||
this.inner.attr('title', options.title);
|
||||
this.inner.text(msg);
|
||||
this.element.fadeIn(100);
|
||||
|
||||
// reset previous set style
|
||||
this.element.removeClass();
|
||||
this.style();
|
||||
if (options.class) {
|
||||
this.element.addClass(options.class);
|
||||
}
|
||||
|
||||
// clear previous timer
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
|
||||
// set the timer if a timeout is given
|
||||
var that = this;
|
||||
if (timeout !== undefined && timeout >= 0) {
|
||||
this.timeout = setTimeout(function () {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
that.element.unbind('click');
|
||||
that.timeout = null;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// if no click callback assume we will just dismiss the notification
|
||||
if (click_callback === undefined) {
|
||||
click_callback = function(){return true};
|
||||
}
|
||||
// on click, remove widget if click callback say so
|
||||
// and unbind click event.
|
||||
this.element.click(function () {
|
||||
if (click_callback() !== false) {
|
||||
that.element.fadeOut(100, function () {that.inner.text('');});
|
||||
that.element.unbind('click');
|
||||
}
|
||||
if (that.timeout !== null) {
|
||||
clearTimeout(that.timeout);
|
||||
that.timeout = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Display an information message (styled with the 'info'
|
||||
* class). Arguments are the same as in set_message. Default
|
||||
* timeout is 3500 milliseconds.
|
||||
*
|
||||
* @method info
|
||||
*/
|
||||
NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' info';
|
||||
timeout = timeout || 3500;
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a warning message (styled with the 'warning'
|
||||
* class). Arguments are the same as in set_message. Messages are
|
||||
* sticky by default.
|
||||
*
|
||||
* @method warning
|
||||
*/
|
||||
NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' warning';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a danger message (styled with the 'danger'
|
||||
* class). Arguments are the same as in set_message. Messages are
|
||||
* sticky by default.
|
||||
*
|
||||
* @method danger
|
||||
*/
|
||||
NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
|
||||
options = options || {};
|
||||
options.class = options.class + ' danger';
|
||||
this.set_message(msg, timeout, click_callback, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text of the widget message.
|
||||
*
|
||||
* @method get_message
|
||||
* @return {string} - the message text
|
||||
*/
|
||||
NotificationWidget.prototype.get_message = function () {
|
||||
return this.inner.html();
|
||||
};
|
||||
|
||||
return {'NotificationWidget': NotificationWidget};
|
||||
});
|
||||
@ -1,78 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/events',
|
||||
], function($, events){
|
||||
"use strict";
|
||||
|
||||
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 || 'div#header');
|
||||
this.site_div_element = $(site_div_selector || 'div#site');
|
||||
|
||||
this.bind_events();
|
||||
};
|
||||
|
||||
Page.prototype.bind_events = function () {
|
||||
// resize site on:
|
||||
// - window resize
|
||||
// - header change
|
||||
// - page load
|
||||
var _handle_resize = $.proxy(this._resize_site, this);
|
||||
|
||||
$(window).resize(_handle_resize);
|
||||
|
||||
// On document ready, resize codemirror.
|
||||
$(document).ready(_handle_resize);
|
||||
events.on('resize-header.Page', _handle_resize);
|
||||
};
|
||||
|
||||
Page.prototype.show = function () {
|
||||
/**
|
||||
* The header and site divs start out hidden to prevent FLOUC.
|
||||
* Main scripts should call this method after styling everything.
|
||||
*/
|
||||
this.show_header();
|
||||
this.show_site();
|
||||
};
|
||||
|
||||
Page.prototype.show_header = function () {
|
||||
/**
|
||||
* The header and site divs start out hidden to prevent FLOUC.
|
||||
* Main scripts should call this method after styling everything.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
this.site_div_element.css('display', 'block');
|
||||
this._resize_site();
|
||||
};
|
||||
|
||||
Page.prototype._resize_site = function(e) {
|
||||
/**
|
||||
* Update the site's size.
|
||||
*/
|
||||
|
||||
// In the case an event is passed in, only trigger if the event does
|
||||
// *not* have a target DOM node (i.e., it is not bubbling up). See
|
||||
// https://bugs.jquery.com/ticket/9841#comment:8
|
||||
if (!(e && e.target && e.target.tagName)) {
|
||||
$('div#site').height($(window).height() - $('#header').height());
|
||||
}
|
||||
};
|
||||
|
||||
return {'Page': Page};
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Define an object to attach promises to for one-time events.
|
||||
|
||||
define(['base/js/events', 'base/js/namespace'], function(events, Jupyter) {
|
||||
"use strict";
|
||||
|
||||
// Promise to be resolved when the application is initialized.
|
||||
// The value is the name of the app on the current page.
|
||||
var app_initialized = new Promise(function(resolve, reject) {
|
||||
events.on('app_initialized.NotebookApp', function() {
|
||||
resolve('NotebookApp');
|
||||
});
|
||||
events.on('app_initialized.DashboardApp', function() {
|
||||
resolve('DashboardApp');
|
||||
});
|
||||
});
|
||||
|
||||
var promises = {
|
||||
app_initialized: app_initialized
|
||||
};
|
||||
Jupyter.promises = promises;
|
||||
|
||||
return promises;
|
||||
});
|
||||
@ -1,51 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'components/sanitizer/index',
|
||||
], function($, sanitizer) {
|
||||
"use strict";
|
||||
|
||||
var noop = function (x) { return x; };
|
||||
var defaultSanitizer = sanitizer.defaultSanitizer;
|
||||
|
||||
var sanitize_html = function (html, allow_css) {
|
||||
/**
|
||||
* sanitize HTML
|
||||
* if allow_css is true (default: false), CSS is sanitized as well.
|
||||
* otherwise, CSS elements and attributes are simply removed.
|
||||
*/
|
||||
const options = {};
|
||||
if (!allow_css) {
|
||||
options.allowedStyles = {};
|
||||
}
|
||||
return defaultSanitizer.sanitize(html, options);
|
||||
};
|
||||
|
||||
var sanitize_html_and_parse = function (html, allow_css) {
|
||||
/**
|
||||
* Sanitize HTML and parse it safely using jQuery.
|
||||
*
|
||||
* This disable's jQuery's html 'prefilter', which can make invalid
|
||||
* HTML valid after the sanitizer has checked it.
|
||||
*
|
||||
* Returns an array of DOM nodes.
|
||||
*/
|
||||
var sanitized_html = sanitize_html(html, allow_css);
|
||||
var prev_htmlPrefilter = $.htmlPrefilter;
|
||||
$.htmlPrefilter = function(html) {return html;}; // Don't modify HTML
|
||||
try {
|
||||
return $.parseHTML(sanitized_html);
|
||||
} finally {
|
||||
$.htmlPrefilter = prev_htmlPrefilter; // Set it back again
|
||||
}
|
||||
};
|
||||
|
||||
var security = {
|
||||
sanitize_html_and_parse: sanitize_html_and_parse,
|
||||
sanitize_html: sanitize_html
|
||||
};
|
||||
|
||||
return security;
|
||||
});
|
||||
@ -1,24 +0,0 @@
|
||||
div.error {
|
||||
margin: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.error > h1 {
|
||||
font-size: 500%;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
div.error > p {
|
||||
font-size: 200%;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
div.traceback-wrapper {
|
||||
text-align: left;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
pre.traceback {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@ -1,269 +0,0 @@
|
||||
|
||||
/* Flexible box model classes */
|
||||
/* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */
|
||||
|
||||
/* This file is a compatibility layer. It allows the usage of flexible box
|
||||
model layouts accross multiple browsers, including older browsers. The newest,
|
||||
universal implementation of the flexible box model is used when available (see
|
||||
`Modern browsers` comments below). Browsers that are known to implement this
|
||||
new spec completely include:
|
||||
|
||||
Firefox 28.0+
|
||||
Chrome 29.0+
|
||||
Internet Explorer 11+
|
||||
Opera 17.0+
|
||||
|
||||
Browsers not listed, including Safari, are supported via the styling under the
|
||||
`Old browsers` comments below.
|
||||
*/
|
||||
|
||||
|
||||
.hbox {
|
||||
/* Old browsers */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-align: stretch;
|
||||
|
||||
display: -moz-box;
|
||||
-moz-box-orient: horizontal;
|
||||
-moz-box-align: stretch;
|
||||
|
||||
display: box;
|
||||
box-orient: horizontal;
|
||||
box-align: stretch;
|
||||
|
||||
/* Modern browsers */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hbox > * {
|
||||
/* Old browsers */
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
|
||||
/* Modern browsers */
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.vbox {
|
||||
/* Old browsers */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-align: stretch;
|
||||
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-align: stretch;
|
||||
|
||||
display: box;
|
||||
box-orient: vertical;
|
||||
box-align: stretch;
|
||||
|
||||
/* Modern browsers */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.vbox > * {
|
||||
/* Old browsers */
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
|
||||
/* Modern browsers */
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.hbox.reverse,
|
||||
.vbox.reverse,
|
||||
.reverse {
|
||||
/* Old browsers */
|
||||
-webkit-box-direction: reverse;
|
||||
-moz-box-direction: reverse;
|
||||
box-direction: reverse;
|
||||
|
||||
/* Modern browsers */
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.hbox.box-flex0,
|
||||
.vbox.box-flex0,
|
||||
.box-flex0 {
|
||||
/* Old browsers */
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
|
||||
/* Modern browsers */
|
||||
flex: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.hbox.box-flex1,
|
||||
.vbox.box-flex1,
|
||||
.box-flex1 {
|
||||
/* Old browsers */
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
|
||||
/* Modern browsers */
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hbox.box-flex,
|
||||
.vbox.box-flex,
|
||||
.box-flex {
|
||||
/* Old browsers */
|
||||
.box-flex1();
|
||||
}
|
||||
|
||||
.hbox.box-flex2,
|
||||
.vbox.box-flex2,
|
||||
.box-flex2 {
|
||||
/* Old browsers */
|
||||
-webkit-box-flex: 2;
|
||||
-moz-box-flex: 2;
|
||||
box-flex: 2;
|
||||
|
||||
/* Modern browsers */
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.box-group1 {
|
||||
/* Deprecated */
|
||||
-webkit-box-flex-group: 1;
|
||||
-moz-box-flex-group: 1;
|
||||
box-flex-group: 1;
|
||||
}
|
||||
|
||||
.box-group2 {
|
||||
/* Deprecated */
|
||||
-webkit-box-flex-group: 2;
|
||||
-moz-box-flex-group: 2;
|
||||
box-flex-group: 2;
|
||||
}
|
||||
|
||||
.hbox.start,
|
||||
.vbox.start,
|
||||
.start {
|
||||
/* Old browsers */
|
||||
-webkit-box-pack: start;
|
||||
-moz-box-pack: start;
|
||||
box-pack: start;
|
||||
|
||||
/* Modern browsers */
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.hbox.end,
|
||||
.vbox.end,
|
||||
.end {
|
||||
/* Old browsers */
|
||||
-webkit-box-pack: end;
|
||||
-moz-box-pack: end;
|
||||
box-pack: end;
|
||||
|
||||
/* Modern browsers */
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.hbox.center,
|
||||
.vbox.center,
|
||||
.center {
|
||||
/* Old browsers */
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
box-pack: center;
|
||||
|
||||
/* Modern browsers */
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hbox.baseline,
|
||||
.vbox.baseline,
|
||||
.baseline {
|
||||
/* Old browsers */
|
||||
-webkit-box-pack: baseline;
|
||||
-moz-box-pack: baseline;
|
||||
box-pack: baseline;
|
||||
|
||||
/* Modern browsers */
|
||||
justify-content: baseline;
|
||||
}
|
||||
|
||||
.hbox.stretch,
|
||||
.vbox.stretch,
|
||||
.stretch {
|
||||
/* Old browsers */
|
||||
-webkit-box-pack: stretch;
|
||||
-moz-box-pack: stretch;
|
||||
box-pack: stretch;
|
||||
|
||||
/* Modern browsers */
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.hbox.align-start,
|
||||
.vbox.align-start,
|
||||
.align-start {
|
||||
/* Old browsers */
|
||||
-webkit-box-align: start;
|
||||
-moz-box-align: start;
|
||||
box-align: start;
|
||||
|
||||
/* Modern browsers */
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hbox.align-end,
|
||||
.vbox.align-end,
|
||||
.align-end {
|
||||
/* Old browsers */
|
||||
-webkit-box-align: end;
|
||||
-moz-box-align: end;
|
||||
box-align: end;
|
||||
|
||||
/* Modern browsers */
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.hbox.align-center,
|
||||
.vbox.align-center,
|
||||
.align-center {
|
||||
/* Old browsers */
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
box-align: center;
|
||||
|
||||
/* Modern browsers */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hbox.align-baseline,
|
||||
.vbox.align-baseline,
|
||||
.align-baseline {
|
||||
/* Old browsers */
|
||||
-webkit-box-align: baseline;
|
||||
-moz-box-align: baseline;
|
||||
box-align: baseline;
|
||||
|
||||
/* Modern browsers */
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.hbox.align-stretch,
|
||||
.vbox.align-stretch,
|
||||
.align-stretch {
|
||||
/* Old browsers */
|
||||
-webkit-box-align: stretch;
|
||||
-moz-box-align: stretch;
|
||||
box-align: stretch;
|
||||
|
||||
/* Modern browsers */
|
||||
align-items: stretch;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
// Mixin CSS classes
|
||||
|
||||
.border-box-sizing {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
.corner-all {
|
||||
border-radius: @border-radius-base;
|
||||
}
|
||||
|
||||
.border-radius(@radius) {
|
||||
border-radius: @radius;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding: 0px;
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
/**
|
||||
* Primary styles
|
||||
*
|
||||
* Author: Jupyter Development Team
|
||||
*/
|
||||
|
||||
|
||||
body {
|
||||
background-color: @body-bg;
|
||||
/* This makes sure that the body covers the entire window and needs to
|
||||
be in a different element than the display: box in wrapper below */
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
body > #header {
|
||||
/* Initially hidden to prevent FLOUC */
|
||||
display: none;
|
||||
background-color: @body-bg;
|
||||
|
||||
/* Display over codemirror */
|
||||
position: relative;
|
||||
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();
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: @navbar-default-border;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
#header-spacer {
|
||||
width: 100%;
|
||||
visibility: hidden;
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#ipython_notebook {
|
||||
padding-left: 0px;
|
||||
padding-top: (@navbar-height - @logo_height) / 2;
|
||||
padding-bottom: (@navbar-height - @logo_height) / 2;
|
||||
}
|
||||
|
||||
[dir="rtl"] #ipython_notebook {
|
||||
margin-right: 10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] #ipython_notebook.pull-left {
|
||||
.pull-right();
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#noscript {
|
||||
width: auto;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#ipython_notebook img {
|
||||
height: @logo_height;
|
||||
}
|
||||
|
||||
#site {
|
||||
width: 100%;
|
||||
display: none;
|
||||
.border-box-sizing();
|
||||
overflow: auto;
|
||||
@media print {
|
||||
// force auto-height on print (overrides manual resizing in live view)
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smaller buttons */
|
||||
.ui-button .ui-button-text {
|
||||
padding: 0.2em 0.8em;
|
||||
font-size: 77%;
|
||||
}
|
||||
|
||||
input.ui-button {
|
||||
padding: 0.3em 0.9em;
|
||||
}
|
||||
|
||||
span#kernel_logo_widget {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
span#login_widget {
|
||||
float: right;
|
||||
}
|
||||
|
||||
[dir="rtl"] span#login_widget {
|
||||
float: left;
|
||||
}
|
||||
|
||||
span#login_widget > .button,
|
||||
#logout, #shutdown
|
||||
{
|
||||
.btn-default();
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.nav-header {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
#header > span {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
// class for stretching dialogs to fill the screen
|
||||
.modal_stretch .modal-dialog {
|
||||
.vbox();
|
||||
min-height: 80vh;
|
||||
.modal-body {
|
||||
// ~"foo" is to avoid less turning this into a weird value
|
||||
max-height: calc(~"100vh - 200px");
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-sm-min) {
|
||||
.modal .modal-dialog {
|
||||
width: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
// less mixin to be sure to add the right class to get icons with font awesome.
|
||||
.icon(@ico){
|
||||
.fa();
|
||||
content: @ico;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-sm-min) {
|
||||
select.form-control {
|
||||
margin-left: @padding-base-horizontal;
|
||||
margin-right: @padding-base-horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
/* rtl fixes for the error, connecting, and renaming window */
|
||||
|
||||
[dir="rtl"] .modal-footer {
|
||||
text-align : left !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .close {
|
||||
float : left;
|
||||
}
|
||||
|
||||
[dir="rtl"] .fa-step-forward::before {
|
||||
content: "\f048";
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
/*!
|
||||
*
|
||||
* IPython base
|
||||
*
|
||||
*/
|
||||
@import "variables.less";
|
||||
@import "mixins.less";
|
||||
@import "flexbox.less";
|
||||
@import "error.less";
|
||||
@ -1,62 +0,0 @@
|
||||
// Our customizations to bootstrap go here.
|
||||
|
||||
@black: #000;
|
||||
@text-color: @black;
|
||||
@font-size-base: 13px;
|
||||
@font-family-monospace: monospace; // to allow user to customize their fonts
|
||||
@navbar-height: 30px;
|
||||
@breadcrumb-color: darken(@border_color, 30%);
|
||||
@blockquote-font-size: inherit;
|
||||
@modal-inner-padding: 15px;
|
||||
@grid-float-breakpoint: 541px;
|
||||
@screen-xs: 540px;
|
||||
@logo_height: 28px;
|
||||
@border-radius-small: 1px;
|
||||
@border-radius-base: 2px;
|
||||
@border-radius-large: 3px;
|
||||
@grid-gutter-width: 0px;
|
||||
@kbd-color: #888;
|
||||
@kbd-bg: transparent;
|
||||
|
||||
@icon-font-path: "../components/bootstrap/fonts/";
|
||||
|
||||
// Disable modal slide-in from top animation.
|
||||
.modal {
|
||||
&.fade .modal-dialog {
|
||||
.translate(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the default code color.
|
||||
code {
|
||||
color: @black; // default code color in bootstrap is #d14 (crimson / amaranth)
|
||||
}
|
||||
|
||||
// Override bootstrap pre element styling.
|
||||
pre {
|
||||
// bootstrap has pre defaults that we don't want to inherit.
|
||||
// start pre tag defaults based on the surrounding context instead.
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
// Disable bold labels in BS3
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
// Our own global variables for all pages go here
|
||||
@global-shadow: 0px 0px 12px 1px rgba(87, 87, 87, 0.2);
|
||||
@global-shadow-dark: 0px 0px 12px 1px rgba(87, 87, 87, 0.4);
|
||||
@page-header-padding: 20px;
|
||||
/* Make the page background atleast 100% the height of the view port */
|
||||
@page-backdrop-height: 100vh;
|
||||
/* Make the page itself atleast 70% the height of the view port */
|
||||
@page-min-height: 0;
|
||||
@page-backdrop-color: #EEE;
|
||||
@page-color: @body-bg;
|
||||
@page-padding: 15px;
|
||||
|
||||
// preven container size to jump from 768px to 720px
|
||||
// when window width go from 768 to 769+
|
||||
@container-sm : @screen-sm-min;
|
||||
@ -1,45 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['bidi/numericshaping'], function(numericshaping) {
|
||||
'use strict';
|
||||
|
||||
var shaperType = '';
|
||||
|
||||
var _uiLang = function() {
|
||||
return navigator.language.toLowerCase();
|
||||
};
|
||||
|
||||
var _loadLocale = function() {
|
||||
if (_isMirroringEnabled()) {
|
||||
document.body.dir = 'rtl';
|
||||
}
|
||||
|
||||
requirejs(['moment'], function (moment) {
|
||||
console.log('Loaded moment locale', moment.locale(_uiLang()));
|
||||
});
|
||||
|
||||
shaperType = _uiLang().split('-')[0] == 'ar' ? 'national' : 'defaultNumeral';
|
||||
};
|
||||
|
||||
var _isMirroringEnabled = function() {
|
||||
return new RegExp('^(ar|ara|arc|ae|ave|egy|he|heb|nqo|pal|phn|sam|syc|syr|fa|per|fas|ckb|ur|urd)').test(_uiLang());
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value : the string to apply the bidi-support on it.
|
||||
* @param flag :indicates the type of bidi-support (Numeric-shaping ,Base-text-dir ).
|
||||
*/
|
||||
var _applyBidi = function(value /*, flag*/) {
|
||||
value = numericshaping.shapeNumerals(value, shaperType);
|
||||
return value;
|
||||
};
|
||||
|
||||
var bidi = {
|
||||
applyBidi: _applyBidi,
|
||||
isMirroringEnabled: _isMirroringEnabled,
|
||||
loadLocale: _loadLocale,
|
||||
};
|
||||
|
||||
return bidi;
|
||||
});
|
||||
@ -1,42 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([],
|
||||
function(bidi) {
|
||||
"use strict";
|
||||
|
||||
var regex = /([0-9])|([\u0660-\u0669])|([\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE]+)|([^0-9\u0660-\u0669\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE\u0600-\u0607\u0609-\u060A\u060C\u060E-\u061A\u064B-\u066C\u0670\u06D6-\u06E4\u06E7-\u06ED\u06F0-\u06F9\u08E4-\u08FF\uFD3E-\uFD3F\uFDD0-\uFDEF\uFDFD\uFEFF\u0000-\u0040\u005B-\u0060\u007B-\u007F\u0080-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02B9-\u02BA\u02C2-\u02CF\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u02FF\u2070\u2074-\u207E\u2080-\u208E\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A-\u213B\u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189\uA720-\uA721\uA788\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE]+)/g;
|
||||
|
||||
var shape = function(text, shaperType) {
|
||||
text = text.toString();
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
switch (shaperType) {
|
||||
case "defaultNumeral":
|
||||
return _shapeEuropean(text);
|
||||
case "national":
|
||||
return _shapeArabic(text);
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
var _shapeEuropean = function(text) {
|
||||
return text.replace(/[\u0660-\u0669]/g, function(c) {
|
||||
return c.charCodeAt(0) - 1632;
|
||||
});
|
||||
};
|
||||
|
||||
var _shapeArabic = function(text) {
|
||||
return text.replace(/[0-9]/g, function(c) {
|
||||
return String.fromCharCode(parseInt(c) + 1632);
|
||||
});
|
||||
};
|
||||
|
||||
var numericshaping = {
|
||||
shapeNumerals : shape
|
||||
};
|
||||
|
||||
return numericshaping;
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
/*
|
||||
Placeholder for custom user CSS
|
||||
|
||||
mainly to be overridden in profile/static/custom/custom.css
|
||||
|
||||
This will always be an empty file
|
||||
*/
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
// leave at least 2 line with only a star on it below, or doc generation fails
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Placeholder for custom user javascript
|
||||
* mainly to be overridden in profile/static/custom/custom.js
|
||||
* This will always be an empty file in IPython
|
||||
*
|
||||
* User could add any javascript in the `profile/static/custom/custom.js` file.
|
||||
* It will be executed by the ipython notebook at load time.
|
||||
*
|
||||
* Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
|
||||
*
|
||||
*
|
||||
* The object available at load time depend on the version of IPython in use.
|
||||
* there is no guaranties of API stability.
|
||||
*
|
||||
* The example below explain the principle, and might not be valid.
|
||||
*
|
||||
* Instances are created after the loading of this file and might need to be accessed using events:
|
||||
* define([
|
||||
* 'base/js/namespace',
|
||||
* 'base/js/promises'
|
||||
* ], function(IPython, promises) {
|
||||
* promises.app_initialized.then(function (appName) {
|
||||
* if (appName !== 'NotebookApp') return;
|
||||
* IPython.keyboard_manager....
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* __Example 1:__
|
||||
*
|
||||
* Create a custom button in toolbar that execute `%qtconsole` in kernel
|
||||
* and hence open a qtconsole attached to the same kernel as the current notebook
|
||||
*
|
||||
* define([
|
||||
* 'base/js/namespace',
|
||||
* 'base/js/promises'
|
||||
* ], function(IPython, promises) {
|
||||
* promises.app_initialized.then(function (appName) {
|
||||
* if (appName !== 'NotebookApp') return;
|
||||
* IPython.toolbar.add_buttons_group([
|
||||
* {
|
||||
* 'label' : 'run qtconsole',
|
||||
* 'icon' : 'icon-terminal', // select your icon from http://fortawesome.github.io/Font-Awesome/icons
|
||||
* 'callback': function () {
|
||||
* IPython.notebook.kernel.execute('%qtconsole')
|
||||
* }
|
||||
* }
|
||||
* // add more button here if needed.
|
||||
* ]);
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* __Example 2:__
|
||||
*
|
||||
* At the completion of the dashboard loading, load an unofficial javascript extension
|
||||
* that is installed in profile/static/custom/
|
||||
*
|
||||
* define([
|
||||
* 'base/js/events'
|
||||
* ], function(events) {
|
||||
* events.on('app_initialized.DashboardApp', function(){
|
||||
* requirejs(['custom/unofficial_extension.js'])
|
||||
* });
|
||||
* });
|
||||
*
|
||||
*
|
||||
*
|
||||
* @module IPython
|
||||
* @namespace IPython
|
||||
* @class customjs
|
||||
* @static
|
||||
*/
|
||||
@ -1,370 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/meta',
|
||||
'codemirror/addon/comment/comment',
|
||||
'codemirror/addon/dialog/dialog',
|
||||
'codemirror/addon/edit/closebrackets',
|
||||
'codemirror/addon/edit/matchbrackets',
|
||||
'codemirror/addon/search/searchcursor',
|
||||
'codemirror/addon/search/search',
|
||||
'codemirror/keymap/emacs',
|
||||
'codemirror/keymap/sublime',
|
||||
'codemirror/keymap/vim',
|
||||
],
|
||||
function(
|
||||
$,
|
||||
utils,
|
||||
i18n,
|
||||
dialog,
|
||||
CodeMirror
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
var Editor = function(selector, options) {
|
||||
var that = this;
|
||||
this.selector = selector;
|
||||
this.clean = false;
|
||||
this.contents = options.contents;
|
||||
this.events = options.events;
|
||||
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.last_modified = null;
|
||||
this._changed_on_disk_dialog = null;
|
||||
|
||||
this.codemirror = new CodeMirror($(this.selector)[0]);
|
||||
this.codemirror.on('changes', function(cm, changes){
|
||||
that._clean_state();
|
||||
});
|
||||
this.generation = -1;
|
||||
|
||||
// It appears we have to set commands on the CodeMirror class, not the
|
||||
// instance. I'd like to be wrong, but since there should only be one CM
|
||||
// instance on the page, this is good enough for now.
|
||||
CodeMirror.commands.save = $.proxy(this.save, this);
|
||||
|
||||
this.save_enabled = false;
|
||||
|
||||
this.config.loaded.then(function () {
|
||||
// load codemirror config
|
||||
var cfg = that.config.data.Editor || {};
|
||||
var cmopts = $.extend(true, {}, // true = recursive copy
|
||||
Editor.default_codemirror_options,
|
||||
cfg.codemirror_options || {}
|
||||
);
|
||||
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/>');
|
||||
$('.last_modified').before(this.clean_sel);
|
||||
this.clean_sel.addClass('dirty-indicator-dirty');
|
||||
};
|
||||
|
||||
// default CodeMirror options
|
||||
Editor.default_codemirror_options = {
|
||||
extraKeys: {
|
||||
"Cmd-Right": "goLineRight",
|
||||
"End": "goLineRight",
|
||||
"Cmd-Left": "goLineLeft",
|
||||
"Tab": "indentMore",
|
||||
"Shift-Tab" : "indentLess",
|
||||
"Cmd-/" : "toggleComment",
|
||||
"Ctrl-/" : "toggleComment",
|
||||
},
|
||||
indentUnit: 4,
|
||||
theme: "ipython",
|
||||
lineNumbers: true,
|
||||
lineWrapping: true
|
||||
};
|
||||
|
||||
Editor.prototype.load = function() {
|
||||
/** load the file */
|
||||
var that = this;
|
||||
var cm = this.codemirror;
|
||||
return this.contents.get(this.file_path, {type: 'file', format: 'text'})
|
||||
.then(function(model) {
|
||||
cm.setValue(model.content);
|
||||
|
||||
// Setting the file's initial value creates a history entry,
|
||||
// which we don't want.
|
||||
cm.clearHistory();
|
||||
that._set_mode_for_model(model);
|
||||
that.save_enabled = true;
|
||||
that.generation = cm.changeGeneration();
|
||||
that.events.trigger("file_loaded.Editor", model);
|
||||
that._clean_state();
|
||||
that.last_modified = new Date(model.last_modified);
|
||||
}).catch(
|
||||
function(error) {
|
||||
that.events.trigger("file_load_failed.Editor", error);
|
||||
console.warn('Error loading: ', error);
|
||||
cm.setValue("Error! " + error.message +
|
||||
"\nSaving disabled.\nSee Console for more details.");
|
||||
cm.setOption('readOnly','nocursor');
|
||||
that.save_enabled = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Editor.prototype._set_mode_for_model = function (model) {
|
||||
/** Set the CodeMirror mode based on the file model */
|
||||
|
||||
// Find and load the highlighting mode,
|
||||
// first by mime-type, then by file extension
|
||||
|
||||
var modeinfo;
|
||||
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") {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype.set_codemirror_mode = function (modeinfo) {
|
||||
/** set the codemirror mode from a modeinfo struct */
|
||||
var that = this;
|
||||
utils.requireCodeMirrorMode(modeinfo, function () {
|
||||
that.codemirror.setOption('mode', modeinfo.mime);
|
||||
that.events.trigger("mode_changed.Editor", modeinfo);
|
||||
}, function(err) {
|
||||
console.log('Error getting CodeMirror mode: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename the file.
|
||||
* @param {string} new_name
|
||||
* @return {Promise} promise that resolves when the file is renamed.
|
||||
*/
|
||||
Editor.prototype.rename = function (new_name) {
|
||||
/** rename the file */
|
||||
var that = this;
|
||||
var parent = utils.url_path_split(this.file_path)[0];
|
||||
var new_path = utils.url_path_join(parent, new_name);
|
||||
return this.contents.rename(this.file_path, new_path).then(
|
||||
function (model) {
|
||||
that.file_path = model.path;
|
||||
that.events.trigger('file_renamed.Editor', model);
|
||||
that.last_modified = new Date(model.last_modified);
|
||||
that._set_mode_for_model(model);
|
||||
that._clean_state();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save this file on the server.
|
||||
*
|
||||
* @param {boolean} check_last_modified - checks if file has been modified on disk
|
||||
* @return {Promise} - promise that resolves when the notebook is saved.
|
||||
*/
|
||||
Editor.prototype.save = function (check_last_modified) {
|
||||
/** save the file */
|
||||
if (!this.save_enabled) {
|
||||
console.log("Not saving, save disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// used to check for last modified saves
|
||||
if (check_last_modified === undefined) {
|
||||
check_last_modified = true;
|
||||
}
|
||||
|
||||
var model = {
|
||||
path: this.file_path,
|
||||
type: 'file',
|
||||
format: 'text',
|
||||
content: this.codemirror.getValue(),
|
||||
};
|
||||
var that = this;
|
||||
|
||||
var _save = function () {
|
||||
that.events.trigger("file_saving.Editor");
|
||||
return that.contents.save(that.file_path, model).then(function(data) {
|
||||
// record change generation for isClean
|
||||
that.generation = that.codemirror.changeGeneration();
|
||||
that.events.trigger("file_saved.Editor", data);
|
||||
that.last_modified = new Date(data.last_modified);
|
||||
that._clean_state();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the current working file, and checks if the file has been modified on disk. If so, it
|
||||
* creates & opens a modal that issues the user a warning and prompts them to overwrite the file.
|
||||
*
|
||||
* If it can't get the working file, it builds a new file and saves.
|
||||
*/
|
||||
if (check_last_modified) {
|
||||
return this.contents.get(that.file_path, {content: false}).then(
|
||||
function check_if_modified(data) {
|
||||
var last_modified = new Date(data.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) {
|
||||
// since the modal's event bindings are removed when destroyed, we reinstate
|
||||
// save & reload callbacks on the confirmation & reload buttons
|
||||
that._changed_on_disk_dialog.find('.save-confirm-btn').click(_save);
|
||||
that._changed_on_disk_dialog.find('.btn-warning').click(function () {window.location.reload()});
|
||||
|
||||
// redisplay existing dialog
|
||||
that._changed_on_disk_dialog.modal('show');
|
||||
} else {
|
||||
// create new dialog
|
||||
that._changed_on_disk_dialog = dialog.modal({
|
||||
keyboard_manager: that.keyboard_manager,
|
||||
title: i18n.msg._("File changed"),
|
||||
body: i18n.msg._("The 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',
|
||||
click: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
Cancel: {},
|
||||
Overwrite: {
|
||||
class: 'btn-danger save-confirm-btn',
|
||||
click: function () {
|
||||
_save();
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return _save();
|
||||
}
|
||||
}, function (error) {
|
||||
console.log(error);
|
||||
// maybe it has been deleted or renamed? Go ahead and save.
|
||||
return _save();
|
||||
})
|
||||
} else {
|
||||
return _save();
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype._clean_state = function(){
|
||||
var clean = this.codemirror.isClean(this.generation);
|
||||
if (clean === this.clean){
|
||||
return;
|
||||
} else {
|
||||
this.clean = clean;
|
||||
}
|
||||
if(clean){
|
||||
this.events.trigger("save_status_clean.Editor");
|
||||
this.clean_sel.attr('class','dirty-indicator-clean').attr('title','No changes to save');
|
||||
} else {
|
||||
this.events.trigger("save_status_dirty.Editor");
|
||||
this.clean_sel.attr('class','dirty-indicator-dirty').attr('title','Unsaved changes');
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype._set_codemirror_options = function (options) {
|
||||
// update codemirror options from a dict
|
||||
var codemirror = this.codemirror;
|
||||
$.map(options, function (value, opt) {
|
||||
if (value === null) {
|
||||
value = CodeMirror.defaults[opt];
|
||||
}
|
||||
codemirror.setOption(opt, value);
|
||||
});
|
||||
var that = this;
|
||||
};
|
||||
|
||||
Editor.prototype.update_codemirror_options = function (options) {
|
||||
/** update codemirror options locally and save changes in config */
|
||||
var that = this;
|
||||
this._set_codemirror_options(options);
|
||||
return this.config.update({
|
||||
Editor: {
|
||||
codemirror_options: options
|
||||
}
|
||||
}).then(
|
||||
that.events.trigger('config_changed.Editor', {config: that.config})
|
||||
);
|
||||
};
|
||||
|
||||
return {Editor: Editor};
|
||||
});
|
||||
@ -1,113 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
requirejs([
|
||||
'jquery',
|
||||
'contents',
|
||||
'base/js/namespace',
|
||||
'base/js/utils',
|
||||
'base/js/page',
|
||||
'base/js/events',
|
||||
'services/config',
|
||||
'edit/js/editor',
|
||||
'edit/js/menubar',
|
||||
'edit/js/savewidget',
|
||||
'edit/js/notificationarea',
|
||||
'bidi/bidi',
|
||||
'auth/js/loginwidget',
|
||||
], function(
|
||||
$,
|
||||
contents_service,
|
||||
IPython,
|
||||
utils,
|
||||
page,
|
||||
events,
|
||||
configmod,
|
||||
editmod,
|
||||
menubar,
|
||||
savewidget,
|
||||
notificationarea,
|
||||
bidi,
|
||||
loginwidget,
|
||||
){
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
requirejs(['custom/custom'], function() {});
|
||||
bidi.loadLocale();
|
||||
} catch(err) {
|
||||
console.log("Error loading custom.js from edition service. Continuing and logging");
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
page = new page.Page('div#header', 'div#site');
|
||||
|
||||
var base_url = utils.get_body_data('baseUrl');
|
||||
var file_path = utils.get_body_data('filePath');
|
||||
// This enables logout
|
||||
var login_widget = new loginwidget.LoginWidget('#login_widget', {
|
||||
base_url: base_url
|
||||
});
|
||||
var config = new configmod.ConfigSection('edit', {base_url: base_url});
|
||||
config.load();
|
||||
var common_config = new configmod.ConfigSection('common', {base_url: base_url});
|
||||
common_config.load();
|
||||
var contents = new contents_service.Contents({
|
||||
base_url: base_url,
|
||||
common_config: common_config
|
||||
});
|
||||
|
||||
var editor = new editmod.Editor('#texteditor-container', {
|
||||
base_url: base_url,
|
||||
events: events,
|
||||
contents: contents,
|
||||
file_path: file_path,
|
||||
config: config,
|
||||
});
|
||||
|
||||
// Make it available for debugging
|
||||
IPython.editor = editor;
|
||||
|
||||
var save_widget = new savewidget.SaveWidget('span#save_widget', {
|
||||
editor: editor,
|
||||
events: events,
|
||||
});
|
||||
|
||||
var menus = new menubar.MenuBar('#menubar', {
|
||||
base_url: base_url,
|
||||
editor: editor,
|
||||
events: events,
|
||||
save_widget: save_widget,
|
||||
});
|
||||
|
||||
var notification_area = new notificationarea.EditorNotificationArea(
|
||||
'#notification_area', {
|
||||
events: events,
|
||||
});
|
||||
editor.notification_area = notification_area;
|
||||
notification_area.init_notification_widgets();
|
||||
|
||||
utils.load_extensions_from_config(config);
|
||||
utils.load_extensions_from_config(common_config);
|
||||
editor.load();
|
||||
page.show();
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
if (editor.save_enabled && !editor.codemirror.isClean(editor.generation)) {
|
||||
return "Unsaved changes will be lost. Close anyway?";
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure the codemirror editor is sized appropriately.
|
||||
var _handle_resize = function() {
|
||||
var backdrop = $("#texteditor-backdrop");
|
||||
|
||||
// account for padding on the backdrop wrapper
|
||||
var padding = backdrop.outerHeight(true) - backdrop.height();
|
||||
$('div.CodeMirror').height($("#site").height() - padding);
|
||||
};
|
||||
$(window).resize(_handle_resize);
|
||||
|
||||
// On document ready, resize codemirror.
|
||||
$(document).ready(_handle_resize);
|
||||
});
|
||||
@ -1,167 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/utils',
|
||||
'base/js/dialog',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/meta',
|
||||
], function($, IPython, utils, dialog, CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var MenuBar = function (selector, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A MenuBar Class to generate the menubar of IPython notebook
|
||||
*
|
||||
* Parameters:
|
||||
* selector: string
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* codemirror: CodeMirror instance
|
||||
* contents: ContentManager instance
|
||||
* events: $(Events) instance
|
||||
* base_url : string
|
||||
* file_path : string
|
||||
*/
|
||||
options = options || {};
|
||||
this.base_url = options.base_url || utils.get_body_data("baseUrl");
|
||||
this.selector = selector;
|
||||
this.editor = options.editor;
|
||||
this.events = options.events;
|
||||
this.save_widget = options.save_widget;
|
||||
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.bind_events();
|
||||
}
|
||||
this._load_mode_menu();
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
MenuBar.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
var editor = that.editor;
|
||||
|
||||
// File
|
||||
this.element.find('#new-file').click(function () {
|
||||
var w = window.open(undefined, IPython._target);
|
||||
// Create a new file in the current directory
|
||||
var parent = utils.url_path_split(editor.file_path)[0];
|
||||
editor.contents.new_untitled(parent, {type: "file"}).then(
|
||||
function (data) {
|
||||
w.location = utils.url_path_join(
|
||||
that.base_url, 'edit', utils.encode_uri_components(data.path)
|
||||
);
|
||||
},
|
||||
function(error) {
|
||||
w.close();
|
||||
dialog.modal({
|
||||
title : 'Creating New File Failed',
|
||||
body : "The error was: " + error.message,
|
||||
buttons : {'OK' : {'class' : 'btn-primary'}}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
this.element.find('#save-file').click(function () {
|
||||
editor.save();
|
||||
});
|
||||
this.element.find('#rename-file').click(function () {
|
||||
that.save_widget.rename();
|
||||
});
|
||||
this.element.find('#download-file').click(function () {
|
||||
window.open(utils.url_path_join(
|
||||
that.base_url, 'files',
|
||||
utils.encode_uri_components(that.editor.file_path)
|
||||
) + '?download=1');
|
||||
});
|
||||
|
||||
// Edit
|
||||
this.element.find('#menu-find').click(function () {
|
||||
editor.codemirror.execCommand("find");
|
||||
});
|
||||
this.element.find('#menu-replace').click(function () {
|
||||
editor.codemirror.execCommand("replace");
|
||||
});
|
||||
this.element.find('#menu-keymap-default').click(function () {
|
||||
editor.update_codemirror_options({
|
||||
vimMode: false,
|
||||
keyMap: 'default'
|
||||
});
|
||||
});
|
||||
this.element.find('#menu-keymap-sublime').click(function () {
|
||||
editor.update_codemirror_options({
|
||||
vimMode: false,
|
||||
keyMap: 'sublime'
|
||||
});
|
||||
});
|
||||
this.element.find('#menu-keymap-emacs').click(function () {
|
||||
editor.update_codemirror_options({
|
||||
vimMode: false,
|
||||
keyMap: 'emacs'
|
||||
});
|
||||
});
|
||||
this.element.find('#menu-keymap-vim').click(function () {
|
||||
editor.update_codemirror_options({
|
||||
vimMode: true,
|
||||
keyMap: 'vim'
|
||||
});
|
||||
});
|
||||
|
||||
// View
|
||||
|
||||
this.element.find('#toggle_header').click(function (){
|
||||
$("#header-container").toggle();
|
||||
});
|
||||
|
||||
this.element.find('#menu-line-numbers').click(function () {
|
||||
var current = editor.codemirror.getOption('lineNumbers');
|
||||
var value = Boolean(1-current);
|
||||
editor.update_codemirror_options({lineNumbers: value});
|
||||
});
|
||||
|
||||
this.events.on("config_changed.Editor", function () {
|
||||
var keyMap = editor.codemirror.getOption('keyMap') || 'default';
|
||||
that.element.find(".selected-keymap").removeClass("selected-keymap");
|
||||
that.element.find("#menu-keymap-" + keyMap).addClass("selected-keymap");
|
||||
});
|
||||
|
||||
this.events.on("mode_changed.Editor", function (evt, modeinfo) {
|
||||
that.element.find("#current-mode")
|
||||
.text(modeinfo.name)
|
||||
.attr(
|
||||
'title',
|
||||
"The current language is " + modeinfo.name
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
MenuBar.prototype._load_mode_menu = function () {
|
||||
var list = this.element.find("#mode-menu");
|
||||
var editor = this.editor;
|
||||
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++) {
|
||||
var info = CodeMirror.modeInfo[i];
|
||||
list.append($("<li>").append(
|
||||
$("<a>").attr("href", "#")
|
||||
.text(info.name)
|
||||
.click(make_set_mode(info))
|
||||
.attr('title',
|
||||
"Set language to " + info.name
|
||||
)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
return {'MenuBar': MenuBar};
|
||||
});
|
||||
@ -1,29 +0,0 @@
|
||||
define([
|
||||
'base/js/notificationarea'
|
||||
], function(notificationarea) {
|
||||
"use strict";
|
||||
var NotificationArea = notificationarea.NotificationArea;
|
||||
|
||||
var EditorNotificationArea = function(selector, options) {
|
||||
NotificationArea.apply(this, [selector, options]);
|
||||
}
|
||||
|
||||
EditorNotificationArea.prototype = Object.create(NotificationArea.prototype);
|
||||
|
||||
/**
|
||||
* Initialize the default set of notification widgets.
|
||||
*
|
||||
* @method init_notification_widgets
|
||||
*/
|
||||
EditorNotificationArea.prototype.init_notification_widgets = function () {
|
||||
var that = this;
|
||||
var savew = this.new_notification_widget('save');
|
||||
|
||||
this.events.on("file_saved.Editor", function() {
|
||||
savew.set_message("File saved", 2000);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return {EditorNotificationArea: EditorNotificationArea};
|
||||
});
|
||||
@ -1,192 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/dialog',
|
||||
'base/js/keyboard',
|
||||
'moment',
|
||||
'bidi/bidi',
|
||||
], function($, utils, dialog, keyboard, moment, bidi) {
|
||||
"use strict";
|
||||
|
||||
var SaveWidget = function (selector, options) {
|
||||
this.editor = undefined;
|
||||
this.selector = selector;
|
||||
this.events = options.events;
|
||||
this.editor = options.editor;
|
||||
this._last_modified = undefined;
|
||||
this._filename = undefined;
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.bind_events();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
this.element.find('span.filename').click(function () {
|
||||
that.rename();
|
||||
});
|
||||
this.events.on('save_status_clean.Editor', function (evt) {
|
||||
that.update_document_title();
|
||||
});
|
||||
this.events.on('save_status_dirty.Editor', function (evt) {
|
||||
that.update_document_title(undefined, true);
|
||||
});
|
||||
this.events.on('file_loaded.Editor', function (evt, model) {
|
||||
that.update_filename(model.name);
|
||||
that.update_document_title(model.name);
|
||||
that.update_last_modified(model.last_modified);
|
||||
});
|
||||
this.events.on('file_saved.Editor', function (evt, model) {
|
||||
that.update_filename(model.name);
|
||||
that.update_document_title(model.name);
|
||||
that.update_last_modified(model.last_modified);
|
||||
});
|
||||
this.events.on('file_renamed.Editor', function (evt, model) {
|
||||
that.update_filename(model.name);
|
||||
that.update_document_title(model.name);
|
||||
that.update_address_bar(model.path);
|
||||
});
|
||||
this.events.on('file_save_failed.Editor', function () {
|
||||
that.set_save_status('Save Failed!');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.rename = function (options) {
|
||||
options = options || {};
|
||||
var that = this;
|
||||
var dialog_body = $('<div/>').append(
|
||||
$("<p/>").addClass("rename-message")
|
||||
.text('Enter a new filename:')
|
||||
).append(
|
||||
$("<br/>")
|
||||
).append(
|
||||
$('<input/>').attr('type','text').attr('size','25').addClass('form-control')
|
||||
.val(that.editor.get_filename())
|
||||
);
|
||||
var d = dialog.modal({
|
||||
title: "Rename File",
|
||||
body: dialog_body,
|
||||
default_button: "Cancel",
|
||||
buttons : {
|
||||
"Cancel": {},
|
||||
"OK": {
|
||||
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(
|
||||
function () {
|
||||
d.modal('hide');
|
||||
}, function (error) {
|
||||
d.find('.rename-message').text(error.message || 'Unknown error');
|
||||
d.find('input[type="text"]').prop('disabled', false).focus().select();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
open : function () {
|
||||
// Upon ENTER, click the OK button.
|
||||
d.find('input[type="text"]').keydown(function (event) {
|
||||
if (event.which === keyboard.keycodes.enter) {
|
||||
d.find('.btn-primary').first().click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
d.find('input[type="text"]').focus().select();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.update_filename = function (filename) {
|
||||
filename = bidi.applyBidi(filename);
|
||||
this.element.find('span.filename').text(filename);
|
||||
};
|
||||
|
||||
SaveWidget.prototype.update_document_title = function (filename, dirty) {
|
||||
if(filename){
|
||||
this._filename = filename;
|
||||
}
|
||||
document.title = (dirty ? '*' : '') + this._filename + ' - Jupyter Text Editor';
|
||||
};
|
||||
|
||||
SaveWidget.prototype.update_address_bar = function (path) {
|
||||
var state = {path : path};
|
||||
window.history.replaceState(state, "", utils.url_path_join(
|
||||
this.editor.base_url,
|
||||
"edit",
|
||||
utils.encode_uri_components(path)
|
||||
));
|
||||
};
|
||||
|
||||
SaveWidget.prototype.update_last_modified = function (last_modified) {
|
||||
if (last_modified) {
|
||||
this._last_modified = new Date(last_modified);
|
||||
} else {
|
||||
this._last_modified = null;
|
||||
}
|
||||
this._render_last_modified();
|
||||
};
|
||||
|
||||
SaveWidget.prototype._render_last_modified = function () {
|
||||
/** actually set the text in the element, from our _last_modified value
|
||||
|
||||
called directly, and periodically in timeouts.
|
||||
*/
|
||||
this._schedule_render_last_modified();
|
||||
var el = this.element.find('span.last_modified');
|
||||
if (!this._last_modified) {
|
||||
el.text('').attr('title', 'never saved');
|
||||
return;
|
||||
}
|
||||
var chkd = moment(this._last_modified);
|
||||
var long_date = chkd.format('llll');
|
||||
var human_date;
|
||||
var tdelta = Math.ceil(new Date() - this._last_modified);
|
||||
if (tdelta < utils.time.milliseconds.d){
|
||||
// less than 24 hours old, use relative date
|
||||
human_date = chkd.fromNow();
|
||||
} else {
|
||||
// otherwise show calendar
|
||||
// <Today | yesterday|...> at hh,mm,ss
|
||||
human_date = chkd.calendar();
|
||||
}
|
||||
el.text(human_date).attr('title', long_date);
|
||||
};
|
||||
|
||||
SaveWidget.prototype._schedule_render_last_modified = function () {
|
||||
/** schedule the next update to relative date
|
||||
|
||||
periodically updated, so short values like 'a few seconds ago' don't get stale.
|
||||
*/
|
||||
if (!this._last_modified) {
|
||||
return;
|
||||
}
|
||||
if ((this._last_modified_timeout)) {
|
||||
clearTimeout(this._last_modified_timeout);
|
||||
}
|
||||
var dt = Math.ceil(new Date() - this._last_modified);
|
||||
this._last_modified_timeout = setTimeout(
|
||||
$.proxy(this._render_last_modified, this),
|
||||
utils.time.timeout_from_dt(dt)
|
||||
);
|
||||
};
|
||||
|
||||
return {'SaveWidget': SaveWidget};
|
||||
|
||||
});
|
||||
@ -1,55 +0,0 @@
|
||||
.dirty-indicator{
|
||||
.fa();
|
||||
width:20px;
|
||||
}
|
||||
.dirty-indicator-dirty{
|
||||
.dirty-indicator();
|
||||
}
|
||||
|
||||
.dirty-indicator-clean{
|
||||
.dirty-indicator();
|
||||
&:before{
|
||||
.icon(@fa-var-check);
|
||||
}
|
||||
}
|
||||
|
||||
#filename {
|
||||
font-size: 16pt;
|
||||
display: table;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
#current-mode{
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#texteditor-backdrop {
|
||||
padding-top: @page-header-padding;
|
||||
padding-bottom: @page-header-padding;
|
||||
|
||||
@media not print{
|
||||
background-color: @page-backdrop-color;
|
||||
}
|
||||
|
||||
#texteditor-container {
|
||||
.CodeMirror-gutter, .CodeMirror-gutters {
|
||||
@media print {
|
||||
background-color: @body-bg;
|
||||
}
|
||||
@media not print {
|
||||
background-color: @page-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media not print{
|
||||
padding: 0px;
|
||||
background-color : @page-color;
|
||||
.box-shadow(@global-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-dialog {
|
||||
background-color: @page-color;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
.selected-keymap {
|
||||
i.fa {
|
||||
padding: 0px 5px;
|
||||
}
|
||||
i.fa:before {
|
||||
content: @fa-var-check;
|
||||
}
|
||||
}
|
||||
|
||||
#mode-menu {
|
||||
// truncate mode-menu, so it doesn't get longer than the screen
|
||||
overflow: auto;
|
||||
max-height: 20em;
|
||||
}
|
||||
|
||||
.edit_app {
|
||||
#header {
|
||||
.box-shadow(@global-shadow);
|
||||
}
|
||||
|
||||
#menubar .navbar {
|
||||
/* Use a negative 1 bottom margin, so the border overlaps the border of the
|
||||
header */
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
/*!
|
||||
*
|
||||
* IPython text editor webapp
|
||||
*
|
||||
*/
|
||||
@import "menubar.less";
|
||||
@import "edit.less";
|
||||
|
Before Width: | Height: | Size: 31 KiB |
@ -1,7 +0,0 @@
|
||||
/*This file contains any manual css for this page that needs to override the global styles.
|
||||
This is only required when different pages style the same element differently. This is just
|
||||
a hack to deal with our current css styles and no new styling should be added in this file.*/
|
||||
|
||||
#ipython-main-app {
|
||||
position: relative;
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
requirejs([
|
||||
'jquery',
|
||||
'base/js/dialog',
|
||||
'base/js/i18n',
|
||||
'underscore',
|
||||
'base/js/namespace'
|
||||
], function ($, dialog, i18n, _, IPython) {
|
||||
'use strict';
|
||||
$('#notebook_about').click(function () {
|
||||
// use underscore template to auto html escape
|
||||
if (sys_info) {
|
||||
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 + '<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(i18n.msg._('Waiting for kernel to be available...'));
|
||||
var body = $('<div/>');
|
||||
body.append($('<h4/>').text(i18n.msg._('Server Information:')));
|
||||
body.append($('<p/>').html(text));
|
||||
body.append($('<h4/>').text(i18n.msg._('Current Kernel Information:')));
|
||||
body.append(kinfo);
|
||||
} else {
|
||||
var text = i18n.msg._('Could not access sys_info variable for version information.');
|
||||
var body = $('<div/>');
|
||||
body.append($('<h4/>').text(i18n.msg._('Cannot find sys_info!')));
|
||||
body.append($('<p/>').html(text));
|
||||
}
|
||||
dialog.modal({
|
||||
title: i18n.msg._('About Jupyter Notebook'),
|
||||
body: body,
|
||||
buttons: { 'OK': {} }
|
||||
});
|
||||
try {
|
||||
IPython.notebook.session.kernel.kernel_info(function (data) {
|
||||
kinfo.html($('<pre/>').text(data.content.banner));
|
||||
});
|
||||
} catch (e) {
|
||||
kinfo.html($('<p/>').text(i18n.msg._('unable to contact kernel')));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,878 +0,0 @@
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module cell
|
||||
* @namespace cell
|
||||
* @class Cell
|
||||
*/
|
||||
|
||||
|
||||
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, i18n, CodeMirror, cm_match, cm_closeb, cm_comment, configmod) {
|
||||
"use strict";
|
||||
|
||||
function is_single_cursor(dict1, dict2) {
|
||||
return ((dict1.line == dict2.line) && (dict1.ch == dict2.ch));
|
||||
};
|
||||
|
||||
var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
|
||||
|
||||
CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
|
||||
overlayHack.apply(this, arguments);
|
||||
// Reverse `min-height: 18px` scrollbar hack on OS X
|
||||
// which causes a dead area, making it impossible to click on the last line
|
||||
// when there is horizontal scrolling to do and the "show scrollbar only when scrolling" behavior
|
||||
// is enabled.
|
||||
// This, in turn, has the undesirable behavior of never showing the horizontal scrollbar,
|
||||
// even when it should, which is less problematic, at least.
|
||||
if (/Mac/.test(navigator.platform)) {
|
||||
this.horiz.style.minHeight = "";
|
||||
}
|
||||
};
|
||||
|
||||
var Cell = function (options) {
|
||||
/* Constructor
|
||||
*
|
||||
* The Base `Cell` class from which to inherit.
|
||||
* @constructor
|
||||
* @param:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: dictionary
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
*/
|
||||
options = options || {};
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
this.events = options.events;
|
||||
var config = options.config;
|
||||
// superclass default overwrite our default
|
||||
|
||||
this.selected = false;
|
||||
this.anchor = false;
|
||||
this.rendered = false;
|
||||
this.mode = 'command';
|
||||
|
||||
// Metadata property
|
||||
var that = this;
|
||||
this._metadata = {};
|
||||
Object.defineProperty(this, 'metadata', {
|
||||
get: function() { return that._metadata; },
|
||||
set: function(value) {
|
||||
that._metadata = value;
|
||||
if (that.celltoolbar) {
|
||||
that.celltoolbar.rebuild();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// backward compat.
|
||||
Object.defineProperty(this, 'cm_config', {
|
||||
get: function() {
|
||||
console.warn(i18n.msg._("Warning: accessing Cell.cm_config directly is deprecated."));
|
||||
return that._options.cm_config;
|
||||
},
|
||||
});
|
||||
|
||||
// load this from metadata later ?
|
||||
this.user_highlight = 'auto';
|
||||
|
||||
// merge my class-specific config data with general cell-level config
|
||||
var class_config_data = {};
|
||||
if (this.class_config) {
|
||||
class_config_data = this.class_config.get_sync();
|
||||
}
|
||||
|
||||
var cell_config = new configmod.ConfigWithDefaults(options.config,
|
||||
Cell.options_default, 'Cell');
|
||||
var cell_config_data = cell_config.get_sync();
|
||||
|
||||
// this._options is a merge of SomeCell and Cell config data:
|
||||
this._options = utils.mergeopt({}, cell_config_data, class_config_data);
|
||||
this.placeholder = this._options.placeholder || '';
|
||||
|
||||
this.cell_id = utils.uuid();
|
||||
|
||||
// For JS VM engines optimization, attributes should be all set (even
|
||||
// to null) in the constructor, and if possible, if different subclass
|
||||
// have new attributes with same name, they should be created in the
|
||||
// same order. Easiest is to create and set to null in parent class.
|
||||
|
||||
this.element = null;
|
||||
this.cell_type = this.cell_type || null;
|
||||
this.code_mirror = null;
|
||||
|
||||
// The nbformat only specifies attachments for textcell, but to avoid
|
||||
// data loss when switching between cell types in the UI, all cells
|
||||
// have an attachments property here. It is only saved to disk
|
||||
// for textcell though (in toJSON)
|
||||
this.attachments = {};
|
||||
|
||||
this.create_element();
|
||||
if (this.element !== null) {
|
||||
this.element.data("cell", this);
|
||||
this.bind_events();
|
||||
this.init_classes();
|
||||
}
|
||||
};
|
||||
|
||||
Cell.options_default = {
|
||||
cm_config : {
|
||||
indentUnit : 4,
|
||||
readOnly: false,
|
||||
theme: "default",
|
||||
extraKeys: {
|
||||
"Cmd-Right": "goLineRight",
|
||||
"End": "goLineRight",
|
||||
"Cmd-Left": "goLineLeft",
|
||||
"Tab": "indentMore",
|
||||
"Shift-Tab" : "indentLess",
|
||||
"Cmd-/" : "toggleComment",
|
||||
"Ctrl-/" : "toggleComment",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: Workaround CM Bug #332 (Safari segfault on drag)
|
||||
// by disabling drag/drop altogether on Safari
|
||||
// https://github.com/codemirror/CodeMirror/issues/332
|
||||
if (utils.browser[0] == "Safari") {
|
||||
Cell.options_default.cm_config.dragDrop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty. Subclasses must implement create_element.
|
||||
* This should contain all the code to create the DOM element in notebook
|
||||
* and will be called by Base Class constructor.
|
||||
* @method create_element
|
||||
*/
|
||||
Cell.prototype.create_element = function () {
|
||||
};
|
||||
|
||||
Cell.prototype.init_classes = function () {
|
||||
/**
|
||||
* Call after this.element exists to initialize the css classes
|
||||
* related to selected, rendered and mode.
|
||||
*/
|
||||
if (this.selected) {
|
||||
this.element.addClass('selected');
|
||||
} else {
|
||||
this.element.addClass('unselected');
|
||||
}
|
||||
if (this.rendered) {
|
||||
this.element.addClass('rendered');
|
||||
} else {
|
||||
this.element.addClass('unrendered');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* trigger on focus and on click to bubble up to the notebook and
|
||||
* potentially extend the selection if shift-click, contract the selection
|
||||
* if just codemirror focus (so edit mode).
|
||||
* We **might** be able to move that to notebook `handle_edit_mode`.
|
||||
*/
|
||||
Cell.prototype._on_click = function (event) {
|
||||
if (!this.selected) {
|
||||
this.events.trigger('select.Cell', {'cell':this, 'extendSelection':event.shiftKey});
|
||||
} else {
|
||||
// I'm already part of the selection; contract selection to just me
|
||||
this.events.trigger('select.Cell', {'cell': this});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Subclasses can implement override bind_events.
|
||||
* Be careful to call the parent method when overwriting as it fires event.
|
||||
* this will be triggered after create_element in constructor.
|
||||
* @method bind_events
|
||||
*/
|
||||
Cell.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
// We trigger events so that Cell doesn't have to depend on Notebook.
|
||||
that.element.click(function (event) {
|
||||
that._on_click(event);
|
||||
});
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.on("change", function(cm, change) {
|
||||
that.events.trigger("change.Cell", {cell: that, change: change});
|
||||
that.events.trigger("set_dirty.Notebook", {value: true});
|
||||
});
|
||||
}
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.on('focus', function(cm, change) {
|
||||
if (!that.selected) {
|
||||
that.events.trigger('select.Cell', {'cell':that});
|
||||
}
|
||||
that.events.trigger('edit_mode.Cell', {cell: that});
|
||||
});
|
||||
}
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.on('blur', function(cm, change) {
|
||||
that.events.trigger('command_mode.Cell', {cell: that});
|
||||
});
|
||||
}
|
||||
|
||||
this.element.dblclick(function () {
|
||||
if (that.selected === false) {
|
||||
this.events.trigger('select.Cell', {'cell':that});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method gets called in CodeMirror's onKeyDown/onKeyPress
|
||||
* handlers and is used to provide custom key handling.
|
||||
*
|
||||
* To have custom handling, subclasses should override this method, but still call it
|
||||
* in order to process the Edit mode keyboard shortcuts.
|
||||
*
|
||||
* @method handle_codemirror_keyevent
|
||||
* @param {CodeMirror} editor - The codemirror instance bound to the cell
|
||||
* @param {event} event - key press event which either should or should not be handled by CodeMirror
|
||||
* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
|
||||
*/
|
||||
Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
|
||||
var shortcuts = this.keyboard_manager.edit_shortcuts;
|
||||
|
||||
var cur = editor.getCursor();
|
||||
if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
|
||||
event._ipkmIgnore = true;
|
||||
}
|
||||
var nLastLine = editor.lastLine();
|
||||
if ((event.keyCode === 40) &&
|
||||
((cur.line !== nLastLine) ||
|
||||
(cur.ch !== editor.getLineHandle(nLastLine).text.length))
|
||||
) {
|
||||
event._ipkmIgnore = true;
|
||||
}
|
||||
// if this is an edit_shortcuts shortcut, the global keyboard/shortcut
|
||||
// manager will handle it
|
||||
if (shortcuts.handles(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Triger typesetting of math by mathjax on current cell element
|
||||
* @method typeset
|
||||
*/
|
||||
Cell.prototype.typeset = function () {
|
||||
utils.typeset(this.element);
|
||||
};
|
||||
|
||||
/**
|
||||
* handle cell level logic when a cell is selected
|
||||
* @method select
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.select = function (moveanchor) {
|
||||
// if anchor is true, set the move the anchor
|
||||
moveanchor = (moveanchor === undefined)? true:moveanchor;
|
||||
if(moveanchor){
|
||||
this.anchor=true;
|
||||
}
|
||||
|
||||
if (!this.selected) {
|
||||
this.element.addClass('selected');
|
||||
this.element.removeClass('unselected');
|
||||
this.selected = true;
|
||||
// disable 'insert image' menu item (specific cell types will enable
|
||||
// it in their override select())
|
||||
this.notebook.set_insert_image_enabled(false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handle cell level logic when the cell is unselected
|
||||
* @method unselect
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.unselect = function (moveanchor) {
|
||||
// if anchor is true, remove also the anchor
|
||||
moveanchor = (moveanchor === undefined)? true:moveanchor;
|
||||
if (moveanchor){
|
||||
this.anchor = false;
|
||||
}
|
||||
if (this.selected) {
|
||||
this.element.addClass('unselected');
|
||||
this.element.removeClass('selected');
|
||||
this.selected = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* should be overwritten by subclass
|
||||
* @method execute
|
||||
*/
|
||||
Cell.prototype.execute = function () {
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* handle cell level logic when a cell is rendered
|
||||
* @method render
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.render = function () {
|
||||
if (!this.rendered) {
|
||||
this.element.addClass('rendered');
|
||||
this.element.removeClass('unrendered');
|
||||
this.rendered = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handle cell level logic when a cell is unrendered
|
||||
* @method unrender
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.unrender = function () {
|
||||
if (this.rendered) {
|
||||
this.element.addClass('unrendered');
|
||||
this.element.removeClass('rendered');
|
||||
this.rendered = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegates keyboard shortcut handling to either Jupyter keyboard
|
||||
* manager when in command mode, or CodeMirror when in edit mode
|
||||
*
|
||||
* @method handle_keyevent
|
||||
* @param {CodeMirror} editor - The codemirror instance bound to the cell
|
||||
* @param {event} - key event to be handled
|
||||
* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
|
||||
*/
|
||||
Cell.prototype.handle_keyevent = function (editor, event) {
|
||||
if (this.mode === 'command') {
|
||||
return true;
|
||||
} else if (this.mode === 'edit') {
|
||||
return this.handle_codemirror_keyevent(editor, event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method at_top
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Cell.prototype.at_top = function () {
|
||||
var cm = this.code_mirror;
|
||||
var cursor = cm.getCursor();
|
||||
if (cursor.line === 0 && cursor.ch === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method at_bottom
|
||||
* @return {Boolean}
|
||||
* */
|
||||
Cell.prototype.at_bottom = function () {
|
||||
var cm = this.code_mirror;
|
||||
var cursor = cm.getCursor();
|
||||
if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* enter the command mode for the cell
|
||||
* @method command_mode
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.command_mode = function () {
|
||||
if (this.mode !== 'command') {
|
||||
this.mode = 'command';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* enter the edit mode for the cell
|
||||
* @method command_mode
|
||||
* @return is the action being taken
|
||||
*/
|
||||
Cell.prototype.edit_mode = function () {
|
||||
if (this.mode !== 'edit') {
|
||||
this.mode = 'edit';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Cell.prototype.ensure_focused = function() {
|
||||
if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
|
||||
this.focus_cell();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the cell in the DOM sense
|
||||
* @method focus_cell
|
||||
*/
|
||||
Cell.prototype.focus_cell = function () {
|
||||
this.element.focus();
|
||||
this._on_click({});
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the editor area so a user can type
|
||||
*
|
||||
* NOTE: If codemirror is focused via a mouse click event, you don't want to
|
||||
* call this because it will cause a page jump.
|
||||
* @method focus_editor
|
||||
*/
|
||||
Cell.prototype.focus_editor = function () {
|
||||
this.refresh();
|
||||
this.code_mirror.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh codemirror instance
|
||||
* @method refresh
|
||||
*/
|
||||
Cell.prototype.refresh = function () {
|
||||
if (this.code_mirror) {
|
||||
this.code_mirror.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* should be overwritten by subclass
|
||||
* @method get_text
|
||||
*/
|
||||
Cell.prototype.get_text = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* should be overwritten by subclass
|
||||
* @method set_text
|
||||
* @param {string} text
|
||||
*/
|
||||
Cell.prototype.set_text = function (text) {
|
||||
};
|
||||
|
||||
/**
|
||||
* should be overwritten by subclass
|
||||
* serialise cell to json.
|
||||
* @method toJSON
|
||||
**/
|
||||
Cell.prototype.toJSON = function () {
|
||||
var data = {};
|
||||
// deepcopy the metadata so copied cells don't share the same object
|
||||
data.metadata = JSON.parse(JSON.stringify(this.metadata));
|
||||
if (this.id !== undefined) {
|
||||
data.id = this.id;
|
||||
}
|
||||
if (data.metadata.deletable) {
|
||||
delete data.metadata.deletable;
|
||||
}
|
||||
if (data.metadata.editable) {
|
||||
delete data.metadata.editable;
|
||||
}
|
||||
if (data.metadata.collapsed === false) {
|
||||
delete data.metadata.collapsed;
|
||||
}
|
||||
data.cell_type = this.cell_type;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* should be overwritten by subclass
|
||||
* @method fromJSON
|
||||
**/
|
||||
Cell.prototype.fromJSON = function (data) {
|
||||
if (data.metadata !== undefined) {
|
||||
this.metadata = data.metadata;
|
||||
}
|
||||
if (data.id !== undefined) {
|
||||
this.id = data.id;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* can the cell be split into two cells (false if not deletable)
|
||||
*
|
||||
* @method is_splittable
|
||||
**/
|
||||
Cell.prototype.is_splittable = function () {
|
||||
return this.is_deletable();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* can the cell be merged with other cells (false if not deletable)
|
||||
* @method is_mergeable
|
||||
**/
|
||||
Cell.prototype.is_mergeable = function () {
|
||||
return this.is_deletable();
|
||||
};
|
||||
|
||||
/**
|
||||
* is the cell edtitable? only false (readonly) if
|
||||
* metadata.editable is explicitly false -- everything else
|
||||
* counts as true
|
||||
*
|
||||
* @method is_editable
|
||||
**/
|
||||
Cell.prototype.is_editable = function () {
|
||||
if (this.metadata.editable === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* is the cell deletable? only false (undeletable) if
|
||||
* metadata.deletable is explicitly false or if the cell is not
|
||||
* editable -- everything else counts as true
|
||||
*
|
||||
* @method is_deletable
|
||||
**/
|
||||
Cell.prototype.is_deletable = function () {
|
||||
if (this.metadata.deletable === false || !this.is_editable()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {String} - the text before the cursor
|
||||
* @method get_pre_cursor
|
||||
**/
|
||||
Cell.prototype.get_pre_cursor = function () {
|
||||
var cursor = this.code_mirror.getCursor();
|
||||
var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
|
||||
text = text.replace(/^\n+/, '').replace(/\n+$/, '');
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {String} - the text after the cursor
|
||||
* @method get_post_cursor
|
||||
**/
|
||||
Cell.prototype.get_post_cursor = function () {
|
||||
var cursor = this.code_mirror.getCursor();
|
||||
var last_line_num = this.code_mirror.lineCount()-1;
|
||||
var last_line_len = this.code_mirror.getLine(last_line_num).length;
|
||||
var end = {line:last_line_num, ch:last_line_len};
|
||||
var text = this.code_mirror.getRange(cursor, end);
|
||||
text = text.replace(/^\n+/, '').replace(/\n+$/, '');
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array} - the text between cursors and within selections (multicursor/sorted)
|
||||
* @method get_split_text
|
||||
**/
|
||||
Cell.prototype.get_split_text = function () {
|
||||
var start = {line:0, ch:0};
|
||||
var last_line_num = this.code_mirror.lineCount()-1;
|
||||
var last_line_len = this.code_mirror.getLine(last_line_num).length;
|
||||
var end = {line:last_line_num, ch:last_line_len};
|
||||
|
||||
var flag_empty_cell = is_single_cursor(start, end);
|
||||
var flag_first_position = false;
|
||||
var flag_last_position = false;
|
||||
var flag_all_select = false;
|
||||
|
||||
var ranges = this.code_mirror.listSelections();
|
||||
|
||||
var cursors = [start];
|
||||
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
// append both to handle selections
|
||||
// ranges[i].head.sticky is null if ctrl-a select
|
||||
if ((ranges[i].head.sticky == 'before') || (ranges[i].head.sticky === null )) {
|
||||
cursors.push(ranges[i].anchor);
|
||||
cursors.push(ranges[i].head);
|
||||
if (is_single_cursor(ranges[i].anchor, start) &&
|
||||
is_single_cursor(ranges[i].head, end)) {
|
||||
flag_all_select = true;
|
||||
}
|
||||
} else {
|
||||
cursors.push(ranges[i].head);
|
||||
cursors.push(ranges[i].anchor);
|
||||
if (is_single_cursor(ranges[i].head, start) &&
|
||||
is_single_cursor(ranges[i].anchor, end)) {
|
||||
flag_all_select = true;
|
||||
}
|
||||
}
|
||||
// single cursor at beginning or end of cell
|
||||
if (is_single_cursor(ranges[i].head, ranges[i].anchor)) {
|
||||
if (is_single_cursor(ranges[i].head, start)) flag_first_position = true;
|
||||
if (is_single_cursor(ranges[i].head, end)) flag_last_position = true;
|
||||
}
|
||||
}
|
||||
cursors.push(end);
|
||||
|
||||
// Cursors is now sorted, but likely has duplicates due to anchor and head being the same for cursors
|
||||
var locations = [cursors[0]];
|
||||
for (var i = 1; i < cursors.length; i++) {
|
||||
var last = locations[locations.length-1];
|
||||
var current = cursors[i];
|
||||
if ((last.line != current.line) || (last.ch != current.ch)) {
|
||||
locations.push(cursors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Split text
|
||||
var text_list = [];
|
||||
// Split single cursors at first position
|
||||
if (flag_empty_cell || flag_first_position) text_list.push('');
|
||||
for (var i = 1; i < locations.length; i++) {
|
||||
var text = this.code_mirror.getRange(locations[i-1], locations[i]);
|
||||
text = text.replace(/^\n+/, '').replace(/\n+$/, ''); // removes newlines at beginning and end
|
||||
text_list.push(text);
|
||||
}
|
||||
// Split single cursors at last position
|
||||
if (flag_last_position) text_list.push('');
|
||||
// Duplicate cell if full cell is selected
|
||||
if ((text_list.length == 1) && flag_all_select && !flag_empty_cell) {
|
||||
text_list = text_list.concat(text_list);
|
||||
}
|
||||
return text_list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/Hide CodeMirror LineNumber
|
||||
* @method show_line_numbers
|
||||
*
|
||||
* @param value {Bool} show (true), or hide (false) the line number in CodeMirror
|
||||
**/
|
||||
Cell.prototype.show_line_numbers = function (value) {
|
||||
this.code_mirror.setOption('lineNumbers', value);
|
||||
this.code_mirror.refresh();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle CodeMirror LineNumber
|
||||
* @method toggle_line_numbers
|
||||
**/
|
||||
Cell.prototype.toggle_line_numbers = function () {
|
||||
var val = this.code_mirror.getOption('lineNumbers');
|
||||
this.show_line_numbers(!val);
|
||||
};
|
||||
|
||||
/**
|
||||
* Force codemirror highlight mode
|
||||
* @method force_highlight
|
||||
* @param {object} - CodeMirror mode
|
||||
**/
|
||||
Cell.prototype.force_highlight = function(mode) {
|
||||
this.user_highlight = mode;
|
||||
this.auto_highlight();
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger autodetection of highlight scheme for current cell
|
||||
* @method auto_highlight
|
||||
*/
|
||||
Cell.prototype.auto_highlight = function () {
|
||||
this._auto_highlight(this.class_config.get_sync('highlight_modes'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to autodetect cell highlight mode, or use selected mode
|
||||
* @methods _auto_highlight
|
||||
* @private
|
||||
* @param {String|object|undefined} - CodeMirror mode | 'auto'
|
||||
**/
|
||||
Cell.prototype._auto_highlight = function (modes) {
|
||||
/**
|
||||
*Here we handle manually selected modes
|
||||
*/
|
||||
var that = this;
|
||||
var mode;
|
||||
if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
|
||||
{
|
||||
mode = this.user_highlight;
|
||||
CodeMirror.autoLoadMode(this.code_mirror, mode);
|
||||
this.code_mirror.setOption('mode', mode);
|
||||
return;
|
||||
}
|
||||
var current_mode = this.code_mirror.getOption('mode', mode);
|
||||
var first_line = this.code_mirror.getLine(0);
|
||||
// loop on every pairs
|
||||
for(mode in modes) {
|
||||
var regs = modes[mode].reg;
|
||||
// only one key every time but regexp can't be keys...
|
||||
for(var i=0; i<regs.length; i++) {
|
||||
// here we handle non magic_modes.
|
||||
// TODO :
|
||||
// On 3.0 and below, these things were regex.
|
||||
// But now should be string for json-able config.
|
||||
// We should get rid of assuming they might be already
|
||||
// in a later version of Jupyter.
|
||||
var re = regs[i];
|
||||
if(typeof(re) === 'string'){
|
||||
re = new RegExp(re);
|
||||
}
|
||||
if(first_line.match(re) !== null) {
|
||||
if(current_mode == mode){
|
||||
return;
|
||||
}
|
||||
if (mode.search('magic_') !== 0) {
|
||||
utils.requireCodeMirrorMode(mode, function (spec) {
|
||||
that.code_mirror.setOption('mode', spec);
|
||||
});
|
||||
return;
|
||||
}
|
||||
var magic_mode = mode;
|
||||
mode = magic_mode.substr(6);
|
||||
if(current_mode == magic_mode){
|
||||
return;
|
||||
}
|
||||
utils.requireCodeMirrorMode(mode, function (spec) {
|
||||
// Add an overlay mode to recognize the first line as "magic" instead
|
||||
// of the mode used for the rest of the cell.
|
||||
CodeMirror.defineMode(magic_mode, function(config) {
|
||||
var magicOverlay = {
|
||||
startState: function() {
|
||||
return {firstMatched : false, inMagicLine: false};
|
||||
},
|
||||
token: function(stream, state) {
|
||||
if(!state.firstMatched) {
|
||||
state.firstMatched = true;
|
||||
if (stream.match("%%", false)) {
|
||||
state.inMagicLine = true;
|
||||
}
|
||||
}
|
||||
if (state.inMagicLine) {
|
||||
stream.eat(function any(ch) { return true; });
|
||||
if (stream.eol()) {
|
||||
state.inMagicLine = false;
|
||||
}
|
||||
return "magic";
|
||||
}
|
||||
stream.skipToEnd();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return CodeMirror.overlayMode(CodeMirror.getMode(config, spec), magicOverlay);
|
||||
});
|
||||
that.code_mirror.setOption('mode', magic_mode);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback on default
|
||||
var default_mode;
|
||||
try {
|
||||
default_mode = this._options.cm_config.mode;
|
||||
} catch(e) {
|
||||
default_mode = 'text/plain';
|
||||
}
|
||||
if( current_mode === default_mode){
|
||||
return;
|
||||
}
|
||||
this.code_mirror.setOption('mode', default_mode);
|
||||
};
|
||||
|
||||
var UnrecognizedCell = function (options) {
|
||||
/** Constructor for unrecognized cells */
|
||||
Cell.apply(this, arguments);
|
||||
this.cell_type = 'unrecognized';
|
||||
this.celltoolbar = null;
|
||||
this.data = {};
|
||||
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
UnrecognizedCell.prototype = Object.create(Cell.prototype);
|
||||
|
||||
|
||||
// cannot merge or split unrecognized cells
|
||||
UnrecognizedCell.prototype.is_mergeable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
UnrecognizedCell.prototype.is_splittable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
UnrecognizedCell.prototype.toJSON = function () {
|
||||
/**
|
||||
* deepcopy the metadata so copied cells don't share the same object
|
||||
*/
|
||||
return JSON.parse(JSON.stringify(this.data));
|
||||
};
|
||||
|
||||
UnrecognizedCell.prototype.fromJSON = function (data) {
|
||||
this.data = data;
|
||||
if (data.metadata !== undefined) {
|
||||
this.metadata = data.metadata;
|
||||
} else {
|
||||
data.metadata = this.metadata;
|
||||
}
|
||||
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 () {
|
||||
Cell.prototype.create_element.apply(this, arguments);
|
||||
var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
|
||||
cell.attr('tabindex','2');
|
||||
|
||||
var prompt = $('<div/>').addClass('prompt input_prompt');
|
||||
cell.append(prompt);
|
||||
var inner_cell = $('<div/>').addClass('inner_cell');
|
||||
inner_cell.append(
|
||||
$("<a>")
|
||||
.attr("href", "#")
|
||||
.text(i18n.msg._("Unrecognized cell type"))
|
||||
);
|
||||
cell.append(inner_cell);
|
||||
this.element = cell;
|
||||
};
|
||||
|
||||
UnrecognizedCell.prototype.bind_events = function () {
|
||||
Cell.prototype.bind_events.apply(this, arguments);
|
||||
var cell = this;
|
||||
|
||||
this.element.find('.inner_cell').find("a").click(function () {
|
||||
cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
Cell: Cell,
|
||||
UnrecognizedCell: UnrecognizedCell
|
||||
};
|
||||
});
|
||||
@ -1,470 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/events',
|
||||
'base/js/i18n'
|
||||
], function($, IPython, events, i18n) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = function (options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* cell: Cell instance
|
||||
* notebook: Notebook instance
|
||||
*
|
||||
* TODO: This leaks, when cell are deleted
|
||||
* There is still a reference to each celltoolbars.
|
||||
*/
|
||||
CellToolbar._instances.push(this);
|
||||
this.notebook = options.notebook;
|
||||
this.cell = options.cell;
|
||||
this.create_element();
|
||||
this.rebuild();
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
CellToolbar.prototype.create_element = function () {
|
||||
this.inner_element = $('<div/>').addClass('celltoolbar');
|
||||
this.element = $('<div/>').addClass('ctb_hideshow')
|
||||
.append(this.inner_element);
|
||||
};
|
||||
|
||||
|
||||
// The default css style for the outer celltoolbar div
|
||||
// (ctb_hideshow) is display: none.
|
||||
// To show the cell toolbar, *both* of the following conditions must be met:
|
||||
// - A parent container has class `ctb_global_show`
|
||||
// - The celltoolbar has the class `ctb_show`
|
||||
// This allows global show/hide, as well as per-cell show/hide.
|
||||
|
||||
CellToolbar.global_hide = function () {
|
||||
$('body').removeClass('ctb_global_show');
|
||||
};
|
||||
|
||||
|
||||
CellToolbar.global_show = function () {
|
||||
$('body').addClass('ctb_global_show');
|
||||
};
|
||||
|
||||
|
||||
CellToolbar.prototype.hide = function () {
|
||||
this.element.removeClass('ctb_show');
|
||||
};
|
||||
|
||||
|
||||
CellToolbar.prototype.show = function () {
|
||||
this.element.addClass('ctb_show');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class variable that should contain a dict of all available callback
|
||||
* we need to think of wether or not we allow nested namespace
|
||||
* @property _callback_dict
|
||||
* @private
|
||||
* @static
|
||||
* @type Dict
|
||||
*/
|
||||
CellToolbar._callback_dict = {};
|
||||
|
||||
|
||||
/**
|
||||
* Class variable that should contain the reverse order list of the button
|
||||
* to add to the toolbar of each cell
|
||||
* @property _ui_controls_list
|
||||
* @private
|
||||
* @static
|
||||
* @type List
|
||||
*/
|
||||
CellToolbar._ui_controls_list = [];
|
||||
|
||||
|
||||
/**
|
||||
* Class variable that should contain the CellToolbar instances for each
|
||||
* cell of the notebook
|
||||
*
|
||||
* @private
|
||||
* @property _instances
|
||||
* @static
|
||||
* @type List
|
||||
*/
|
||||
CellToolbar._instances = [];
|
||||
|
||||
|
||||
/**
|
||||
* keep a list of all the available presets for the toolbar
|
||||
* @private
|
||||
* @property _presets
|
||||
* @static
|
||||
* @type Dict
|
||||
*/
|
||||
CellToolbar._presets = {};
|
||||
|
||||
|
||||
// this is by design not a prototype.
|
||||
/**
|
||||
* Register a callback to create an UI element in a cell toolbar.
|
||||
* @method register_callback
|
||||
* @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
|
||||
* for easier sorting and avoid collision
|
||||
* @param callback {function(div, cell)} callback that will be called to generate the ui element
|
||||
* @param [cell_types] {List_of_String|undefined} optional list of cell types. If present the UI element
|
||||
* will be added only to cells of types in the list.
|
||||
*
|
||||
*
|
||||
* The callback will receive the following element :
|
||||
*
|
||||
* * a div in which to add element.
|
||||
* * the cell it is responsible from
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Example that create callback for a button that toggle between `true` and `false` label,
|
||||
* with the metadata under the key 'foo' to reflect the status of the button.
|
||||
*
|
||||
* // first param reference to a DOM div
|
||||
* // second param reference to the cell.
|
||||
* var toggle = function(div, cell) {
|
||||
* var button_container = $(div)
|
||||
*
|
||||
* // let's create a button that show the current value of the metadata
|
||||
* var button = $('<div/>').button({label:String(cell.metadata.foo)});
|
||||
*
|
||||
* // On click, change the metadata value and update the button label
|
||||
* button.click(function(){
|
||||
* var v = cell.metadata.foo;
|
||||
* cell.metadata.foo = !v;
|
||||
* button.button("option", "label", String(!v));
|
||||
* })
|
||||
*
|
||||
* // add the button to the DOM div.
|
||||
* button_container.append(button);
|
||||
* }
|
||||
*
|
||||
* // now we register the callback under the name `foo` to give the
|
||||
* // user the ability to use it later
|
||||
* CellToolbar.register_callback('foo', toggle);
|
||||
*/
|
||||
CellToolbar.register_callback = function(name, callback, cell_types) {
|
||||
// Overwrite if it already exists.
|
||||
CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a preset of UI element in a cell toolbar.
|
||||
* Not supported Yet.
|
||||
* @method register_preset
|
||||
* @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
|
||||
* for easier sorting and avoid collision
|
||||
* @param preset_list {List_of_String} reverse order of the button in the toolbar. Each String of the list
|
||||
* should correspond to a name of a registered callback.
|
||||
*
|
||||
* @private
|
||||
* @example
|
||||
*
|
||||
* CellToolbar.register_callback('foo.c1', function(div, cell){...});
|
||||
* CellToolbar.register_callback('foo.c2', function(div, cell){...});
|
||||
* CellToolbar.register_callback('foo.c3', function(div, cell){...});
|
||||
* CellToolbar.register_callback('foo.c4', function(div, cell){...});
|
||||
* CellToolbar.register_callback('foo.c5', function(div, cell){...});
|
||||
*
|
||||
* CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
|
||||
* CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
|
||||
*/
|
||||
CellToolbar.register_preset = function(name, preset_list, notebook) {
|
||||
CellToolbar._presets[name] = preset_list;
|
||||
events.trigger('preset_added.CellToolbar', {name: name});
|
||||
// When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
|
||||
// In that case, activate the preset if needed.
|
||||
if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
|
||||
CellToolbar.activate_preset(name);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* unregister the selected preset,
|
||||
*
|
||||
* return true if preset successfully unregistered
|
||||
* false otherwise
|
||||
*
|
||||
**/
|
||||
CellToolbar.unregister_preset = function(name){
|
||||
if(CellToolbar._presets[name]){
|
||||
delete CellToolbar._presets[name];
|
||||
events.trigger('unregistered_preset.CellToolbar', {name: name});
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List the names of the presets that are currently registered.
|
||||
*
|
||||
* @method list_presets
|
||||
* @static
|
||||
*/
|
||||
CellToolbar.list_presets = function() {
|
||||
var keys = [];
|
||||
for (var k in CellToolbar._presets) {
|
||||
keys.push(k);
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Activate an UI preset from `register_preset`
|
||||
*
|
||||
* This does not update the selection UI.
|
||||
*
|
||||
* @method activate_preset
|
||||
* @param preset_name {String} string corresponding to the preset name
|
||||
*
|
||||
* @static
|
||||
* @private
|
||||
* @example
|
||||
*
|
||||
* CellToolbar.activate_preset('foo.foo_preset1');
|
||||
*/
|
||||
CellToolbar.activate_preset = function(preset_name){
|
||||
var preset = CellToolbar._presets[preset_name];
|
||||
|
||||
if(preset !== undefined){
|
||||
CellToolbar._ui_controls_list = preset;
|
||||
CellToolbar.rebuild_all();
|
||||
}
|
||||
|
||||
events.trigger('preset_activated.CellToolbar', {name: preset_name});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This should be called on the class and not on a instance as it will trigger
|
||||
* rebuild of all the instances.
|
||||
* @method rebuild_all
|
||||
* @static
|
||||
*
|
||||
*/
|
||||
CellToolbar.rebuild_all = function(){
|
||||
for(var i=0; i < CellToolbar._instances.length; i++){
|
||||
CellToolbar._instances[i].rebuild();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rebuild all the button on the toolbar to update its state.
|
||||
* @method rebuild
|
||||
*/
|
||||
CellToolbar.prototype.rebuild = function(){
|
||||
/**
|
||||
* strip everything from the div
|
||||
* which is probably inner_element
|
||||
* or this.element.
|
||||
*/
|
||||
this.inner_element.empty();
|
||||
this.ui_controls_list = [];
|
||||
|
||||
var callbacks = CellToolbar._callback_dict;
|
||||
var preset = CellToolbar._ui_controls_list;
|
||||
// Yes we iterate on the class variable, not the instance one.
|
||||
for (var i=0; i < preset.length; i++) {
|
||||
var key = preset[i];
|
||||
var callback = callbacks[key];
|
||||
if (!callback) continue;
|
||||
|
||||
if (typeof callback === 'object') {
|
||||
if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
|
||||
callback = callback.callback;
|
||||
}
|
||||
|
||||
var local_div = $('<div/>').addClass('button_container');
|
||||
try {
|
||||
callback(local_div, this.cell, this);
|
||||
this.ui_controls_list.push(key);
|
||||
} catch (e) {
|
||||
console.log(i18n.msg.sprintf(i18n.msg._("Error in cell toolbar callback %s"), key), e);
|
||||
continue;
|
||||
}
|
||||
// only append if callback succeeded.
|
||||
this.inner_element.append(local_div);
|
||||
}
|
||||
|
||||
// If there are no controls or the cell is a rendered TextCell hide the toolbar.
|
||||
if (!this.ui_controls_list.length) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CellToolbar.utils = {};
|
||||
|
||||
|
||||
/**
|
||||
* A utility function to generate bindings between a checkbox and cell/metadata
|
||||
* @method utils.checkbox_ui_generator
|
||||
* @static
|
||||
*
|
||||
* @param name {string} Label in front of the checkbox
|
||||
* @param setter {function( cell, newValue )}
|
||||
* A setter method to set the newValue
|
||||
* @param getter {function( cell )}
|
||||
* A getter methods which return the current value.
|
||||
*
|
||||
* @return callback {function( div, cell )} Callback to be passed to `register_callback`
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
|
||||
*
|
||||
* var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
|
||||
* // setter
|
||||
* function(cell, value){
|
||||
* // we check that the slideshow namespace exist and create it if needed
|
||||
* if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
|
||||
* // set the value
|
||||
* cell.metadata.slideshow.isSectionStart = value
|
||||
* },
|
||||
* // getter
|
||||
* function(cell){ var ns = cell.metadata.slideshow;
|
||||
* // if the slideshow namespace does not exist return `undefined`
|
||||
* // (will be interpreted as `false` by checkbox) otherwise
|
||||
* // return the value
|
||||
* return (ns == undefined)? undefined: ns.isSectionStart
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* CellToolbar.register_callback('newSlide', newSlide);
|
||||
*
|
||||
*/
|
||||
CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
|
||||
return function(div, cell, celltoolbar) {
|
||||
var button_container = $(div);
|
||||
|
||||
var chkb = $('<input/>').attr('type', 'checkbox');
|
||||
var lbl = $('<label/>').append($('<span/>').text(name));
|
||||
chkb.attr("checked", getter(cell));
|
||||
|
||||
chkb.click(function(){
|
||||
var v = getter(cell);
|
||||
setter(cell, !v);
|
||||
chkb.attr("checked", !v);
|
||||
});
|
||||
button_container.append($('<span/>').append(lbl).append(chkb));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A utility function to generate bindings between a input field and cell/metadata
|
||||
* @method utils.input_ui_generator
|
||||
* @static
|
||||
*
|
||||
* @param name {string} Label in front of the input field
|
||||
* @param setter {function( cell, newValue )}
|
||||
* A setter method to set the newValue
|
||||
* @param getter {function( cell )}
|
||||
* A getter methods which return the current value.
|
||||
*
|
||||
* @return callback {function( div, cell )} Callback to be passed to `register_callback`
|
||||
*
|
||||
*/
|
||||
CellToolbar.utils.input_ui_generator = function(name, setter, getter){
|
||||
return function(div, cell, celltoolbar) {
|
||||
var button_container = $(div);
|
||||
|
||||
var text = $('<input/>').attr('type', 'text');
|
||||
var lbl = $('<label/>').append($('<span/>').text(name));
|
||||
text.attr("value", getter(cell));
|
||||
|
||||
text.keyup(function(){
|
||||
setter(cell, text.val());
|
||||
});
|
||||
button_container.append($('<span/>').append(lbl).append(text));
|
||||
IPython.keyboard_manager.register_events(text);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A utility function to generate bindings between a dropdown list cell
|
||||
* @method utils.select_ui_generator
|
||||
* @static
|
||||
*
|
||||
* @param list_list {list_of_sublist} List of sublist of metadata value and name in the dropdown list.
|
||||
* sublist should contain 2 element each, first a string that would be displayed in the dropdown list,
|
||||
* and second the corresponding value to be passed to setter/return by getter. the corresponding value
|
||||
* should not be "undefined" or behavior can be unexpected.
|
||||
* @param setter {function( cell, newValue )}
|
||||
* A setter method to set the newValue
|
||||
* @param getter {function( cell )}
|
||||
* A getter methods which return the current value of the metadata.
|
||||
* @param [label=""] {String} optional label for the dropdown menu
|
||||
*
|
||||
* @return callback {function( div, cell )} Callback to be passed to `register_callback`
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var select_type = CellToolbar.utils.select_ui_generator([
|
||||
* ["<None>" , "None" ],
|
||||
* ["Header Slide" , "header_slide" ],
|
||||
* ["Slide" , "slide" ],
|
||||
* ["Fragment" , "fragment" ],
|
||||
* ["Skip" , "skip" ],
|
||||
* ],
|
||||
* // setter
|
||||
* function(cell, value){
|
||||
* // we check that the slideshow namespace exist and create it if needed
|
||||
* if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
|
||||
* // set the value
|
||||
* cell.metadata.slideshow.slide_type = value
|
||||
* },
|
||||
* // getter
|
||||
* function(cell){ var ns = cell.metadata.slideshow;
|
||||
* // if the slideshow namespace does not exist return `undefined`
|
||||
* // (will be interpreted as `false` by checkbox) otherwise
|
||||
* // return the value
|
||||
* return (ns == undefined)? undefined: ns.slide_type
|
||||
* }
|
||||
* CellToolbar.register_callback('slideshow.select', select_type);
|
||||
*
|
||||
*/
|
||||
CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
|
||||
label = label || "";
|
||||
return function(div, cell, celltoolbar) {
|
||||
var button_container = $(div);
|
||||
var lbl = $("<label/>").append($('<span/>').text(label));
|
||||
var select = $('<select/>');
|
||||
if(!cell.is_editable()){
|
||||
select.attr("disabled","disabled")
|
||||
}
|
||||
for(var i=0; i < list_list.length; i++){
|
||||
var opt = $('<option/>')
|
||||
.attr('value', list_list[i][1])
|
||||
.text(list_list[i][0]);
|
||||
select.append(opt);
|
||||
}
|
||||
select.val(getter(cell));
|
||||
select.change(function(){
|
||||
setter(cell, select.val());
|
||||
});
|
||||
button_container.append($('<span/>').append(lbl).append(select));
|
||||
};
|
||||
};
|
||||
|
||||
// Backwards compatibility.
|
||||
IPython.CellToolbar = CellToolbar;
|
||||
|
||||
return {'CellToolbar': CellToolbar};
|
||||
});
|
||||
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
'base/js/dialog',
|
||||
'base/js/i18n'
|
||||
], function(celltoolbar, dialog, i18n) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = celltoolbar.CellToolbar;
|
||||
|
||||
var edit_attachments_dialog = function(cell) {
|
||||
dialog.edit_attachments({
|
||||
attachments: cell.attachments,
|
||||
callback: function(attachments) {
|
||||
cell.attachments = attachments;
|
||||
// Force cell refresh
|
||||
cell.unrender();
|
||||
cell.render();
|
||||
},
|
||||
name: 'Cell',
|
||||
notebook: cell.notebook,
|
||||
keyboard_manager: cell.keyboard_manager
|
||||
});
|
||||
};
|
||||
|
||||
var add_dialog_button = function(div, cell) {
|
||||
var button_container = $(div);
|
||||
var button = $('<button />')
|
||||
.addClass('btn btn-default btn-xs')
|
||||
.text(i18n.msg._('Edit Attachments'))
|
||||
.click( function() {
|
||||
edit_attachments_dialog(cell);
|
||||
return false;
|
||||
});
|
||||
button_container.append(button);
|
||||
};
|
||||
|
||||
var register = function(notebook) {
|
||||
CellToolbar.register_callback('attachments.edit', add_dialog_button);
|
||||
|
||||
var attachments_preset = [];
|
||||
attachments_preset.push('attachments.edit');
|
||||
|
||||
CellToolbar.register_preset(i18n.msg._('Attachments'), attachments_preset, notebook);
|
||||
|
||||
};
|
||||
return {'register' : register};
|
||||
});
|
||||
@ -1,52 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
'base/js/dialog',
|
||||
'base/js/i18n'
|
||||
], function(celltoolbar, dialog, i18n) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = celltoolbar.CellToolbar;
|
||||
|
||||
var raw_edit = function (cell , edit_metadata_button) {
|
||||
dialog.edit_metadata({
|
||||
md: cell.metadata,
|
||||
callback: function (md) {
|
||||
cell.metadata = md;
|
||||
},
|
||||
name: i18n.msg._('Cell'),
|
||||
notebook: this.notebook,
|
||||
keyboard_manager: this.keyboard_manager,
|
||||
edit_metadata_button: edit_metadata_button
|
||||
});
|
||||
};
|
||||
|
||||
var add_raw_edit_button = function(div, cell) {
|
||||
var button_container = $(div);
|
||||
var button = $('<button/>')
|
||||
.addClass("btn btn-default btn-xs")
|
||||
.text(i18n.msg._("Edit Metadata"))
|
||||
.click( function () {
|
||||
raw_edit(cell, this);
|
||||
return false;
|
||||
});
|
||||
|
||||
button_container.append(button);
|
||||
};
|
||||
|
||||
var register = function (notebook) {
|
||||
CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
|
||||
raw_edit = $.proxy(raw_edit, {
|
||||
notebook: notebook,
|
||||
keyboard_manager: notebook.keyboard_manager
|
||||
});
|
||||
|
||||
var example_preset = [];
|
||||
example_preset.push('default.rawedit');
|
||||
|
||||
CellToolbar.register_preset(i18n.msg._('Edit Metadata'), example_preset, notebook);
|
||||
};
|
||||
return {'register': register};
|
||||
});
|
||||
@ -1,142 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
], function(celltoolbar) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = celltoolbar.CellToolbar;
|
||||
|
||||
var example_preset = [];
|
||||
|
||||
var simple_button = function(div, cell) {
|
||||
var button_container = $(div);
|
||||
var button = $('<div/>').button({icons:{primary:'ui-icon-locked'}});
|
||||
var fun = function(value){
|
||||
try{
|
||||
if(value){
|
||||
cell.code_mirror.setOption('readOnly','nocursor');
|
||||
button.button('option','icons',{primary:'ui-icon-locked'});
|
||||
} else {
|
||||
cell.code_mirror.setOption('readOnly',false);
|
||||
button.button('option','icons',{primary:'ui-icon-unlocked'});
|
||||
}
|
||||
} catch(e){}
|
||||
|
||||
};
|
||||
fun(cell.metadata.ro);
|
||||
button.click(function(){
|
||||
var v = cell.metadata.ro;
|
||||
var locked = !v;
|
||||
cell.metadata.ro = locked;
|
||||
fun(locked);
|
||||
})
|
||||
.css('height','16px')
|
||||
.css('width','35px');
|
||||
button_container.append(button);
|
||||
};
|
||||
|
||||
CellToolbar.register_callback('example.lock',simple_button);
|
||||
example_preset.push('example.lock');
|
||||
|
||||
var toggle_test = function(div, cell) {
|
||||
var button_container = $(div);
|
||||
var button = $('<div/>')
|
||||
.button({label:String(cell.metadata.foo)}).
|
||||
css('width','65px');
|
||||
button.click(function(){
|
||||
var v = cell.metadata.foo;
|
||||
cell.metadata.foo = !v;
|
||||
button.button("option","label",String(!v));
|
||||
});
|
||||
button_container.append(button);
|
||||
};
|
||||
|
||||
CellToolbar.register_callback('example.toggle',toggle_test);
|
||||
example_preset.push('example.toggle');
|
||||
|
||||
var checkbox_test = CellToolbar.utils.checkbox_ui_generator('Yes/No',
|
||||
// setter
|
||||
function(cell, value){
|
||||
// we check that the slideshow namespace exist and create it if needed
|
||||
if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
|
||||
// set the value
|
||||
cell.metadata.yn_test.value = value;
|
||||
},
|
||||
// getter
|
||||
function(cell){ var ns = cell.metadata.yn_test;
|
||||
// if the slideshow namespace does not exist return `undefined`
|
||||
// (will be interpreted as `false` by checkbox) otherwise
|
||||
// return the value
|
||||
return (ns === undefined)? undefined: ns.value;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
CellToolbar.register_callback('example.checkbox',checkbox_test);
|
||||
example_preset.push('example.checkbox');
|
||||
|
||||
var select_test = CellToolbar.utils.select_ui_generator([
|
||||
["-" ,undefined ],
|
||||
["Header Slide" ,"header_slide" ],
|
||||
["Slide" ,"slide" ],
|
||||
["Fragment" ,"fragment" ],
|
||||
["Skip" ,"skip" ],
|
||||
],
|
||||
// setter
|
||||
function(cell,value){
|
||||
// we check that the slideshow namespace exist and create it if needed
|
||||
if (cell.metadata.test === undefined){cell.metadata.test = {};}
|
||||
// set the value
|
||||
cell.metadata.test.slide_type = value;
|
||||
},
|
||||
// getter
|
||||
function(cell){ var ns = cell.metadata.test;
|
||||
// if the slideshow namespace does not exist return `undefined`
|
||||
// (will be interpreted as `false` by checkbox) otherwise
|
||||
// return the value
|
||||
return (ns === undefined)? undefined: ns.slide_type;
|
||||
});
|
||||
|
||||
CellToolbar.register_callback('example.select',select_test);
|
||||
example_preset.push('example.select');
|
||||
|
||||
var simple_dialog = function(title,text){
|
||||
var dlg = $('<div/>').attr('title',title)
|
||||
.append($('<p/>').text(text));
|
||||
$(dlg).dialog({
|
||||
autoOpen: true,
|
||||
height: 300,
|
||||
width: 650,
|
||||
modal: true,
|
||||
close: function() {
|
||||
/**
|
||||
*cleanup on close
|
||||
*/
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var add_simple_dialog_button = function(div, cell) {
|
||||
var help_text = ["This is the Metadata editting UI.",
|
||||
"It heavily rely on plugin to work ",
|
||||
"and is still under development. You shouldn't wait too long before",
|
||||
" seeing some customisable buttons in those toolbar."
|
||||
].join('\n');
|
||||
var button_container = $(div);
|
||||
var button = $('<div/>').button({label:'?'})
|
||||
.click(function(){simple_dialog('help',help_text); return false;});
|
||||
button_container.append(button);
|
||||
};
|
||||
|
||||
var register = function (notebook) {
|
||||
CellToolbar.register_callback('example.help',add_simple_dialog_button);
|
||||
example_preset.push('example.help');
|
||||
|
||||
CellToolbar.register_preset('Example',example_preset, notebook);
|
||||
console.log('Example extension for metadata editing loaded.');
|
||||
};
|
||||
return {'register': register};
|
||||
});
|
||||
@ -1,100 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
'base/js/dialog',
|
||||
'base/js/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([
|
||||
[i18n.msg._("None"), "-"],
|
||||
["LaTeX", "text/latex"],
|
||||
["reST", "text/restructuredtext"],
|
||||
["HTML", "text/html"],
|
||||
["Markdown", "text/markdown"],
|
||||
["Python", "text/x-python"],
|
||||
[i18n.msg._("Custom"), "dialog"],
|
||||
|
||||
],
|
||||
// setter
|
||||
function(cell, value) {
|
||||
if (value === "-") {
|
||||
delete cell.metadata.raw_mimetype;
|
||||
} else if (value === 'dialog') {
|
||||
var message =
|
||||
i18n.msg._("Set the MIME type of the raw cell:");
|
||||
|
||||
var mimeinput = $('<input/>')
|
||||
.attr('type', 'text')
|
||||
.attr('size', '25')
|
||||
.attr('name', 'mimetype')
|
||||
.val(cell.metadata.raw_mimetype || "-");
|
||||
|
||||
var dialogform = $('<div/>').attr('title', i18n.msg._("Edit MIME type"))
|
||||
.append(
|
||||
$('<form/>').append(
|
||||
$('<fieldset/>').append(
|
||||
$('<label/>')
|
||||
.attr('for', 'mimetype')
|
||||
.text(message)
|
||||
)
|
||||
.append($('<br/>'))
|
||||
.append(mimeinput)
|
||||
)
|
||||
);
|
||||
|
||||
dialog.modal({
|
||||
title: i18n.msg._("Raw Cell MIME Type"),
|
||||
body: dialogform,
|
||||
buttons : {
|
||||
Cancel: {},
|
||||
OK: {
|
||||
class: "btn-primary",
|
||||
click: function () {
|
||||
console.log(cell);
|
||||
cell.metadata.raw_mimetype = $(this).find('input').val();
|
||||
console.log(cell.metadata);
|
||||
}
|
||||
}
|
||||
},
|
||||
notebook: cell.notebook,
|
||||
keyboard_manager: cell.keyboard_manager,
|
||||
open : function (event, ui) {
|
||||
var that = $(this);
|
||||
// Upon ENTER, click the OK button.
|
||||
that.find('input[type="text"]').keydown(function (event, ui) {
|
||||
if (event.which === keyboard.keycodes.enter) {
|
||||
that.find('.btn-primary').first().click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
that.find('input[type="text"]').focus().select();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cell.metadata.raw_mimetype = value;
|
||||
}
|
||||
},
|
||||
//getter
|
||||
function(cell) {
|
||||
return cell.metadata.raw_mimetype || "";
|
||||
},
|
||||
// name
|
||||
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(i18n.msg._('Raw Cell Format'), raw_cell_preset, notebook);
|
||||
};
|
||||
return {'register': register};
|
||||
|
||||
});
|
||||
@ -1,44 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
'base/js/i18n'
|
||||
], function(celltoolbar, i18n) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = celltoolbar.CellToolbar;
|
||||
var slideshow_preset = [];
|
||||
|
||||
var select_type = CellToolbar.utils.select_ui_generator([
|
||||
["-" ,"-" ],
|
||||
[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){
|
||||
// we check that the slideshow namespace exist and create it if needed
|
||||
if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
|
||||
// set the value
|
||||
cell.metadata.slideshow.slide_type = value;
|
||||
},
|
||||
// getter
|
||||
function(cell){ var ns = cell.metadata.slideshow;
|
||||
// if the slideshow namespace does not exist return `undefined`
|
||||
// (will be interpreted as `false` by checkbox) otherwise
|
||||
// return the value
|
||||
return (ns === undefined)? undefined: ns.slide_type;
|
||||
},
|
||||
i18n.msg._("Slide Type"));
|
||||
|
||||
var register = function (notebook) {
|
||||
CellToolbar.register_callback('slideshow.select',select_type);
|
||||
slideshow_preset.push('slideshow.select');
|
||||
|
||||
CellToolbar.register_preset(i18n.msg._('Slideshow'),slideshow_preset, notebook);
|
||||
};
|
||||
return {'register': register};
|
||||
});
|
||||
@ -1,255 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'notebook/js/celltoolbar',
|
||||
'base/js/dialog',
|
||||
'base/js/i18n'
|
||||
], function(celltoolbar, dialog, i18n) {
|
||||
"use strict";
|
||||
|
||||
var CellToolbar = celltoolbar.CellToolbar;
|
||||
|
||||
var array_difference = function(a, b) {
|
||||
return a.filter(function(n) {
|
||||
return b.indexOf(n) === -1;
|
||||
});
|
||||
};
|
||||
|
||||
var write_tag = function(cell, name, add) {
|
||||
if (add) {
|
||||
// Add to metadata
|
||||
if (cell.metadata.tags === undefined) {
|
||||
cell.metadata.tags = [];
|
||||
} else if (cell.metadata.tags.indexOf(name) !== -1) {
|
||||
// Tag already exists
|
||||
return false;
|
||||
}
|
||||
cell.metadata.tags.push(name);
|
||||
} else {
|
||||
// Remove from metadata
|
||||
if (!cell.metadata || !cell.metadata.tags) {
|
||||
// No tags to remove
|
||||
return false;
|
||||
}
|
||||
// Remove tag from tags list
|
||||
var index = cell.metadata.tags.indexOf(name);
|
||||
if (index !== -1) {
|
||||
cell.metadata.tags.splice(index, 1);
|
||||
}
|
||||
// If tags list is empty, remove it
|
||||
if (cell.metadata.tags.length === 0) {
|
||||
delete cell.metadata.tags;
|
||||
}
|
||||
}
|
||||
cell.events.trigger('set_dirty.Notebook', {value: true});
|
||||
return true;
|
||||
};
|
||||
|
||||
var preprocess_input = function(input) {
|
||||
// Split on whitespace + commas:
|
||||
return input.split(/[,\s]+/)
|
||||
};
|
||||
|
||||
var add_tag = function(cell, tag_container, on_remove) {
|
||||
return function(name) {
|
||||
if (name === '') {
|
||||
// Skip empty strings
|
||||
return;
|
||||
}
|
||||
// Write tag to metadata
|
||||
var changed = write_tag(cell, name, true);
|
||||
|
||||
if (changed) {
|
||||
// Make tag UI
|
||||
var tag = make_tag(name, on_remove, cell.is_editable());
|
||||
tag_container.append(tag);
|
||||
var tag_map = jQuery.data(tag_container, "tag_map") || {};
|
||||
tag_map[name] = tag;
|
||||
jQuery.data(tag_container, 'tag_map', tag_map);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var remove_tag = function(cell, tag_container) {
|
||||
return function(name) {
|
||||
var changed = write_tag(cell, name, false);
|
||||
if (changed) {
|
||||
// Remove tag UI
|
||||
var tag_map = jQuery.data(tag_container, "tag_map") || {};
|
||||
var tag_UI = tag_map[name];
|
||||
delete tag_map[name];
|
||||
tag_UI.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var init_tag_container = function(cell, tag_container, on_remove) {
|
||||
var tag_list = cell.metadata.tags || [];
|
||||
if (!Array.isArray(tag_list)) {
|
||||
// We cannot make tags UI for this cell!
|
||||
// Maybe someone else used "tags" for something?
|
||||
return false; // Fail gracefully
|
||||
}
|
||||
|
||||
var tag_map = {};
|
||||
for (var i=0; i < tag_list.length; ++i) {
|
||||
var tag_name = tag_list[i];
|
||||
if (typeof tag_name !== 'string') {
|
||||
// Unexpected type, disable toolbar for safety
|
||||
return false;
|
||||
}
|
||||
var tag = make_tag(tag_name, on_remove, cell.is_editable());
|
||||
tag_container.append(tag);
|
||||
tag_map[tag_name] = tag;
|
||||
}
|
||||
jQuery.data(tag_container, 'tag_map', tag_map);
|
||||
return true;
|
||||
};
|
||||
|
||||
var make_tag = function(name, on_remove, is_editable) {
|
||||
var tag_UI = $('<span/>')
|
||||
.addClass('cell-tag')
|
||||
.text(name);
|
||||
|
||||
if(is_editable){
|
||||
var remove_button = $('<i/>')
|
||||
.addClass('remove-tag-btn')
|
||||
.addClass('fa fa-times')
|
||||
.click(function () {
|
||||
on_remove(name);
|
||||
return false;
|
||||
});
|
||||
tag_UI.append(remove_button);
|
||||
}
|
||||
return tag_UI;
|
||||
};
|
||||
|
||||
// Single edit with button to add tags
|
||||
var add_tag_edit = function(div, cell, on_add, on_remove) {
|
||||
var button_container = $(div);
|
||||
|
||||
var text = $('<input/>').attr('type', 'text');
|
||||
var button = $('<button />')
|
||||
.addClass('btn btn-default btn-xs')
|
||||
.text(i18n.msg._('Add tag'))
|
||||
.click(function() {
|
||||
var tags = preprocess_input(text[0].value);
|
||||
for (var i=0; i < tags.length; ++i) {
|
||||
on_add(tags[i]);
|
||||
}
|
||||
// Clear input after adding:
|
||||
text[0].value = '';
|
||||
return false;
|
||||
});
|
||||
// Wire enter in input to button click
|
||||
text.keyup(function(event){
|
||||
if(event.keyCode == 13){
|
||||
button.click();
|
||||
}
|
||||
});
|
||||
var input_container = $('<span/>')
|
||||
.addClass('tags-input');
|
||||
add_dialog_button(input_container, cell, on_add, on_remove);
|
||||
button_container.append(input_container
|
||||
.append(text)
|
||||
.append(button)
|
||||
);
|
||||
IPython.keyboard_manager.register_events(text);
|
||||
};
|
||||
|
||||
var tag_dialog = function(cell, on_add, on_remove) {
|
||||
var tag_list = cell.metadata.tags || [];
|
||||
|
||||
var message =
|
||||
i18n.msg._("Edit the list of tags below. All whitespace " +
|
||||
"is treated as tag separators.");
|
||||
|
||||
var textarea = $('<textarea/>')
|
||||
.attr('aria-label', i18n.msg._('Edit the tags in the text area'))
|
||||
.attr('rows', '13')
|
||||
.attr('cols', '80')
|
||||
.attr('name', 'tags')
|
||||
.text(tag_list.join('\n'));
|
||||
|
||||
var dialogform = $('<div/>').attr('title', i18n.msg._('Edit the tags'))
|
||||
.append(
|
||||
$('<form/>').append(
|
||||
$('<fieldset/>').append(
|
||||
$('<label/>')
|
||||
.attr('for','tags')
|
||||
.text(message)
|
||||
)
|
||||
.append($('<br/>'))
|
||||
.append(textarea)
|
||||
)
|
||||
);
|
||||
|
||||
var modal_obj = dialog.modal({
|
||||
title: i18n.msg._("Edit Tags"),
|
||||
body: dialogform,
|
||||
default_button: "Cancel",
|
||||
buttons: {
|
||||
Cancel: {},
|
||||
Edit: { class : "btn-primary",
|
||||
click: function() {
|
||||
var old_tags = cell.metadata.tags || [];
|
||||
var new_tags = preprocess_input(textarea[0].value);
|
||||
var added_tags = array_difference(new_tags, old_tags);
|
||||
var removed_tags = array_difference(old_tags, new_tags);
|
||||
for (var i=0; i < added_tags.length; ++i) {
|
||||
on_add(added_tags[i]);
|
||||
}
|
||||
for (var i=0; i < removed_tags.length; ++i) {
|
||||
on_remove(removed_tags[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
notebook: cell.notebook,
|
||||
keyboard_manager: cell.keyboard_manager,
|
||||
});
|
||||
};
|
||||
|
||||
var add_dialog_button = function(container, cell, on_add, on_remove) {
|
||||
var button = $('<button />')
|
||||
.addClass('btn btn-default btn-xs tags-dialog-btn')
|
||||
.text('...')
|
||||
.click( function() {
|
||||
tag_dialog(cell, on_add, on_remove);
|
||||
return false;
|
||||
});
|
||||
container.append(button);
|
||||
};
|
||||
|
||||
var add_tags_cellbar = function(div, cell) {
|
||||
var button_container = $(div);
|
||||
|
||||
button_container.addClass('tags_button_container');
|
||||
|
||||
var tag_container = $('<span/>').
|
||||
addClass('tag-container');
|
||||
var on_remove = remove_tag(cell, tag_container);
|
||||
var ok = init_tag_container(cell, tag_container, on_remove);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
button_container.append(tag_container);
|
||||
|
||||
var on_add = add_tag(cell, tag_container, on_remove);
|
||||
if(cell.is_editable()){
|
||||
add_tag_edit(div, cell, on_add, on_remove);
|
||||
}
|
||||
};
|
||||
|
||||
var register = function(notebook) {
|
||||
CellToolbar.register_callback('tags.edit', add_tags_cellbar);
|
||||
|
||||
var tags_preset = [];
|
||||
tags_preset.push('tags.edit');
|
||||
|
||||
CellToolbar.register_preset('Tags', tags_preset, notebook);
|
||||
|
||||
};
|
||||
return {'register' : register};
|
||||
});
|
||||
@ -1,159 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog'
|
||||
], function($, Jupyter, utils, i18n, dialog) {
|
||||
|
||||
var jcbprefix = '<pre class="jupyter-nb-cells-json">';
|
||||
var jcbsuffix = '</pre>';
|
||||
|
||||
function store_json(cells, clipboard) {
|
||||
// Firefox ignores application/json mime type, so put it in HTML as well.
|
||||
// We also copy a text version so you can paste cell sources into a text editor
|
||||
var j = JSON.stringify(cells);
|
||||
var t = cells.map(function(c) {return c.source;}).join('\n\n');
|
||||
clipboard.setData('text/plain', t);
|
||||
clipboard.setData('text/html', jcbprefix + j + jcbsuffix);
|
||||
clipboard.setData('application/json', j);
|
||||
}
|
||||
|
||||
function load_json(clipboard) {
|
||||
var s = clipboard.getData('text/html');
|
||||
// System/browsers may add some more stuff before/after our content, so
|
||||
// find where our prefix and suffix are.
|
||||
var pix = s.indexOf(jcbprefix);
|
||||
var six = s.lastIndexOf(jcbsuffix);
|
||||
if (pix === -1 || six === -1) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(s.slice(pix + jcbprefix.length, six));
|
||||
}
|
||||
|
||||
function isProgrammaticCopy(event) {
|
||||
return (typeof(event.target.selectionStart) !== 'undefined'
|
||||
&& typeof(event.target.selectionEnd) !== 'undefined'
|
||||
&& ((event.target.selectionEnd - event.target.selectionStart) > 0));
|
||||
}
|
||||
|
||||
function copy(event) {
|
||||
if ((Jupyter.notebook.mode !== 'command') ||
|
||||
// window.getSelection checks if text is selected, e.g. in output
|
||||
!window.getSelection().isCollapsed ||
|
||||
// Allow programmatic copy
|
||||
isProgrammaticCopy(event)) {
|
||||
return;
|
||||
}
|
||||
var selecn = Jupyter.notebook.get_selected_cells().map(
|
||||
function(c) { return c.toJSON();});
|
||||
store_json(selecn, event.clipboardData);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function paste(event) {
|
||||
if (Jupyter.notebook.mode !== 'command') {
|
||||
return;
|
||||
}
|
||||
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?
|
||||
if (Array.isArray(cells) && (cells.length > 0) &&
|
||||
cells[0].cell_type && cells[0].source) {
|
||||
var first_inserted = null;
|
||||
for (var i=0; i < cells.length; i++) {
|
||||
var cell_data = cells[i];
|
||||
var new_cell = Jupyter.notebook.insert_cell_above(cell_data.cell_type);
|
||||
new_cell.fromJSON(cell_data);
|
||||
if (first_inserted === null) {
|
||||
first_inserted = new_cell;
|
||||
}
|
||||
}
|
||||
first_inserted.focus_cell();
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function notebookOnlyEvent(callback) {
|
||||
// Only call the callback to redirect the event if the notebook should be
|
||||
// handling the events, at the discretion of the keyboard manager.
|
||||
// If the focus is in a text widget or something (kbmanager disabled),
|
||||
// allow the default event.
|
||||
return function() {
|
||||
if (Jupyter.keyboard_manager.enabled) {
|
||||
callback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function needs_text_box_for_paste_event() {
|
||||
// I know this is bad, but I don't know a better way to check this
|
||||
return navigator.userAgent.indexOf('Firefox') !== -1;
|
||||
}
|
||||
|
||||
function setup_paste_dialog() {
|
||||
// Firefox only fires a paste event if the cursor is in a text input. So, on
|
||||
// Ctrl-V, bring up a dialog with an invisible text box and catch the
|
||||
// second Ctrl-V
|
||||
var action = {
|
||||
icon: 'fa-clipboard', // a font-awesome class used on buttons, etc
|
||||
help : i18n.msg._('Dialog for paste from system clipboard'),
|
||||
help_index : 'zz',
|
||||
handler : function () {
|
||||
var entry_box = $('<input type="text"/>');
|
||||
entry_box.css('opacity', 0);
|
||||
function paste_close_dlg(e) {
|
||||
paste(e);
|
||||
// There must be a better way to do this, but it's not any of:
|
||||
// .hide(), .remove() or .dialog('close')
|
||||
paste_dlg.find('.close').click();
|
||||
document.removeEventListener('paste', paste_close_dlg);
|
||||
}
|
||||
document.addEventListener('paste', paste_close_dlg);
|
||||
var cmdtrl = i18n.msg._('Ctrl-V');
|
||||
if (utils.platform === 'MacOS') {
|
||||
cmdtrl = i18n.msg._('Cmd-V');
|
||||
}
|
||||
var dialog_body = $("<div/>").append("<p>").append(i18n.msg.sprintf(i18n.msg._("Press %s again to paste"),cmdtrl))
|
||||
.append("<br/>")
|
||||
.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 : i18n.msg.sprintf(i18n.msg._("%s to paste"),cmdtrl),
|
||||
body : dialog_body,
|
||||
open: function() {
|
||||
entry_box.focus();
|
||||
},
|
||||
buttons : {
|
||||
"Cancel" : {
|
||||
// click : function() { reject("Dialog cancelled"); },
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
var full_action_name = Jupyter.actions.register(action, 'paste-dialog', 'system-clipboard');
|
||||
Jupyter.keyboard_manager.command_shortcuts.add_shortcut('Cmdtrl-V', full_action_name);
|
||||
}
|
||||
|
||||
// Set clipboard event listeners on the document.
|
||||
return {setup_clipboard_events: function() {
|
||||
document.addEventListener('copy', notebookOnlyEvent(copy));
|
||||
if (needs_text_box_for_paste_event()) {
|
||||
setup_paste_dialog();
|
||||
} else {
|
||||
document.addEventListener('paste', notebookOnlyEvent(paste));
|
||||
}
|
||||
}};
|
||||
});
|
||||
@ -1,620 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module codecell
|
||||
* @namespace codecell
|
||||
* @class CodeCell
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/keyboard',
|
||||
'services/config',
|
||||
'notebook/js/cell',
|
||||
'notebook/js/outputarea',
|
||||
'notebook/js/completer',
|
||||
'notebook/js/celltoolbar',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/python/python',
|
||||
'notebook/js/codemirror-ipython'
|
||||
], function(
|
||||
$,
|
||||
IPython,
|
||||
utils,
|
||||
i18n,
|
||||
keyboard,
|
||||
configmod,
|
||||
cell,
|
||||
outputarea,
|
||||
completer,
|
||||
celltoolbar,
|
||||
CodeMirror,
|
||||
cmpython,
|
||||
cmip
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
var Cell = cell.Cell;
|
||||
|
||||
/* local util for codemirror */
|
||||
var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
|
||||
|
||||
/**
|
||||
*
|
||||
* function to delete until previous non blanking space character
|
||||
* or first multiple of 4 tabstop.
|
||||
* @private
|
||||
*/
|
||||
CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
|
||||
var tabSize = cm.getOption('tabSize');
|
||||
var ranges = cm.listSelections(); // handle multicursor
|
||||
for (var i = ranges.length - 1; i >= 0; i--) { // iterate reverse so any deletions don't overlap
|
||||
var head = ranges[i].head;
|
||||
var anchor = ranges[i].anchor;
|
||||
var sel = !posEq(head, anchor);
|
||||
if (sel) {
|
||||
// range is selection
|
||||
cm.replaceRange("", anchor, head);
|
||||
} else {
|
||||
// range is cursor
|
||||
var line = cm.getLine(head.line).substring(0, head.ch);
|
||||
if (line.match(/^\ +$/) !== null){
|
||||
// delete tabs
|
||||
var prevTabStop = (Math.ceil(head.ch/tabSize)-1)*tabSize;
|
||||
var from = CodeMirror.Pos(head.line, prevTabStop)
|
||||
cm.replaceRange("", from, head);
|
||||
} else {
|
||||
// delete normally
|
||||
var from = cm.findPosH(head, -1, 'char', false);
|
||||
cm.replaceRange("", from, head);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var keycodes = keyboard.keycodes;
|
||||
|
||||
var CodeCell = function (kernel, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A Cell conceived to write code.
|
||||
*
|
||||
* Parameters:
|
||||
* kernel: Kernel instance
|
||||
* The kernel doesn't have to be set at creation time, in that case
|
||||
* it will be null and set_kernel has to be called later.
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: dictionary
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
* notebook: Notebook instance
|
||||
* tooltip: Tooltip instance
|
||||
*/
|
||||
this.kernel = kernel || null;
|
||||
this.notebook = options.notebook;
|
||||
this.collapsed = false;
|
||||
this.events = options.events;
|
||||
this.tooltip = options.tooltip;
|
||||
this.config = options.config;
|
||||
this.class_config = new configmod.ConfigWithDefaults(this.config,
|
||||
CodeCell.options_default, 'CodeCell');
|
||||
|
||||
// create all attributed in constructor function
|
||||
// even if null for V8 VM optimisation
|
||||
this.input_prompt_number = null;
|
||||
this.celltoolbar = null;
|
||||
this.output_area = null;
|
||||
|
||||
this.last_msg_id = null;
|
||||
this.completer = null;
|
||||
|
||||
Cell.apply(this,[{
|
||||
config: options.config,
|
||||
keyboard_manager: options.keyboard_manager,
|
||||
events: this.events}]);
|
||||
|
||||
// Attributes we want to override in this subclass.
|
||||
this.cell_type = "code";
|
||||
var that = this;
|
||||
this.element.focusout(
|
||||
function() { that.auto_highlight(); }
|
||||
);
|
||||
};
|
||||
|
||||
CodeCell.options_default = {
|
||||
cm_config : {
|
||||
extraKeys: {
|
||||
"Backspace" : "delSpaceToPrevTabStop",
|
||||
},
|
||||
mode: 'text',
|
||||
theme: 'ipython',
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true
|
||||
},
|
||||
highlight_modes : {
|
||||
'magic_javascript' :{'reg':['^%%javascript']},
|
||||
'magic_perl' :{'reg':['^%%perl']},
|
||||
'magic_ruby' :{'reg':['^%%ruby']},
|
||||
'magic_python' :{'reg':['^%%python3?']},
|
||||
'magic_shell' :{'reg':['^%%bash']},
|
||||
'magic_r' :{'reg':['^%%R']},
|
||||
'magic_text/x-cython' :{'reg':['^%%cython']},
|
||||
},
|
||||
};
|
||||
|
||||
CodeCell.msg_cells = {};
|
||||
|
||||
CodeCell.prototype = Object.create(Cell.prototype);
|
||||
|
||||
/** @method create_element */
|
||||
CodeCell.prototype.create_element = function () {
|
||||
Cell.prototype.create_element.apply(this, arguments);
|
||||
var that = this;
|
||||
|
||||
var cell = $('<div></div>').addClass('cell code_cell');
|
||||
cell.attr('tabindex','2');
|
||||
|
||||
var input = $('<div></div>').addClass('input');
|
||||
this.input = input;
|
||||
|
||||
var prompt_container = $('<div/>').addClass('prompt_container');
|
||||
|
||||
var run_this_cell = $('<div></div>').addClass('run_this_cell');
|
||||
run_this_cell.prop('title', 'Run this cell');
|
||||
run_this_cell.append('<i class="fa-step-forward fa"></i>');
|
||||
run_this_cell.click(function (event) {
|
||||
event.stopImmediatePropagation();
|
||||
that.execute();
|
||||
});
|
||||
|
||||
var prompt = $('<div/>').addClass('prompt input_prompt');
|
||||
|
||||
var inner_cell = $('<div/>').addClass('inner_cell');
|
||||
this.celltoolbar = new celltoolbar.CellToolbar({
|
||||
cell: this,
|
||||
notebook: this.notebook});
|
||||
inner_cell.append(this.celltoolbar.element);
|
||||
var input_area = $('<div/>').addClass('input_area').attr("aria-label", i18n.msg._("Edit code here"));
|
||||
this.code_mirror = new CodeMirror(input_area.get(0), this._options.cm_config);
|
||||
// In case of bugs that put the keyboard manager into an inconsistent state,
|
||||
// ensure KM is enabled when CodeMirror is focused:
|
||||
this.code_mirror.on('focus', function () {
|
||||
if (that.keyboard_manager) {
|
||||
that.keyboard_manager.enable();
|
||||
}
|
||||
|
||||
that.code_mirror.setOption('readOnly', !that.is_editable());
|
||||
});
|
||||
this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
|
||||
$(this.code_mirror.getInputField()).attr("spellcheck", "false");
|
||||
inner_cell.append(input_area);
|
||||
prompt_container.append(prompt).append(run_this_cell);
|
||||
input.append(prompt_container).append(inner_cell);
|
||||
|
||||
var output = $('<div></div>');
|
||||
cell.append(input).append(output);
|
||||
this.element = cell;
|
||||
this.output_area = new outputarea.OutputArea({
|
||||
config: this.config,
|
||||
selector: output,
|
||||
prompt_area: true,
|
||||
events: this.events,
|
||||
keyboard_manager: this.keyboard_manager,
|
||||
});
|
||||
this.completer = new completer.Completer(this, this.events);
|
||||
};
|
||||
|
||||
/** @method bind_events */
|
||||
CodeCell.prototype.bind_events = function () {
|
||||
Cell.prototype.bind_events.apply(this, arguments);
|
||||
var that = this;
|
||||
|
||||
this.element.focusout(
|
||||
function() { that.auto_highlight(); }
|
||||
);
|
||||
|
||||
this.events.on('kernel_restarting.Kernel', function() {
|
||||
if (that.input_prompt_number === '*') {
|
||||
that.set_input_prompt();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method gets called in CodeMirror's onKeyDown/onKeyPress
|
||||
* handlers and is used to provide custom key handling. Its return
|
||||
* value is used to determine if CodeMirror should ignore the event:
|
||||
* true = ignore, false = don't ignore.
|
||||
* @method handle_codemirror_keyevent
|
||||
*/
|
||||
|
||||
CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
|
||||
|
||||
var that = this;
|
||||
// whatever key is pressed, first, cancel the tooltip request before
|
||||
// they are sent, and remove tooltip if any, except for tab again
|
||||
var tooltip_closed = null;
|
||||
if (event.type === 'keydown' && event.which !== keycodes.tab ) {
|
||||
tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
|
||||
}
|
||||
|
||||
var cur = editor.getCursor();
|
||||
if (event.keyCode === keycodes.enter){
|
||||
this.auto_highlight();
|
||||
}
|
||||
|
||||
if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
|
||||
// triger on keypress (!) otherwise inconsistent event.which depending on platform
|
||||
// browser and keyboard layout !
|
||||
// Pressing '(' , request tooltip, don't forget to reappend it
|
||||
// The second argument says to hide the tooltip if the docstring
|
||||
// is actually empty
|
||||
this.tooltip.pending(that, true);
|
||||
} else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
|
||||
// If tooltip is active, cancel it. The call to
|
||||
// remove_and_cancel_tooltip above doesn't pass, force=true.
|
||||
// Because of this it won't actually close the tooltip
|
||||
// if it is in sticky mode. Thus, we have to check again if it is open
|
||||
// and close it with force=true.
|
||||
if (!this.tooltip._hidden) {
|
||||
this.tooltip.remove_and_cancel_tooltip(true);
|
||||
}
|
||||
// If we closed the tooltip, don't let CM or the global handlers
|
||||
// handle this event.
|
||||
event.codemirrorIgnore = true;
|
||||
event._ipkmIgnore = true;
|
||||
event.preventDefault();
|
||||
return true;
|
||||
} else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
|
||||
if (editor.somethingSelected() || editor.getSelections().length !== 1){
|
||||
var anchor = editor.getCursor("anchor");
|
||||
var head = editor.getCursor("head");
|
||||
if( anchor.line !== head.line){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
|
||||
if (pre_cursor.trim() === "") {
|
||||
// Don't show tooltip if the part of the line before the cursor
|
||||
// is empty. In this case, let CodeMirror handle indentation.
|
||||
return false;
|
||||
}
|
||||
this.tooltip.request(that);
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
return true;
|
||||
} else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
|
||||
// Tab completion.
|
||||
this.tooltip.remove_and_cancel_tooltip();
|
||||
|
||||
// completion does not work on multicursor, it might be possible though in some cases
|
||||
if (editor.somethingSelected() || editor.getSelections().length > 1) {
|
||||
return false;
|
||||
}
|
||||
var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
|
||||
if (pre_cursor.trim() === "") {
|
||||
// Don't autocomplete if the part of the line before the cursor
|
||||
// is empty. In this case, let CodeMirror handle indentation.
|
||||
return false;
|
||||
} else {
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
this.completer.startCompletion();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// keyboard event wasn't one of those unique to code cells, let's see
|
||||
// if it's one of the generic ones (i.e. check edit mode shortcuts)
|
||||
return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
|
||||
};
|
||||
|
||||
// Kernel related calls.
|
||||
|
||||
CodeCell.prototype.set_kernel = function (kernel) {
|
||||
this.kernel = kernel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute current code cell to the kernel
|
||||
* @method execute
|
||||
*/
|
||||
CodeCell.prototype.execute = function (stop_on_error) {
|
||||
if (!this.kernel) {
|
||||
console.log(i18n.msg._("Can't execute cell since kernel is not set."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stop_on_error === undefined) {
|
||||
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.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);
|
||||
delete CodeCell.msg_cells[old_msg_id];
|
||||
this.last_msg_id = null;
|
||||
}
|
||||
if (this.get_text().trim().length === 0) {
|
||||
// nothing to do
|
||||
this.set_input_prompt(null);
|
||||
return;
|
||||
}
|
||||
this.set_input_prompt('*');
|
||||
this.element.addClass("running");
|
||||
var callbacks = this.get_callbacks();
|
||||
|
||||
this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
|
||||
stop_on_error : stop_on_error});
|
||||
CodeCell.msg_cells[this.last_msg_id] = this;
|
||||
this.render();
|
||||
this.events.trigger('execute.CodeCell', {cell: this});
|
||||
var that = this;
|
||||
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.off('finished_iopub.Kernel', handleFinished);
|
||||
}
|
||||
}
|
||||
this.events.on('finished_iopub.Kernel', handleFinished);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the default callbacks for
|
||||
* @method get_callbacks
|
||||
*/
|
||||
CodeCell.prototype.get_callbacks = function () {
|
||||
var that = this;
|
||||
return {
|
||||
clear_on_done: false,
|
||||
shell : {
|
||||
reply : $.proxy(this._handle_execute_reply, this),
|
||||
payload : {
|
||||
set_next_input : $.proxy(this._handle_set_next_input, this),
|
||||
page : $.proxy(this._open_with_pager, this)
|
||||
}
|
||||
},
|
||||
iopub : {
|
||||
output : function() {
|
||||
that.events.trigger('set_dirty.Notebook', {value: true});
|
||||
that.output_area.handle_output.apply(that.output_area, arguments);
|
||||
},
|
||||
clear_output : function() {
|
||||
that.events.trigger('set_dirty.Notebook', {value: true});
|
||||
that.output_area.handle_clear_output.apply(that.output_area, arguments);
|
||||
},
|
||||
},
|
||||
input : $.proxy(this._handle_input_request, this),
|
||||
};
|
||||
};
|
||||
|
||||
CodeCell.prototype._open_with_pager = function (payload) {
|
||||
this.events.trigger('open_with_text.Pager', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _handle_execute_reply
|
||||
* @private
|
||||
*/
|
||||
CodeCell.prototype._handle_execute_reply = function (msg) {
|
||||
this.set_input_prompt(msg.content.execution_count);
|
||||
this.element.removeClass("running");
|
||||
this.events.trigger('set_dirty.Notebook', {value: true});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _handle_set_next_input
|
||||
* @private
|
||||
*/
|
||||
CodeCell.prototype._handle_set_next_input = function (payload) {
|
||||
var data = {
|
||||
cell: this,
|
||||
text: payload.text,
|
||||
replace: payload.replace,
|
||||
clear_output: payload.clear_output,
|
||||
};
|
||||
this.events.trigger('set_next_input.Notebook', data);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _handle_input_request
|
||||
* @private
|
||||
*/
|
||||
CodeCell.prototype._handle_input_request = function (msg) {
|
||||
this.output_area.append_raw_input(msg);
|
||||
};
|
||||
|
||||
|
||||
// Basic cell manipulation.
|
||||
|
||||
CodeCell.prototype.select = function () {
|
||||
var cont = Cell.prototype.select.apply(this, arguments);
|
||||
if (cont) {
|
||||
this.code_mirror.refresh();
|
||||
this.auto_highlight();
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
CodeCell.prototype.render = function () {
|
||||
var cont = Cell.prototype.render.apply(this, arguments);
|
||||
// Always execute, even if we are already in the rendered state
|
||||
return cont;
|
||||
};
|
||||
|
||||
CodeCell.prototype.select_all = function () {
|
||||
var start = {line: 0, ch: 0};
|
||||
var nlines = this.code_mirror.lineCount();
|
||||
var last_line = this.code_mirror.getLine(nlines-1);
|
||||
var end = {line: nlines-1, ch: last_line.length};
|
||||
this.code_mirror.setSelection(start, end);
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.collapse_output = function () {
|
||||
this.output_area.collapse();
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.expand_output = function () {
|
||||
this.output_area.expand();
|
||||
this.output_area.unscroll_area();
|
||||
};
|
||||
|
||||
CodeCell.prototype.scroll_output = function () {
|
||||
this.output_area.expand();
|
||||
this.output_area.scroll_if_long();
|
||||
};
|
||||
|
||||
CodeCell.prototype.toggle_output = function () {
|
||||
this.output_area.toggle_output();
|
||||
};
|
||||
|
||||
CodeCell.prototype.toggle_output_scroll = function () {
|
||||
this.output_area.toggle_scroll();
|
||||
};
|
||||
|
||||
|
||||
CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
|
||||
var ns;
|
||||
if (prompt_value === undefined || prompt_value === null) {
|
||||
ns = " ";
|
||||
} else {
|
||||
ns = encodeURIComponent(prompt_value);
|
||||
}
|
||||
return '<bdi>'+i18n.msg._('In')+'</bdi> [' + ns + ']:';
|
||||
};
|
||||
|
||||
CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
|
||||
var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
|
||||
for(var i=1; i < lines_number; i++) {
|
||||
html.push(['...:']);
|
||||
}
|
||||
return html.join('<br/>');
|
||||
};
|
||||
|
||||
CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
|
||||
|
||||
|
||||
CodeCell.prototype.set_input_prompt = function (number) {
|
||||
var nline = 1;
|
||||
if (this.code_mirror !== undefined) {
|
||||
nline = this.code_mirror.lineCount();
|
||||
}
|
||||
this.input_prompt_number = number;
|
||||
var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
|
||||
|
||||
// This HTML call is okay because the user contents are escaped.
|
||||
this.element.find('div.input_prompt').html(prompt_html);
|
||||
this.events.trigger('set_dirty.Notebook', {value: true});
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.clear_input = function () {
|
||||
this.code_mirror.setValue('');
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.get_text = function () {
|
||||
return this.code_mirror.getValue();
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.set_text = function (code) {
|
||||
return this.code_mirror.setValue(code);
|
||||
};
|
||||
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
|
||||
// JSON serialization
|
||||
|
||||
CodeCell.prototype.fromJSON = function (data) {
|
||||
Cell.prototype.fromJSON.apply(this, arguments);
|
||||
if (data.cell_type === 'code') {
|
||||
if (data.source !== undefined) {
|
||||
this.set_text(data.source);
|
||||
// make this value the starting point, so that we can only undo
|
||||
// to this state, instead of a blank cell
|
||||
this.code_mirror.clearHistory();
|
||||
this.auto_highlight();
|
||||
}
|
||||
this.set_input_prompt(data.execution_count);
|
||||
this.output_area.trusted = data.metadata.trusted || false;
|
||||
this.output_area.fromJSON(data.outputs, data.metadata);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CodeCell.prototype.toJSON = function () {
|
||||
var data = Cell.prototype.toJSON.apply(this);
|
||||
data.source = this.get_text();
|
||||
// is finite protect against undefined and '*' value
|
||||
if (isFinite(this.input_prompt_number)) {
|
||||
data.execution_count = this.input_prompt_number;
|
||||
} else {
|
||||
data.execution_count = null;
|
||||
}
|
||||
var outputs = this.output_area.toJSON();
|
||||
data.outputs = outputs;
|
||||
data.metadata.trusted = this.output_area.trusted;
|
||||
if (this.output_area.collapsed) {
|
||||
data.metadata.collapsed = this.output_area.collapsed;
|
||||
} else {
|
||||
delete data.metadata.collapsed;
|
||||
}
|
||||
if (this.output_area.scroll_state === 'auto') {
|
||||
delete data.metadata.scrolled;
|
||||
} else {
|
||||
data.metadata.scrolled = this.output_area.scroll_state;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* handle cell level logic when the cell is unselected
|
||||
* @method unselect
|
||||
* @return is the action being taken
|
||||
*/
|
||||
CodeCell.prototype.unselect = function() {
|
||||
var cont = Cell.prototype.unselect.apply(this, arguments);
|
||||
if (cont) {
|
||||
// When a code cell is unselected, make sure that the corresponding
|
||||
// tooltip and completer to that cell is closed.
|
||||
this.tooltip.remove_and_cancel_tooltip(true);
|
||||
if (this.completer !== null) {
|
||||
this.completer.close();
|
||||
}
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
// Backwards compatibility.
|
||||
IPython.CodeCell = CodeCell;
|
||||
|
||||
return {'CodeCell': CodeCell};
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
// IPython mode is just a slightly altered Python Mode with `?` beeing a extra
|
||||
// single operator. Here we define `ipython` mode in the require `python`
|
||||
// callback to auto-load python mode, which is more likely not the best things
|
||||
// to do, but at least the simple one for now.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object"){ // CommonJS
|
||||
mod(requirejs("codemirror/lib/codemirror"),
|
||||
requirejs("codemirror/mode/python/python")
|
||||
);
|
||||
} else if (typeof define == "function" && define.amd){ // AMD
|
||||
define(["codemirror/lib/codemirror",
|
||||
"codemirror/mode/python/python"], mod);
|
||||
} else {// Plain browser env
|
||||
mod(CodeMirror);
|
||||
}
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("ipython", function(conf, parserConf) {
|
||||
var pythonConf = {};
|
||||
for (var prop in parserConf) {
|
||||
if (parserConf.hasOwnProperty(prop)) {
|
||||
pythonConf[prop] = parserConf[prop];
|
||||
}
|
||||
}
|
||||
pythonConf.name = 'python';
|
||||
pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|@\\^~<>!\\?]");
|
||||
if (pythonConf.version === 3) {
|
||||
pythonConf.identifiers = new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*");
|
||||
} else if (pythonConf.version === 2) {
|
||||
pythonConf.identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
|
||||
}
|
||||
return CodeMirror.getMode(conf, pythonConf);
|
||||
}, 'python');
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipython", "ipython");
|
||||
})
|
||||
@ -1,63 +0,0 @@
|
||||
// IPython GFM (GitHub Flavored Markdown) mode is just a slightly altered GFM
|
||||
// Mode with support for latex.
|
||||
//
|
||||
// Latex support was supported by Codemirror GFM as of
|
||||
// https://github.com/codemirror/CodeMirror/pull/567
|
||||
// But was later removed in
|
||||
// https://github.com/codemirror/CodeMirror/commit/d9c9f1b1ffe984aee41307f3e927f80d1f23590c
|
||||
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object"){ // CommonJS
|
||||
mod(requirejs("codemirror/lib/codemirror")
|
||||
,requirejs("codemirror/addon/mode/multiplex")
|
||||
,requirejs("codemirror/mode/gfm/gfm")
|
||||
,requirejs("codemirror/mode/stex/stex")
|
||||
);
|
||||
} else if (typeof define == "function" && define.amd){ // AMD
|
||||
define(["codemirror/lib/codemirror"
|
||||
,"codemirror/addon/mode/multiplex"
|
||||
,"codemirror/mode/python/python"
|
||||
,"codemirror/mode/stex/stex"
|
||||
], mod);
|
||||
} else {// Plain browser env
|
||||
mod(CodeMirror);
|
||||
}
|
||||
})( function(CodeMirror){
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
|
||||
|
||||
var gfm_mode = CodeMirror.getMode(config, "gfm");
|
||||
var tex_mode = CodeMirror.getMode(config, "stex");
|
||||
|
||||
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: "$$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "$", close: "$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\(", close: "\\)",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\[", close: "\\]",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
}
|
||||
// .. more multiplexed styles can follow here
|
||||
);
|
||||
}, 'gfm');
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm");
|
||||
})
|
||||
@ -1,207 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'typeahead',
|
||||
'base/js/i18n',
|
||||
'notebook/js/quickhelp'
|
||||
],function($, typeahead, i18n, QH){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Humanize the action name to be consumed by user.
|
||||
* internally the actions anem are of the form
|
||||
* <namespace>:<description-with-dashes>
|
||||
* we drop <namespace> and replace dashes for space.
|
||||
*/
|
||||
var humanize_action_id = function(str) {
|
||||
return str.split(':')[1].replace(/-/g, ' ').replace(/_/g, '-');
|
||||
};
|
||||
|
||||
/**
|
||||
* given an action id return 'command-shortcut', 'edit-shortcut' or 'no-shortcut'
|
||||
* for the action. This allows us to tag UI in order to visually distinguish
|
||||
* wether an action have a keybinding or not.
|
||||
**/
|
||||
var get_mode_for_action_id = function(name, notebook) {
|
||||
var shortcut = notebook.keyboard_manager.command_shortcuts.get_action_shortcut(name);
|
||||
if (shortcut) {
|
||||
return 'command-shortcut';
|
||||
}
|
||||
shortcut = notebook.keyboard_manager.edit_shortcuts.get_action_shortcut(name);
|
||||
if (shortcut) {
|
||||
return 'edit-shortcut';
|
||||
}
|
||||
return 'no-shortcut';
|
||||
};
|
||||
|
||||
var CommandPalette = function(notebook) {
|
||||
if(!notebook){
|
||||
throw new Error("CommandPalette takes a notebook non-null mandatory argument");
|
||||
}
|
||||
|
||||
// typeahead lib need a specific layout with specific class names.
|
||||
// the following just does that
|
||||
var form = $('<form/>');
|
||||
var container = $('<div/>').addClass('typeahead__container');
|
||||
var field = $('<div/>').addClass('typeahead__field');
|
||||
var input = $('<input/>').attr('type', 'search');
|
||||
|
||||
field
|
||||
.append(
|
||||
$('<span>').addClass('typeahead__query').append(
|
||||
input
|
||||
)
|
||||
)
|
||||
.append(
|
||||
$('<span/>').addClass('typeahead__button').append(
|
||||
$('<button/>').attr('type', 'submit').append(
|
||||
$('<span/>').addClass('typeahead__search-icon')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
container.append(field);
|
||||
form.append(container);
|
||||
|
||||
|
||||
var mod = $('<div/>').addClass('modal cmd-palette').append(
|
||||
$('<div/>').addClass('modal-dialog')
|
||||
.append(
|
||||
$('<div/>').addClass('modal-content').append(
|
||||
$('<div/>').addClass('modal-body')
|
||||
.append(
|
||||
form
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// end setting up right layout
|
||||
.modal({show: false, backdrop:true})
|
||||
.on('shown.bs.modal', function () {
|
||||
// click on button trigger de-focus on mouse up.
|
||||
// or something like that.
|
||||
setTimeout(function(){input.focus();}, 100);
|
||||
});
|
||||
|
||||
notebook.keyboard_manager.disable();
|
||||
|
||||
var before_close = function() {
|
||||
// little trick to trigger early in onsubmit
|
||||
// when the action called pop-up a dialog
|
||||
// insure this function is only called once
|
||||
if (before_close.ok) {
|
||||
return;
|
||||
}
|
||||
var cell = notebook.get_selected_cell();
|
||||
if (cell) {
|
||||
cell.select();
|
||||
}
|
||||
if (notebook.keyboard_manager) {
|
||||
notebook.keyboard_manager.enable();
|
||||
notebook.keyboard_manager.command_mode();
|
||||
}
|
||||
before_close.ok = true; // avoid double call.
|
||||
};
|
||||
|
||||
mod.on("hide.bs.modal", before_close);
|
||||
|
||||
|
||||
// will be trigger when user select action
|
||||
var onSubmit = function(node, query, result, resultCount) {
|
||||
if (actions.indexOf(result.key) >= 0) {
|
||||
before_close();
|
||||
notebook.keyboard_manager.actions.call(result.key);
|
||||
} else {
|
||||
console.warning("No command " + result.key);
|
||||
}
|
||||
mod.modal('hide');
|
||||
};
|
||||
|
||||
/* Whenever a result is rendered, if there is only one resulting
|
||||
* element then automatically select that element.
|
||||
*/
|
||||
var onResult = function(node, query, result, resultCount) {
|
||||
if (resultCount == 1) {
|
||||
requestAnimationFrame(function() {
|
||||
$('.typeahead-list > li:nth-child(2)').addClass('active');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// generate structure needed for typeahead layout and ability to search
|
||||
var src = {};
|
||||
|
||||
var actions = Object.keys(notebook.keyboard_manager.actions._actions);
|
||||
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
var action_id = actions[i];
|
||||
var action = notebook.keyboard_manager.actions.get(action_id);
|
||||
var group = action_id.split(':')[0];
|
||||
|
||||
src[group] = src[group] || {
|
||||
data: [],
|
||||
display: 'display'
|
||||
};
|
||||
|
||||
var short = notebook.keyboard_manager.command_shortcuts.get_action_shortcut(action_id) ||
|
||||
notebook.keyboard_manager.edit_shortcuts.get_action_shortcut(action_id);
|
||||
if (short) {
|
||||
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);
|
||||
}
|
||||
|
||||
var help = null;
|
||||
if (action.help) {
|
||||
help = i18n.msg._(action.help);
|
||||
}
|
||||
|
||||
src[group].data.push({
|
||||
display: display_text,
|
||||
shortcut: short,
|
||||
mode_shortcut: get_mode_for_action_id(action_id, notebook),
|
||||
group: group,
|
||||
icon: action.icon,
|
||||
help: help,
|
||||
key: action_id,
|
||||
});
|
||||
}
|
||||
|
||||
// now src is the right structure for typeahead
|
||||
|
||||
input.typeahead({
|
||||
emptyTemplate: function(query) {
|
||||
return $('<div>').text("No results found for ").append(
|
||||
$('<code>').text(query)
|
||||
);
|
||||
},
|
||||
maxItem: 1e3,
|
||||
minLength: 0,
|
||||
hint: true,
|
||||
group: {
|
||||
template:"{{group}} command group"
|
||||
},
|
||||
searchOnFocus: true,
|
||||
mustSelectItem: true,
|
||||
template: '<i class="fa fa-icon {{icon}}"></i>{{display}} <div title={{key}} class="pull-right {{mode_shortcut}}">{{shortcut}}</div>',
|
||||
order: "asc",
|
||||
source: src,
|
||||
callback: {
|
||||
onSubmit: onSubmit,
|
||||
onClickAfter: onSubmit,
|
||||
onResult: onResult
|
||||
},
|
||||
debug: false,
|
||||
});
|
||||
|
||||
mod.modal('show');
|
||||
};
|
||||
return {'CommandPalette': CommandPalette};
|
||||
});
|
||||
@ -1,421 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/keyboard',
|
||||
'notebook/js/contexthint',
|
||||
'codemirror/lib/codemirror',
|
||||
], function($, utils, keyboard, CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
// easier key mapping
|
||||
var keycodes = keyboard.keycodes;
|
||||
|
||||
var prepend_n_prc = function(str, n) {
|
||||
for( var i =0 ; i< n ; i++){
|
||||
str = '%'+str ;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
var _existing_completion = function(item, completion_array){
|
||||
for( var i=0; i < completion_array.length; i++) {
|
||||
if (completion_array[i].trim().substr(-item.length) == item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// what is the common start of all completions
|
||||
function shared_start(B, drop_prct) {
|
||||
if (B.length == 1) {
|
||||
return B[0];
|
||||
}
|
||||
var A = [];
|
||||
var common;
|
||||
var min_lead_prct = 10;
|
||||
for (var i = 0; i < B.length; i++) {
|
||||
var str = B[i].str;
|
||||
var localmin = 0;
|
||||
if(drop_prct === true){
|
||||
while ( str.substr(0, 1) == '%') {
|
||||
localmin = localmin+1;
|
||||
str = str.substring(1);
|
||||
}
|
||||
}
|
||||
min_lead_prct = Math.min(min_lead_prct, localmin);
|
||||
A.push(str);
|
||||
}
|
||||
|
||||
if (A.length > 1) {
|
||||
var tem1, tem2, s;
|
||||
A = A.slice(0).sort();
|
||||
tem1 = A[0];
|
||||
s = tem1.length;
|
||||
tem2 = A.pop();
|
||||
while (s && tem2.indexOf(tem1) == -1) {
|
||||
tem1 = tem1.substring(0, --s);
|
||||
}
|
||||
if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
|
||||
return {
|
||||
str:prepend_n_prc('', min_lead_prct),
|
||||
type: "computed",
|
||||
from: B[0].from,
|
||||
to: B[0].to
|
||||
};
|
||||
}
|
||||
return {
|
||||
str: prepend_n_prc(tem1, min_lead_prct),
|
||||
type: "computed",
|
||||
from: B[0].from,
|
||||
to: B[0].to
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var Completer = function (cell, events) {
|
||||
this.cell = cell;
|
||||
this.editor = cell.code_mirror;
|
||||
var that = this;
|
||||
events.on('kernel_busy.Kernel', function () {
|
||||
that.skip_kernel_completion = true;
|
||||
});
|
||||
events.on('kernel_idle.Kernel', function () {
|
||||
that.skip_kernel_completion = false;
|
||||
});
|
||||
};
|
||||
|
||||
Completer.prototype.startCompletion = function () {
|
||||
/**
|
||||
* call for a 'first' completion, that will set the editor and do some
|
||||
* special behavior like autopicking if only one completion available.
|
||||
*/
|
||||
if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) return;
|
||||
this.done = false;
|
||||
// use to get focus back on opera
|
||||
this.carry_on_completion(true);
|
||||
};
|
||||
|
||||
|
||||
// easy access for julia to monkeypatch
|
||||
//
|
||||
Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
|
||||
|
||||
Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
|
||||
return Completer.reinvoke_re.test(pre_cursor);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* pass true as parameter if this is the first invocation of the completer
|
||||
* this will prevent the completer to dismiss itself if it is not on a
|
||||
* word boundary like pressing tab after a space, and make it autopick the
|
||||
* only choice if there is only one which prevent from popping the UI. as
|
||||
* well as fast-forwarding the typing if all completion have a common
|
||||
* shared start
|
||||
**/
|
||||
Completer.prototype.carry_on_completion = function (first_invocation) {
|
||||
/**
|
||||
* Pass true as parameter if you want the completer to autopick when
|
||||
* only one completion. This function is automatically reinvoked at
|
||||
* each keystroke with first_invocation = false
|
||||
*/
|
||||
var cur = this.editor.getCursor();
|
||||
var line = this.editor.getLine(cur.line);
|
||||
var pre_cursor = this.editor.getRange({
|
||||
line: cur.line,
|
||||
ch: cur.ch - 1
|
||||
}, cur);
|
||||
|
||||
// we need to check that we are still on a word boundary
|
||||
// because while typing the completer is still reinvoking itself
|
||||
// so dismiss if we are on a "bad" character
|
||||
if (!this.reinvoke(pre_cursor) && !first_invocation) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.autopick = false;
|
||||
if (first_invocation) {
|
||||
this.autopick = true;
|
||||
}
|
||||
|
||||
// We want a single cursor position.
|
||||
if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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: [],
|
||||
cursor_start: cursor_pos,
|
||||
cursor_end: cursor_pos,
|
||||
}});
|
||||
} else {
|
||||
this.cell.kernel.complete(text, cursor_pos,
|
||||
$.proxy(this.finish_completing, this)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Completer.prototype.finish_completing = function (msg) {
|
||||
/**
|
||||
* let's build a function that wrap all that stuff into what is needed
|
||||
* for the new completer:
|
||||
*/
|
||||
var content = msg.content;
|
||||
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) {
|
||||
// adapted message spec replies don't have cursor position info,
|
||||
// interpret end=null as current position,
|
||||
// and negative start relative to that
|
||||
end = this.editor.indexFromPos(cur);
|
||||
if (start === null) {
|
||||
start = end;
|
||||
} 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
|
||||
//that are already in kernel completion
|
||||
var i;
|
||||
for (i=0; i < results.length; i++) {
|
||||
if (!_existing_completion(results[i].str, matches)) {
|
||||
filtered_results.push(results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// append the introspection result, in order, at at the beginning of
|
||||
// the table and compute the replacement range from current cursor
|
||||
// position and matched_text length.
|
||||
var from = this.editor.posFromIndex(start);
|
||||
var to = this.editor.posFromIndex(end);
|
||||
for (i = matches.length - 1; i >= 0; --i) {
|
||||
filtered_results.unshift({
|
||||
str: matches[i],
|
||||
type: "introspection",
|
||||
from: from,
|
||||
to: to
|
||||
});
|
||||
}
|
||||
|
||||
// one the 2 sources results have been merge, deal with it
|
||||
this.raw_result = filtered_results;
|
||||
|
||||
// if empty result return
|
||||
if (!this.raw_result || !this.raw_result.length) return;
|
||||
|
||||
// When there is only one completion, use it directly.
|
||||
if (this.autopick && this.raw_result.length == 1) {
|
||||
this.insert(this.raw_result[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.raw_result.length == 1) {
|
||||
// test if first and only completion totally matches
|
||||
// what is typed, in this case dismiss
|
||||
var str = this.raw_result[0].str;
|
||||
var pre_cursor = this.editor.getRange({
|
||||
line: cur.line,
|
||||
ch: cur.ch - str.length
|
||||
}, cur);
|
||||
if (pre_cursor == str) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.visible) {
|
||||
this.complete = $('<div/>').addClass('completions');
|
||||
this.complete.attr('id', 'complete');
|
||||
|
||||
// Currently webkit doesn't use the size attr correctly. See:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=4579
|
||||
this.sel = $('<select/>')
|
||||
.attr('tabindex', -1)
|
||||
.attr('multiple', 'true');
|
||||
this.complete.append(this.sel);
|
||||
this.visible = true;
|
||||
$('body').append(this.complete);
|
||||
|
||||
//build the container
|
||||
var that = this;
|
||||
this.sel.click(function () {
|
||||
that.pick();
|
||||
that.editor.focus();
|
||||
});
|
||||
this._handle_keydown = function (cm, event) {
|
||||
that.keydown(event);
|
||||
};
|
||||
this.editor.on('keydown', this._handle_keydown);
|
||||
this._handle_keypress = function (cm, event) {
|
||||
that.keypress(event);
|
||||
};
|
||||
this.editor.on('keypress', this._handle_keypress);
|
||||
}
|
||||
this.sel.attr('size', Math.min(10, this.raw_result.length));
|
||||
|
||||
// After everything is on the page, compute the position.
|
||||
// We put it above the code if it is too close to the bottom of the page.
|
||||
var pos = this.editor.cursorCoords(
|
||||
this.editor.posFromIndex(start)
|
||||
);
|
||||
var left = pos.left-3;
|
||||
var top;
|
||||
var cheight = this.complete.height();
|
||||
var wheight = $(window).height();
|
||||
if (pos.bottom+cheight+5 > wheight) {
|
||||
top = pos.top-cheight-4;
|
||||
} else {
|
||||
top = pos.bottom+1;
|
||||
}
|
||||
this.complete.css('left', left + 'px');
|
||||
this.complete.css('top', top + 'px');
|
||||
|
||||
// Clear and fill the list.
|
||||
this.sel.text('');
|
||||
this.build_gui_list(this.raw_result);
|
||||
return true;
|
||||
};
|
||||
|
||||
Completer.prototype.insert = function (completion) {
|
||||
this.editor.replaceRange(completion.str, completion.from, completion.to);
|
||||
};
|
||||
|
||||
Completer.prototype.build_gui_list = function (completions) {
|
||||
var MAXIMUM_GUI_LIST_LENGTH = 1000;
|
||||
for (var i = 0; i < completions.length && i < MAXIMUM_GUI_LIST_LENGTH; ++i) {
|
||||
var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
|
||||
this.sel.append(opt);
|
||||
}
|
||||
this.sel.children().first().attr('selected', 'true');
|
||||
this.sel.scrollTop(0);
|
||||
};
|
||||
|
||||
Completer.prototype.close = function () {
|
||||
this.done = true;
|
||||
$('#complete').remove();
|
||||
this.editor.off('keydown', this._handle_keydown);
|
||||
this.editor.off('keypress', this._handle_keypress);
|
||||
this.visible = false;
|
||||
};
|
||||
|
||||
Completer.prototype.pick = function () {
|
||||
this.insert(this.raw_result[this.sel[0].selectedIndex]);
|
||||
this.close();
|
||||
};
|
||||
|
||||
Completer.prototype.keydown = function (event) {
|
||||
var code = event.keyCode;
|
||||
|
||||
// Enter
|
||||
var options;
|
||||
var index;
|
||||
if (code == keycodes.enter) {
|
||||
event.codemirrorIgnore = true;
|
||||
event._ipkmIgnore = true;
|
||||
event.preventDefault();
|
||||
this.pick();
|
||||
// Escape or backspace
|
||||
} else if (code == keycodes.esc || code == keycodes.backspace) {
|
||||
event.codemirrorIgnore = true;
|
||||
event._ipkmIgnore = true;
|
||||
event.preventDefault();
|
||||
this.close();
|
||||
} else if (code == keycodes.tab) {
|
||||
//all the fastforwarding operation,
|
||||
//Check that shared start is not null which can append with prefixed completion
|
||||
// like %pylab , pylab have no shared start, and ff will result in py<tab><tab>
|
||||
// to erase py
|
||||
var sh = shared_start(this.raw_result, true);
|
||||
if (sh.str !== '') {
|
||||
this.insert(sh);
|
||||
}
|
||||
this.close();
|
||||
this.carry_on_completion();
|
||||
} else if (code == keycodes.up || code == keycodes.down) {
|
||||
// need to do that to be able to move the arrow
|
||||
// when on the first or last line of a code cell
|
||||
event.codemirrorIgnore = true;
|
||||
event._ipkmIgnore = true;
|
||||
event.preventDefault();
|
||||
|
||||
options = this.sel.find('option');
|
||||
index = this.sel[0].selectedIndex;
|
||||
if (code == keycodes.up) {
|
||||
index--;
|
||||
}
|
||||
if (code == keycodes.down) {
|
||||
index++;
|
||||
}
|
||||
index = Math.min(Math.max(index, 0), options.length-1);
|
||||
this.sel[0].selectedIndex = index;
|
||||
} else if (code == keycodes.pageup || code == keycodes.pagedown) {
|
||||
event.codemirrorIgnore = true;
|
||||
event._ipkmIgnore = true;
|
||||
|
||||
options = this.sel.find('option');
|
||||
index = this.sel[0].selectedIndex;
|
||||
if (code == keycodes.pageup) {
|
||||
index -= 10; // As 10 is the hard coded size of the drop down menu
|
||||
} else {
|
||||
index += 10;
|
||||
}
|
||||
index = Math.min(Math.max(index, 0), options.length-1);
|
||||
this.sel[0].selectedIndex = index;
|
||||
} else if (code == keycodes.left || code == keycodes.right) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
Completer.prototype.keypress = function (event) {
|
||||
/**
|
||||
* FIXME: This is a band-aid.
|
||||
* on keypress, trigger insertion of a single character.
|
||||
* This simulates the old behavior of completion as you type,
|
||||
* before events were disconnected and CodeMirror stopped
|
||||
* receiving events while the completer is focused.
|
||||
*/
|
||||
|
||||
var that = this;
|
||||
var code = event.keyCode;
|
||||
|
||||
// don't handle keypress if it's not a character (arrows on FF)
|
||||
// or ENTER/TAB
|
||||
if (event.charCode === 0 ||
|
||||
code == keycodes.tab ||
|
||||
code == keycodes.enter
|
||||
) return;
|
||||
|
||||
this.close();
|
||||
this.editor.focus();
|
||||
setTimeout(function () {
|
||||
that.carry_on_completion();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
return {'Completer': Completer};
|
||||
});
|
||||
@ -1,98 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// highly adapted for codemiror jshint
|
||||
define(['codemirror/lib/codemirror'], function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var forEach = function(arr, f) {
|
||||
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
||||
};
|
||||
|
||||
var arrayContains = function(arr, item) {
|
||||
if (!Array.prototype.indexOf) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (arr[i] === item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return arr.indexOf(item) != -1;
|
||||
};
|
||||
|
||||
CodeMirror.contextHint = function (editor) {
|
||||
// Find the token at the cursor
|
||||
var cur = editor.getCursor(),
|
||||
token = editor.getTokenAt(cur),
|
||||
tprop = token;
|
||||
// If it's not a 'word-style' token, ignore the token.
|
||||
// If it is a property, find out what it is a property of.
|
||||
var list = [];
|
||||
var clist = getCompletions(token, editor);
|
||||
for (var i = 0; i < clist.length; i++) {
|
||||
list.push({
|
||||
str: clist[i],
|
||||
type: "context",
|
||||
from: {
|
||||
line: cur.line,
|
||||
ch: token.start
|
||||
},
|
||||
to: {
|
||||
line: cur.line,
|
||||
ch: token.end
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
// find all 'words' of current cell
|
||||
var getAllTokens = function (editor) {
|
||||
var found = [];
|
||||
|
||||
// add to found if not already in it
|
||||
|
||||
|
||||
function maybeAdd(str) {
|
||||
if (!arrayContains(found, str)) found.push(str);
|
||||
}
|
||||
|
||||
// loop through all token on all lines
|
||||
var lineCount = editor.lineCount();
|
||||
// loop on line
|
||||
for (var l = 0; l < lineCount; l++) {
|
||||
var line = editor.getLine(l);
|
||||
//loop on char
|
||||
for (var c = 1; c < line.length; c++) {
|
||||
var tk = editor.getTokenAt({
|
||||
line: l,
|
||||
ch: c
|
||||
});
|
||||
// if token has a class, it has geat chances of beeing
|
||||
// of interest. Add it to the list of possible completions.
|
||||
// we could skip token of ClassName 'comment'
|
||||
// or 'number' and 'operator'
|
||||
if (tk.className !== null) {
|
||||
maybeAdd(tk.string);
|
||||
}
|
||||
// jump to char after end of current token
|
||||
c = tk.end;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
var getCompletions = function(token, editor) {
|
||||
var candidates = getAllTokens(editor);
|
||||
// filter all token that have a common start (but nox exactly) the length of the current token
|
||||
var lambda = function (x) {
|
||||
return (x.indexOf(token.string) === 0 && x != token.string);
|
||||
};
|
||||
var filterd = candidates.filter(lambda);
|
||||
return filterd;
|
||||
};
|
||||
|
||||
return {'contextHint': CodeMirror.contextHint};
|
||||
});
|
||||
@ -1,352 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/dialog',
|
||||
'base/js/utils',
|
||||
'base/js/i18n'
|
||||
], function($, IPython, dialog, utils, i18n) {
|
||||
"use strict";
|
||||
|
||||
var KernelSelector = function(selector, notebook) {
|
||||
var that = this;
|
||||
this.selector = selector;
|
||||
this.notebook = notebook;
|
||||
this.notebook.set_kernelselector(this);
|
||||
this.events = notebook.events;
|
||||
this.current_selection = null;
|
||||
this.kernelspecs = {};
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.request_kernelspecs();
|
||||
}
|
||||
this.bind_events();
|
||||
// Make the object globally available for user convenience & inspection
|
||||
IPython.kernelselector = this;
|
||||
this._finish_load = null;
|
||||
this._loaded = false;
|
||||
this.loaded = new Promise(function(resolve) {
|
||||
that._finish_load = resolve;
|
||||
});
|
||||
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
KernelSelector.prototype.request_kernelspecs = function() {
|
||||
// Preliminary documentation for kernelspecs api is at
|
||||
// https://github.com/ipython/ipython/wiki/IPEP-25%3A-Registry-of-installed-kernels#rest-api
|
||||
var url = utils.url_path_join(this.notebook.base_url, 'api/kernelspecs');
|
||||
utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
|
||||
};
|
||||
|
||||
var _sorted_names = function(kernelspecs) {
|
||||
// sort kernel names
|
||||
return Object.keys(kernelspecs).sort(function (a, b) {
|
||||
// sort by display_name
|
||||
var da = kernelspecs[a].spec.display_name;
|
||||
var db = kernelspecs[b].spec.display_name;
|
||||
if (da === db) {
|
||||
return 0;
|
||||
} else if (da > db) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
KernelSelector.prototype._got_kernelspecs = function(data) {
|
||||
var that = this;
|
||||
this.kernelspecs = data.kernelspecs;
|
||||
var change_kernel_submenu = $("#menu-change-kernel-submenu");
|
||||
var new_notebook_submenu = $("#menu-new-notebook-submenu");
|
||||
var keys = _sorted_names(data.kernelspecs);
|
||||
|
||||
keys.map(function (key) {
|
||||
// Create the Kernel > Change kernel submenu
|
||||
var ks = data.kernelspecs[key];
|
||||
change_kernel_submenu.append(
|
||||
$("<li>").attr("id", "kernel-submenu-"+ks.name).append(
|
||||
$('<a>')
|
||||
.attr('href', '#')
|
||||
.click( function () {
|
||||
that.set_kernel(ks.name);
|
||||
})
|
||||
.text(ks.spec.display_name)
|
||||
)
|
||||
);
|
||||
// Create the File > New Notebook submenu
|
||||
new_notebook_submenu.append(
|
||||
$("<li>").attr("id", "new-notebook-submenu-"+ks.name).append(
|
||||
$('<a>')
|
||||
.attr('href', '#')
|
||||
.click( function () {
|
||||
that.new_notebook(ks.name);
|
||||
})
|
||||
.text(ks.spec.display_name)
|
||||
)
|
||||
);
|
||||
|
||||
});
|
||||
// trigger loaded promise
|
||||
this._loaded = true;
|
||||
this._finish_load();
|
||||
};
|
||||
|
||||
KernelSelector.prototype._spec_changed = function (event, ks) {
|
||||
/** event handler for spec_changed */
|
||||
var that = this;
|
||||
|
||||
// update selection
|
||||
this.current_selection = ks.name;
|
||||
|
||||
// put the current kernel at the top of File > New Notebook
|
||||
var cur_kernel_entry = $("#new-notebook-submenu-" + ks.name);
|
||||
var parent = cur_kernel_entry.parent();
|
||||
// do something only if there is more than one kernel
|
||||
if (parent.children().length > 1) {
|
||||
// first, sort back the submenu
|
||||
parent.append(
|
||||
parent.children("li[class!='divider']").sort(
|
||||
function (a,b) {
|
||||
var da = $("a",a).text();
|
||||
var db = $("a",b).text();
|
||||
if (da === db) {
|
||||
return 0;
|
||||
} else if (da > db) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}}));
|
||||
// then, if there is no divider yet, add one
|
||||
if (!parent.children("li[class='divider']").length) {
|
||||
parent.prepend($("<li>").attr("class","divider"));
|
||||
}
|
||||
// finally, put the current kernel at the top
|
||||
parent.prepend(cur_kernel_entry);
|
||||
}
|
||||
|
||||
// load logo
|
||||
var logo_img = this.element.find("img.current_kernel_logo");
|
||||
$("#kernel_indicator").find('.kernel_indicator_name').text(ks.spec.display_name);
|
||||
if (ks.resources['logo-64x64']) {
|
||||
logo_img.attr("src", ks.resources['logo-64x64']);
|
||||
logo_img.attr("title", ks.spec.display_name);
|
||||
logo_img.show();
|
||||
} else {
|
||||
logo_img.hide();
|
||||
}
|
||||
|
||||
// load kernel css
|
||||
var css_url = ks.resources['kernel.css'];
|
||||
if (css_url) {
|
||||
$('#kernel-css').attr('href', css_url);
|
||||
} else {
|
||||
$('#kernel-css').attr('href', '');
|
||||
}
|
||||
|
||||
// load kernel js
|
||||
if (ks.resources['kernel.js']) {
|
||||
|
||||
// Debug added for Notebook 4.2, please remove at some point in the
|
||||
// future if the following does not append anymore when kernels
|
||||
// have kernel.js
|
||||
//
|
||||
// > Uncaught (in promise) TypeError: require is not a function
|
||||
//
|
||||
console.info('Dynamically requiring kernel.js, `requirejs` is ', requirejs);
|
||||
requirejs([ks.resources['kernel.js']],
|
||||
function (kernel_mod) {
|
||||
if (kernel_mod && kernel_mod.onload) {
|
||||
kernel_mod.onload();
|
||||
} else {
|
||||
console.warn("Kernel " + ks.name + " has a kernel.js file that does not contain "+
|
||||
"any asynchronous module definition. This is undefined behavior "+
|
||||
"and not recommended.");
|
||||
}
|
||||
}, function (err) {
|
||||
console.warn("Failed to load kernel.js from ", ks.resources['kernel.js'], err);
|
||||
}
|
||||
);
|
||||
this.events.on('spec_changed.Kernel', function (evt, new_ks) {
|
||||
if (ks.name != new_ks.name) {
|
||||
console.warn("kernelspec %s had custom kernel.js. Forcing page reload for %s.",
|
||||
ks.name, new_ks.name);
|
||||
that.notebook.save_notebook().then(function () {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
KernelSelector.prototype.set_kernel = function (selected) {
|
||||
/** set the kernel by name, ensuring kernelspecs have been loaded, first
|
||||
|
||||
kernel can be just a kernel name, or a notebook kernelspec metadata
|
||||
(name, language, display_name).
|
||||
*/
|
||||
var that = this;
|
||||
if (typeof selected === 'string') {
|
||||
selected = {
|
||||
name: selected
|
||||
};
|
||||
}
|
||||
if (this._loaded) {
|
||||
this._set_kernel(selected);
|
||||
} else {
|
||||
return this.loaded.then(function () {
|
||||
that._set_kernel(selected);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
KernelSelector.prototype._set_kernel = function (selected) {
|
||||
/** Actually set the kernel (kernelspecs have been loaded) */
|
||||
if (selected.name === this.current_selection) {
|
||||
// only trigger event if value changed
|
||||
return;
|
||||
}
|
||||
var kernelspecs = this.kernelspecs;
|
||||
var ks = kernelspecs[selected.name];
|
||||
if (ks === undefined) {
|
||||
var available = _sorted_names(kernelspecs);
|
||||
var matches = [];
|
||||
if (selected.language && selected.language.length > 0) {
|
||||
available.map(function (name) {
|
||||
if (kernelspecs[name].spec.language.toLowerCase() === selected.language.toLowerCase()) {
|
||||
matches.push(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (matches.length === 1) {
|
||||
ks = kernelspecs[matches[0]];
|
||||
console.log("No exact match found for " + selected.name +
|
||||
", using only kernel that matches language=" + selected.language, ks);
|
||||
this.events.trigger("spec_match_found.Kernel", {
|
||||
selected: selected,
|
||||
found: ks,
|
||||
});
|
||||
}
|
||||
// if still undefined, trigger failure event
|
||||
if (ks === undefined) {
|
||||
this.events.trigger("spec_not_found.Kernel", {
|
||||
selected: selected,
|
||||
matches: matches,
|
||||
available: available,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.notebook._session_starting &&
|
||||
this.notebook.session.kernel.name !== ks.name) {
|
||||
console.error("Cannot change kernel while waiting for pending session start.");
|
||||
return;
|
||||
}
|
||||
this.current_selection = ks.name;
|
||||
this.events.trigger('spec_changed.Kernel', ks);
|
||||
};
|
||||
|
||||
KernelSelector.prototype._spec_not_found = function (event, data) {
|
||||
var that = this;
|
||||
var select = $("<select>").addClass('form-control');
|
||||
console.warn("Kernelspec not found:", data);
|
||||
var names;
|
||||
if (data.matches.length > 1) {
|
||||
names = data.matches;
|
||||
} else {
|
||||
names = data.available;
|
||||
}
|
||||
names.map(function (name) {
|
||||
var ks = that.kernelspecs[name];
|
||||
select.append(
|
||||
$('<option/>').attr('value', ks.name).text(ks.spec.display_name || ks.name)
|
||||
);
|
||||
});
|
||||
|
||||
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(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 : i18n.msg._('Kernel not found'),
|
||||
body : body,
|
||||
buttons : {
|
||||
'Continue Without Kernel' : {
|
||||
class : 'btn-danger',
|
||||
click : function () {
|
||||
that.events.trigger('no_kernel.Kernel');
|
||||
}
|
||||
},
|
||||
'Set Kernel' : {
|
||||
class : 'btn-primary',
|
||||
click : function () {
|
||||
that.set_kernel(select.val());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
KernelSelector.prototype.new_notebook = function (kernel_name) {
|
||||
|
||||
var w = window.open('', IPython._target);
|
||||
// Create a new notebook in the same path as the current
|
||||
// notebook's path.
|
||||
var that = this;
|
||||
var parent = utils.url_path_split(that.notebook.notebook_path)[0];
|
||||
that.notebook.contents.new_untitled(parent, {type: "notebook"}).then(
|
||||
function (data) {
|
||||
var url = utils.url_path_join(
|
||||
that.notebook.base_url, 'notebooks',
|
||||
utils.encode_uri_components(data.path)
|
||||
);
|
||||
url += "?kernel_name=" + kernel_name;
|
||||
w.location = url;
|
||||
},
|
||||
function(error) {
|
||||
w.close();
|
||||
dialog.modal({
|
||||
title : i18n.msg._('Creating Notebook Failed'),
|
||||
body : i18n.msg.sprintf(i18n.msg._("The error was: %s"), error.message),
|
||||
buttons : {'OK' : {'class' : 'btn-primary'}}
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
KernelSelector.prototype.lock_switch = function() {
|
||||
// should set a flag and display warning+reload if user want to
|
||||
// re-change kernel. As UI discussion never finish
|
||||
// making that a separate PR.
|
||||
console.warn('switching kernel is not guaranteed to work !');
|
||||
};
|
||||
|
||||
KernelSelector.prototype.bind_events = function() {
|
||||
var that = this;
|
||||
this.events.on('spec_changed.Kernel', $.proxy(this._spec_changed, this));
|
||||
this.events.on('spec_not_found.Kernel', $.proxy(this._spec_not_found, this));
|
||||
this.events.on('kernel_created.Session', function (event, data) {
|
||||
that.set_kernel(data.kernel.name);
|
||||
});
|
||||
|
||||
var logo_img = this.element.find("img.current_kernel_logo");
|
||||
logo_img.on("load", function() {
|
||||
logo_img.show();
|
||||
});
|
||||
logo_img.on("error", function() {
|
||||
logo_img.hide();
|
||||
});
|
||||
};
|
||||
|
||||
return {'KernelSelector': KernelSelector};
|
||||
});
|
||||
@ -1,277 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module keyboardmanager
|
||||
* @namespace keyboardmanager
|
||||
* @class KeyboardManager
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/keyboard',
|
||||
], function($, utils, keyboard) {
|
||||
"use strict";
|
||||
|
||||
// Main keyboard manager for the notebook
|
||||
var keycodes = keyboard.keycodes;
|
||||
|
||||
var KeyboardManager = function (options) {
|
||||
/**
|
||||
* A class to deal with keyboard event and shortcut
|
||||
*
|
||||
* @class KeyboardManager
|
||||
* @constructor
|
||||
* @param options {dict} Dictionary of keyword arguments :
|
||||
* @param options.events {$(Events)} instance
|
||||
* @param options.pager: {Pager} pager instance
|
||||
*/
|
||||
this.mode = 'command';
|
||||
this.enabled = true;
|
||||
this.pager = options.pager;
|
||||
this.quick_help = undefined;
|
||||
this.notebook = undefined;
|
||||
this.last_mode = undefined;
|
||||
this.bind_events();
|
||||
this.env = {pager:this.pager};
|
||||
this.actions = options.actions;
|
||||
this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env, options.config, 'command');
|
||||
this.command_shortcuts._add_default_shortcuts(this.get_default_common_shortcuts());
|
||||
this.command_shortcuts._add_default_shortcuts(this.get_default_command_shortcuts());
|
||||
this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env);
|
||||
this.edit_shortcuts._add_default_shortcuts(this.get_default_common_shortcuts());
|
||||
this.edit_shortcuts._add_default_shortcuts(this.get_default_edit_shortcuts());
|
||||
|
||||
|
||||
this.config = options.config;
|
||||
var that = this;
|
||||
|
||||
this.config.loaded.then(function(){
|
||||
var edit_unbind;
|
||||
|
||||
try {
|
||||
edit_unbind = that.config.data.keys.edit.unbind||[];
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
edit_unbind = [];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
edit_unbind.forEach(function(u){that.edit_shortcuts.remove_shortcut(u);});
|
||||
|
||||
var command_unbind;
|
||||
|
||||
try {
|
||||
command_unbind = that.config.data.keys.command.unbind||[];
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
command_unbind = [];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
command_unbind.forEach(function(u){that.command_shortcuts.remove_shortcut(u);});
|
||||
|
||||
that.command_shortcuts.add_shortcuts( ((that.config.data.keys||{}).command||{}).bind);
|
||||
that.edit_shortcuts.add_shortcuts( ((that.config.data.keys||{}).edit ||{}).bind);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a dict of common shortcut
|
||||
* @method get_default_common_shortcuts
|
||||
*
|
||||
* @example Example of returned shortcut
|
||||
* ```
|
||||
* 'shortcut-key': 'action-name'
|
||||
* // a string representing the shortcut as dash separated value.
|
||||
* // e.g. 'shift' , 'shift-enter', 'cmd-t'
|
||||
*```
|
||||
*/
|
||||
KeyboardManager.prototype.get_default_common_shortcuts = function() {
|
||||
return {
|
||||
'shift' : 'jupyter-notebook:ignore',
|
||||
'shift-enter' : 'jupyter-notebook:run-cell-and-select-next',
|
||||
'alt-enter' : 'jupyter-notebook:run-cell-and-insert-below',
|
||||
'ctrl-enter' : 'jupyter-notebook:run-cell',
|
||||
// cmd on mac, ctrl otherwise
|
||||
'cmdtrl-enter' : 'jupyter-notebook:run-cell',
|
||||
'cmdtrl-s' : 'jupyter-notebook:save-notebook'
|
||||
};
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.get_default_edit_shortcuts = function() {
|
||||
return {
|
||||
'cmdtrl-shift-p' : 'jupyter-notebook:show-command-palette',
|
||||
'cmdtrl-shift-f' : 'jupyter-notebook:show-command-palette',
|
||||
'esc' : 'jupyter-notebook:enter-command-mode',
|
||||
'ctrl-m' : 'jupyter-notebook:enter-command-mode',
|
||||
'up' : 'jupyter-notebook:move-cursor-up',
|
||||
'down' : 'jupyter-notebook:move-cursor-down',
|
||||
'ctrl-shift--' : 'jupyter-notebook:split-cell-at-cursor',
|
||||
};
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.get_default_command_shortcuts = function() {
|
||||
return {
|
||||
'cmdtrl-shift-p': 'jupyter-notebook:show-command-palette',
|
||||
'cmdtrl-shift-f': 'jupyter-notebook:show-command-palette',
|
||||
'shift-space': 'jupyter-notebook:scroll-notebook-up',
|
||||
'shift-v' : 'jupyter-notebook:paste-cell-above',
|
||||
'shift-m' : 'jupyter-notebook:merge-cells',
|
||||
'shift-o' : 'jupyter-notebook:toggle-cell-output-scrolled',
|
||||
'enter' : 'jupyter-notebook:enter-edit-mode',
|
||||
'space' : 'jupyter-notebook:scroll-notebook-down',
|
||||
'down' : 'jupyter-notebook:select-next-cell',
|
||||
'i,i' : 'jupyter-notebook:interrupt-kernel',
|
||||
'0,0' : 'jupyter-notebook:confirm-restart-kernel',
|
||||
'd,d' : 'jupyter-notebook:delete-cell',
|
||||
'esc': 'jupyter-notebook:close-pager',
|
||||
'up' : 'jupyter-notebook:select-previous-cell',
|
||||
'k' : 'jupyter-notebook:select-previous-cell',
|
||||
'j' : 'jupyter-notebook:select-next-cell',
|
||||
'shift-k': 'jupyter-notebook:extend-selection-above',
|
||||
'shift-j': 'jupyter-notebook:extend-selection-below',
|
||||
'shift-up': 'jupyter-notebook:extend-selection-above',
|
||||
'shift-down': 'jupyter-notebook:extend-selection-below',
|
||||
'cmdtrl-a': 'jupyter-notebook:select-all',
|
||||
'x' : 'jupyter-notebook:cut-cell',
|
||||
'c' : 'jupyter-notebook:copy-cell',
|
||||
'v' : 'jupyter-notebook:paste-cell-below',
|
||||
'a' : 'jupyter-notebook:insert-cell-above',
|
||||
'b' : 'jupyter-notebook:insert-cell-below',
|
||||
'y' : 'jupyter-notebook:change-cell-to-code',
|
||||
'm' : 'jupyter-notebook:change-cell-to-markdown',
|
||||
'r' : 'jupyter-notebook:change-cell-to-raw',
|
||||
'1' : 'jupyter-notebook:change-cell-to-heading-1',
|
||||
'2' : 'jupyter-notebook:change-cell-to-heading-2',
|
||||
'3' : 'jupyter-notebook:change-cell-to-heading-3',
|
||||
'4' : 'jupyter-notebook:change-cell-to-heading-4',
|
||||
'5' : 'jupyter-notebook:change-cell-to-heading-5',
|
||||
'6' : 'jupyter-notebook:change-cell-to-heading-6',
|
||||
'o' : 'jupyter-notebook:toggle-cell-output-collapsed',
|
||||
's' : 'jupyter-notebook:save-notebook',
|
||||
'l' : 'jupyter-notebook:toggle-cell-line-numbers',
|
||||
'shift-l' : 'jupyter-notebook:toggle-all-line-numbers',
|
||||
'h' : 'jupyter-notebook:show-keyboard-shortcuts',
|
||||
'z' : 'jupyter-notebook:undo-cell-deletion',
|
||||
'q' : 'jupyter-notebook:close-pager',
|
||||
'p' : 'jupyter-notebook:show-command-palette',
|
||||
};
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
$(document).keydown(function (event) {
|
||||
if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){
|
||||
return false;
|
||||
}
|
||||
return that.handle_keydown(event);
|
||||
});
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.set_notebook = function (notebook) {
|
||||
this.notebook = notebook;
|
||||
this.actions.extend_env({notebook:notebook});
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.set_quickhelp = function (notebook) {
|
||||
this.actions.extend_env({quick_help:notebook});
|
||||
};
|
||||
|
||||
|
||||
KeyboardManager.prototype.handle_keydown = function (event) {
|
||||
/**
|
||||
* returning false from this will stop event propagation
|
||||
**/
|
||||
|
||||
if (event.which === keycodes.esc) {
|
||||
// Intercept escape at highest level to avoid closing
|
||||
// websocket connection with firefox
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (!this.enabled) {
|
||||
if (event.which === keycodes.esc) {
|
||||
this.notebook.command_mode();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
return this.edit_shortcuts.call_handler(event);
|
||||
} else if (this.mode === 'command') {
|
||||
return this.command_shortcuts.call_handler(event);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.edit_mode = function () {
|
||||
this.last_mode = this.mode;
|
||||
this.mode = 'edit';
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.command_mode = function () {
|
||||
this.last_mode = this.mode;
|
||||
this.mode = 'command';
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.enable = function () {
|
||||
this.enabled = true;
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.disable = function () {
|
||||
this.enabled = false;
|
||||
};
|
||||
|
||||
KeyboardManager.prototype.register_events = function (e) {
|
||||
e = $(e);
|
||||
var that = this;
|
||||
var handle_focus = function () {
|
||||
that.disable();
|
||||
};
|
||||
var handle_blur = function () {
|
||||
that.enable();
|
||||
};
|
||||
e.on('focusin', handle_focus);
|
||||
e.on('focusout', handle_blur);
|
||||
// TODO: Very strange. The focusout event does not seem fire for the
|
||||
// bootstrap textboxes on FF25&26... This works around that by
|
||||
// registering focus and blur events recursively on all inputs within
|
||||
// registered element.
|
||||
e.find('input').blur(handle_blur);
|
||||
e.on('DOMNodeInserted', function (event) {
|
||||
var target = $(event.target);
|
||||
if (target.is('input')) {
|
||||
target.blur(handle_blur);
|
||||
} else {
|
||||
target.find('input').blur(handle_blur);
|
||||
}
|
||||
});
|
||||
// There are times (raw_input) where we remove the element from the DOM before
|
||||
// focusout is called. In this case we bind to the remove event of jQueryUI,
|
||||
// which gets triggered upon removal, iff it is focused at the time.
|
||||
// is_focused must be used to check for the case where an element within
|
||||
// the element being removed is focused.
|
||||
e.on('remove', function () {
|
||||
if (utils.is_focused(e[0])) {
|
||||
that.enable();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {'KeyboardManager': KeyboardManager};
|
||||
});
|
||||
@ -1,249 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// adapted from Mozilla Developer Network example at
|
||||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
|
||||
// shim `bind` for testing under casper.js
|
||||
var bind = function bind(obj) {
|
||||
var slice = [].slice;
|
||||
var args = slice.call(arguments, 1),
|
||||
self = this,
|
||||
nop = function() {
|
||||
},
|
||||
bound = function() {
|
||||
return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments)));
|
||||
};
|
||||
nop.prototype = this.prototype || {}; // Firefox cries sometimes if prototype is undefined
|
||||
bound.prototype = new nop();
|
||||
return bound;
|
||||
};
|
||||
Function.prototype.bind = Function.prototype.bind || bind ;
|
||||
|
||||
requirejs.config({
|
||||
map: {
|
||||
"*": {
|
||||
"typeahead": "jquery-typeahead"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
requirejs([
|
||||
'jquery',
|
||||
'contents',
|
||||
'base/js/namespace',
|
||||
'notebook/js/notebook',
|
||||
'services/config',
|
||||
'base/js/utils',
|
||||
'base/js/page',
|
||||
'base/js/events',
|
||||
'base/js/promises',
|
||||
'auth/js/loginwidget',
|
||||
'notebook/js/maintoolbar',
|
||||
'notebook/js/pager',
|
||||
'notebook/js/promises',
|
||||
'notebook/js/quickhelp',
|
||||
'notebook/js/menubar',
|
||||
'notebook/js/notificationarea',
|
||||
'notebook/js/savewidget',
|
||||
'notebook/js/actions',
|
||||
'notebook/js/keyboardmanager',
|
||||
'notebook/js/kernelselector',
|
||||
'codemirror/lib/codemirror',
|
||||
'notebook/js/about',
|
||||
'notebook/js/searchandreplace',
|
||||
'notebook/js/clipboard',
|
||||
'bidi/bidi',
|
||||
'notebook/js/celltoolbarpresets/tags'
|
||||
], function(
|
||||
$,
|
||||
contents_service,
|
||||
IPython,
|
||||
notebook,
|
||||
configmod,
|
||||
utils,
|
||||
page,
|
||||
events,
|
||||
promises,
|
||||
loginwidget,
|
||||
maintoolbar,
|
||||
pager,
|
||||
nb_promises,
|
||||
quickhelp,
|
||||
menubar,
|
||||
notificationarea,
|
||||
savewidget,
|
||||
actions,
|
||||
keyboardmanager,
|
||||
kernelselector,
|
||||
CodeMirror,
|
||||
about,
|
||||
searchandreplace,
|
||||
clipboard,
|
||||
bidi
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
// Pull typeahead from the global jquery object
|
||||
var typeahead = $.typeahead;
|
||||
|
||||
try{
|
||||
requirejs(['custom/custom'], function() {});
|
||||
bidi.loadLocale();
|
||||
} catch(err) {
|
||||
console.log("Error processing custom.js. Logging and continuing");
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
// compat with old IPython, remove for IPython > 3.0
|
||||
window.CodeMirror = CodeMirror;
|
||||
|
||||
// Setup all of the config related things
|
||||
|
||||
|
||||
var common_options = {
|
||||
ws_url : utils.get_body_data("wsUrl"),
|
||||
base_url : utils.get_body_data("baseUrl"),
|
||||
notebook_path : utils.get_body_data("notebookPath"),
|
||||
notebook_name : utils.get_body_data('notebookName')
|
||||
};
|
||||
|
||||
var config_section = new configmod.ConfigSection('notebook', common_options);
|
||||
config_section.load();
|
||||
var common_config = new configmod.ConfigSection('common', common_options);
|
||||
common_config.load();
|
||||
|
||||
// Instantiate the main objects
|
||||
|
||||
var page = new page.Page('div#header', 'div#site');
|
||||
var pager = new pager.Pager('div#pager', {
|
||||
events: events});
|
||||
var acts = new actions.init();
|
||||
var keyboard_manager = new keyboardmanager.KeyboardManager({
|
||||
pager: pager,
|
||||
events: events,
|
||||
actions: acts,
|
||||
config: config_section,
|
||||
});
|
||||
var save_widget = new savewidget.SaveWidget('span#save_widget', {
|
||||
events: events,
|
||||
keyboard_manager: keyboard_manager});
|
||||
acts.extend_env({save_widget:save_widget});
|
||||
var contents = new contents_service.Contents({
|
||||
base_url: common_options.base_url,
|
||||
common_config: common_config
|
||||
});
|
||||
var notebook = new notebook.Notebook('div#notebook', $.extend({
|
||||
events: events,
|
||||
keyboard_manager: keyboard_manager,
|
||||
save_widget: save_widget,
|
||||
contents: contents,
|
||||
config: config_section},
|
||||
common_options));
|
||||
var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
|
||||
var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
|
||||
notebook: notebook,
|
||||
events: events,
|
||||
actions: acts});
|
||||
var quick_help = new quickhelp.QuickHelp({
|
||||
keyboard_manager: keyboard_manager,
|
||||
events: events,
|
||||
notebook: notebook});
|
||||
keyboard_manager.set_notebook(notebook);
|
||||
keyboard_manager.set_quickhelp(quick_help);
|
||||
var menubar = new menubar.MenuBar('#menubar', $.extend({
|
||||
notebook: notebook,
|
||||
contents: contents,
|
||||
events: events,
|
||||
save_widget: save_widget,
|
||||
quick_help: quick_help,
|
||||
actions: acts,
|
||||
config: config_section},
|
||||
common_options));
|
||||
var notification_area = new notificationarea.NotebookNotificationArea(
|
||||
'#notification_area', {
|
||||
events: events,
|
||||
save_widget: save_widget,
|
||||
notebook: notebook,
|
||||
keyboard_manager: keyboard_manager});
|
||||
notification_area.init_notification_widgets();
|
||||
var kernel_selector = new kernelselector.KernelSelector(
|
||||
'#kernel_logo_widget', notebook);
|
||||
searchandreplace.load(keyboard_manager);
|
||||
|
||||
$('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
|
||||
'<span id="test2" style="font-weight: bold;">x</span>'+
|
||||
'<span id="test3" style="font-style: italic;">x</span></pre></div>');
|
||||
var nh = $('#test1').innerHeight();
|
||||
var bh = $('#test2').innerHeight();
|
||||
var ih = $('#test3').innerHeight();
|
||||
if(nh != bh || nh != ih) {
|
||||
$('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
|
||||
}
|
||||
$('#fonttest').remove();
|
||||
|
||||
page.show();
|
||||
|
||||
events.one('notebook_loaded.Notebook', function () {
|
||||
var hash = document.location.hash;
|
||||
if (hash) {
|
||||
document.location.hash = '';
|
||||
document.location.hash = hash;
|
||||
}
|
||||
notebook.set_autosave_interval(notebook.minimum_autosave_interval);
|
||||
});
|
||||
|
||||
IPython.page = page;
|
||||
IPython.notebook = notebook;
|
||||
IPython.contents = contents;
|
||||
IPython.pager = pager;
|
||||
IPython.quick_help = quick_help;
|
||||
IPython.login_widget = login_widget;
|
||||
IPython.menubar = menubar;
|
||||
IPython.toolbar = toolbar;
|
||||
IPython.notification_area = notification_area;
|
||||
IPython.keyboard_manager = keyboard_manager;
|
||||
IPython.save_widget = save_widget;
|
||||
IPython.tooltip = notebook.tooltip;
|
||||
|
||||
try {
|
||||
events.trigger('app_initialized.NotebookApp');
|
||||
} catch (e) {
|
||||
console.error("Error in app_initialized callback", e);
|
||||
}
|
||||
|
||||
Object.defineProperty( IPython, 'actions', {
|
||||
get: function() {
|
||||
console.warn('accessing "actions" on the global IPython/Jupyter is not recommended. Pass it to your objects constructors at creation time');
|
||||
return acts;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
clipboard.setup_clipboard_events();
|
||||
|
||||
// Now actually load nbextensionsload_extensions_from_config
|
||||
Promise.all([
|
||||
utils.load_extensions_from_config(config_section),
|
||||
utils.load_extensions_from_config(common_config),
|
||||
])
|
||||
.catch(function(error) {
|
||||
console.error('Could not load nbextensions from user config files', error);
|
||||
})
|
||||
// BEGIN HARDCODED WIDGETS HACK
|
||||
.then(function() {
|
||||
if (!utils.is_loaded('jupyter-js-widgets/extension')) {
|
||||
// Fallback to the ipywidgets extension
|
||||
utils.load_extension('widgets/notebook/js/extension').catch(function () {
|
||||
console.warn('Widgets are not available. Please install widgetsnbextension or ipywidgets 4.0');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Could not load ipywidgets', error);
|
||||
});
|
||||
// END HARDCODED WIDGETS HACK
|
||||
|
||||
notebook.load_notebook(common_options.notebook_path);
|
||||
|
||||
});
|
||||
@ -1,135 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'require',
|
||||
'./toolbar',
|
||||
'./celltoolbar',
|
||||
'base/js/i18n'
|
||||
], function($, requirejs, toolbar, celltoolbar, i18n) {
|
||||
"use strict";
|
||||
|
||||
var MainToolBar = function (selector, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* selector: string
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* notebook: Notebook instance
|
||||
**/
|
||||
toolbar.ToolBar.apply(this, [selector, options] );
|
||||
this.events = options.events;
|
||||
this.notebook = options.notebook;
|
||||
this._make();
|
||||
Object.seal(this);
|
||||
};
|
||||
|
||||
MainToolBar.prototype = Object.create(toolbar.ToolBar.prototype);
|
||||
|
||||
MainToolBar.prototype._make = function () {
|
||||
var grps = [
|
||||
[
|
||||
['jupyter-notebook:save-notebook'],
|
||||
'save-notbook'
|
||||
],
|
||||
[
|
||||
['jupyter-notebook:insert-cell-below'],
|
||||
'insert_above_below'],
|
||||
[
|
||||
['jupyter-notebook:cut-cell',
|
||||
'jupyter-notebook:copy-cell',
|
||||
'jupyter-notebook:paste-cell-below'
|
||||
] ,
|
||||
'cut_copy_paste'],
|
||||
[
|
||||
['jupyter-notebook:move-cell-up',
|
||||
'jupyter-notebook:move-cell-down'
|
||||
],
|
||||
'move_up_down'],
|
||||
[ [new toolbar.Button('jupyter-notebook:run-cell-and-select-next',
|
||||
{label: i18n.msg._('Run')}),
|
||||
'jupyter-notebook:interrupt-kernel',
|
||||
'jupyter-notebook:confirm-restart-kernel',
|
||||
'jupyter-notebook:confirm-restart-kernel-and-run-all-cells'
|
||||
],
|
||||
'run_int'],
|
||||
['<add_celltype_list>'],
|
||||
[
|
||||
['jupyter-notebook:show-command-palette'],
|
||||
'cmd_palette']
|
||||
];
|
||||
this.construct(grps);
|
||||
};
|
||||
|
||||
MainToolBar.prototype._pseudo_actions = {};
|
||||
|
||||
// add a cell type drop down to the maintoolbar.
|
||||
// triggered when the pseudo action `<add_celltype_list>` is
|
||||
// encountered when building a toolbar.
|
||||
MainToolBar.prototype._pseudo_actions.add_celltype_list = function () {
|
||||
var that = this;
|
||||
var multiselect = $('<option/>').attr('value','multiselect').attr('disabled','').text('-');
|
||||
var sel = $('<select/>')
|
||||
.attr('id','cell_type')
|
||||
.attr('aria-label', i18n.msg._('combobox, select cell type'))
|
||||
.attr('role', 'combobox')
|
||||
.addClass('form-control select-xs')
|
||||
.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) {
|
||||
if (data.editable === false) {
|
||||
sel.attr('disabled', true);
|
||||
} else {
|
||||
sel.removeAttr('disabled');
|
||||
}
|
||||
|
||||
if (that.notebook.get_selected_cells_indices().length > 1) {
|
||||
multiselect.show();
|
||||
sel.val('multiselect');
|
||||
} else {
|
||||
multiselect.hide();
|
||||
if (data.cell_type === 'heading') {
|
||||
sel.val('Markdown');
|
||||
} else {
|
||||
sel.val(data.cell_type);
|
||||
}
|
||||
}
|
||||
});
|
||||
sel.change(function () {
|
||||
var cell_type = $(this).val();
|
||||
switch (cell_type) {
|
||||
case 'code':
|
||||
that.notebook.cells_to_code();
|
||||
break;
|
||||
case 'markdown':
|
||||
that.notebook.cells_to_markdown();
|
||||
break;
|
||||
case 'raw':
|
||||
that.notebook.cells_to_raw();
|
||||
break;
|
||||
case 'heading':
|
||||
that.notebook._warn_heading();
|
||||
that.notebook.to_heading();
|
||||
sel.val('markdown');
|
||||
break;
|
||||
case 'multiselect':
|
||||
break;
|
||||
default:
|
||||
console.log(i18n.msg._("unrecognized cell type:"), cell_type);
|
||||
}
|
||||
that.notebook.focus_cell();
|
||||
});
|
||||
return sel;
|
||||
|
||||
};
|
||||
|
||||
return {'MainToolBar': MainToolBar};
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
define([
|
||||
'base/js/mathjaxutils'
|
||||
], function(mathjaxutils) {
|
||||
"use strict"
|
||||
|
||||
return mathjaxutils;
|
||||
});
|
||||
@ -1,469 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/namespace',
|
||||
'base/js/dialog',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'notebook/js/quickhelp',
|
||||
'./celltoolbar',
|
||||
'./tour',
|
||||
'moment',
|
||||
], function($, IPython, dialog, utils, i18n, quickhelp, celltoolbar, tour, moment) {
|
||||
"use strict";
|
||||
|
||||
var MenuBar = function (selector, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A MenuBar Class to generate the menubar of Jupyter notebook
|
||||
*
|
||||
* Parameters:
|
||||
* selector: string
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* notebook: Notebook instance
|
||||
* render keyboard shortcuts from KeyboardManager
|
||||
* contents: ContentManager instance
|
||||
* events: $(Events) instance
|
||||
* save_widget: SaveWidget instance
|
||||
* quick_help: QuickHelp instance
|
||||
* base_url : string
|
||||
* notebook_path : string
|
||||
* notebook_name : string
|
||||
* config: ConfigSection instance
|
||||
*/
|
||||
options = options || {};
|
||||
this.base_url = options.base_url || utils.get_body_data("baseUrl");
|
||||
this.selector = selector;
|
||||
this.notebook = options.notebook;
|
||||
this.keyboard_manager = this.notebook.keyboard_manager;
|
||||
this.actions = this.keyboard_manager.actions;
|
||||
this.contents = options.contents;
|
||||
this.events = options.events;
|
||||
this.save_widget = options.save_widget;
|
||||
this.quick_help = options.quick_help;
|
||||
this.actions = options.actions;
|
||||
this.config = options.config;
|
||||
|
||||
try {
|
||||
this.tour = new tour.Tour(this.notebook, this.events);
|
||||
} catch (e) {
|
||||
this.tour = undefined;
|
||||
console.log("Failed to instantiate Notebook Tour", e);
|
||||
}
|
||||
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.style();
|
||||
this.add_bundler_items();
|
||||
this.bind_events();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: This has definitively nothing to do with style ...
|
||||
MenuBar.prototype.style = function () {
|
||||
var that = this;
|
||||
this.element.find("li").click(function (event, ui) {
|
||||
// The selected cell loses focus when the menu is entered, so we
|
||||
// re-select it upon selection.
|
||||
var i = that.notebook.get_selected_index();
|
||||
that.notebook.select(i, false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
MenuBar.prototype.add_bundler_items = function() {
|
||||
var that = this;
|
||||
this.config.loaded.then(function() {
|
||||
var bundlers = that.config.data.bundlerextensions;
|
||||
if(bundlers) {
|
||||
// Stable sort the keys to ensure menu items don't hop around
|
||||
var ids = Object.keys(bundlers).sort()
|
||||
ids.forEach(function(bundler_id) {
|
||||
var bundler = bundlers[bundler_id];
|
||||
var group = that.element.find('#'+bundler.group+'_menu')
|
||||
|
||||
// Validate menu item metadata
|
||||
if(!group.length) {
|
||||
console.warn('unknown group', bundler.group, 'for bundler ID', bundler_id, '; skipping');
|
||||
return;
|
||||
} else if(!bundler.label) {
|
||||
console.warn('no label for bundler ID', bundler_id, '; skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert menu item into correct group, click handler
|
||||
group.parent().removeClass('hidden');
|
||||
var $li = $('<li>')
|
||||
.appendTo(group);
|
||||
$('<a>')
|
||||
.attr('href', '#')
|
||||
.text(bundler.label)
|
||||
.appendTo($li)
|
||||
.on('click', that._bundle.bind(that, bundler_id))
|
||||
.appendTo($li);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MenuBar.prototype._new_window = function(url) {
|
||||
var w = window.open('', IPython._target);
|
||||
if (this.notebook.dirty && this.notebook.writable) {
|
||||
this.notebook.save_notebook().then(function() {
|
||||
w.location = url;
|
||||
});
|
||||
} else {
|
||||
w.location = url;
|
||||
}
|
||||
};
|
||||
|
||||
MenuBar.prototype._bundle = function(bundler_id) {
|
||||
// Read notebook path and base url here in case they change
|
||||
var notebook_path = utils.encode_uri_components(this.notebook.notebook_path);
|
||||
var url = utils.url_path_join(
|
||||
this.base_url,
|
||||
'bundle',
|
||||
notebook_path
|
||||
) + '?bundler=' + utils.encode_uri_components(bundler_id);
|
||||
|
||||
this._new_window(url);
|
||||
};
|
||||
|
||||
MenuBar.prototype._nbconvert = function (format, download) {
|
||||
download = download || false;
|
||||
var notebook_path = utils.encode_uri_components(this.notebook.notebook_path);
|
||||
var url = utils.url_path_join(
|
||||
this.base_url,
|
||||
'nbconvert',
|
||||
format,
|
||||
notebook_path
|
||||
) + "?download=" + download.toString();
|
||||
|
||||
this._new_window(url);
|
||||
};
|
||||
|
||||
MenuBar.prototype._size_header = function() {
|
||||
/**
|
||||
* Update header spacer size.
|
||||
*/
|
||||
console.warn('`_size_header` is deprecated and will be removed in future versions.'+
|
||||
' Please trigger the `resize-header.Page` manually if you rely on it.');
|
||||
this.events.trigger('resize-header.Page');
|
||||
};
|
||||
|
||||
(function($){
|
||||
$(document).ready(function(){
|
||||
$('ul.dropdown-menu [data-toggle=dropdown]').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(this).parent().siblings().removeClass('open');
|
||||
$(this).parent().toggleClass('open');
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
MenuBar.prototype.bind_events = function () {
|
||||
/**
|
||||
* File
|
||||
*/
|
||||
var that = this;
|
||||
|
||||
|
||||
this.element.find('#open_notebook').click(function () {
|
||||
var parent = utils.url_path_split(that.notebook.notebook_path)[0];
|
||||
window.open(
|
||||
utils.url_path_join(
|
||||
that.base_url, 'tree',
|
||||
utils.encode_uri_components(parent)
|
||||
), IPython._target);
|
||||
});
|
||||
this.element.find('#copy_notebook').click(function () {
|
||||
that.notebook.copy_notebook();
|
||||
return false;
|
||||
});
|
||||
this.element.find('#save_notebook_as').click(function() {
|
||||
that.notebook.save_notebook_as();
|
||||
return false;
|
||||
});
|
||||
|
||||
this.element.find('#print_preview').click(function () {
|
||||
that._nbconvert('html', false);
|
||||
});
|
||||
|
||||
this.element.find('#download_menu li').click(function (ev) {
|
||||
that._nbconvert(ev.target.parentElement.getAttribute('id').substring(9), true);
|
||||
});
|
||||
|
||||
this.events.on('trust_changed.Notebook', function (event, trusted) {
|
||||
if (trusted) {
|
||||
that.element.find('#trust_notebook')
|
||||
.addClass("disabled").off('click')
|
||||
.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(i18n.msg._("Trust Notebook"));
|
||||
}
|
||||
});
|
||||
|
||||
// View
|
||||
this._add_celltoolbar_list();
|
||||
|
||||
// Edit
|
||||
this.element.find('#edit_nb_metadata').click(function () {
|
||||
that.notebook.edit_metadata({
|
||||
notebook: that.notebook,
|
||||
keyboard_manager: that.notebook.keyboard_manager});
|
||||
});
|
||||
|
||||
var id_actions_dict = {
|
||||
'#trust_notebook' : 'trust-notebook',
|
||||
'#rename_notebook' : 'rename-notebook',
|
||||
'#find_and_replace' : 'find-and-replace',
|
||||
'#save_checkpoint': 'save-notebook',
|
||||
'#shutdown_kernel': 'confirm-shutdown-kernel',
|
||||
'#restart_kernel': 'confirm-restart-kernel',
|
||||
'#restart_clear_output': 'confirm-restart-kernel-and-clear-output',
|
||||
'#restart_run_all': 'confirm-restart-kernel-and-run-all-cells',
|
||||
'#close_and_halt': 'close-and-halt',
|
||||
'#int_kernel': 'interrupt-kernel',
|
||||
'#cut_cell': 'cut-cell',
|
||||
'#copy_cell': 'copy-cell',
|
||||
'#paste_cell_above': 'paste-cell-above',
|
||||
'#paste_cell_below': 'paste-cell-below',
|
||||
'#paste_cell_replace': 'paste-cell-replace',
|
||||
'#delete_cell': 'delete-cell',
|
||||
'#undelete_cell': 'undo-cell-deletion',
|
||||
'#split_cell': 'split-cell-at-cursor',
|
||||
'#merge_cell_above': 'merge-cell-with-previous-cell',
|
||||
'#merge_cell_below': 'merge-cell-with-next-cell',
|
||||
'#move_cell_up': 'move-cell-up',
|
||||
'#move_cell_down': 'move-cell-down',
|
||||
'#toggle_header': 'toggle-header',
|
||||
'#toggle_toolbar': 'toggle-toolbar',
|
||||
'#toggle_line_numbers': 'toggle-all-line-numbers',
|
||||
'#insert_cell_above': 'insert-cell-above',
|
||||
'#insert_cell_below': 'insert-cell-below',
|
||||
'#run_cell': 'run-cell',
|
||||
'#run_cell_select_below': 'run-cell-and-select-next',
|
||||
'#run_cell_insert_below': 'run-cell-and-insert-below',
|
||||
'#run_all_cells': 'run-all-cells',
|
||||
'#run_all_cells_above': 'run-all-cells-above',
|
||||
'#run_all_cells_below': 'run-all-cells-below',
|
||||
'#to_code': 'change-cell-to-code',
|
||||
'#to_markdown': 'change-cell-to-markdown',
|
||||
'#to_raw': 'change-cell-to-raw',
|
||||
'#toggle_current_output': 'toggle-cell-output-collapsed',
|
||||
'#toggle_current_output_scroll': 'toggle-cell-output-scrolled',
|
||||
'#clear_current_output': 'clear-cell-output',
|
||||
'#toggle_all_output': 'toggle-all-cells-output-collapsed',
|
||||
'#toggle_all_output_scroll': 'toggle-all-cells-output-scrolled',
|
||||
'#clear_all_output': 'clear-all-cells-output',
|
||||
'#cut_cell_attachments': 'cut-cell-attachments',
|
||||
'#copy_cell_attachments': 'copy-cell-attachments',
|
||||
'#paste_cell_attachments': 'paste-cell-attachments',
|
||||
'#insert_image': 'insert-image',
|
||||
'#keyboard_shortcuts' : 'show-keyboard-shortcuts',
|
||||
'#edit_keyboard_shortcuts' : 'edit-command-mode-keyboard-shortcuts',
|
||||
};
|
||||
|
||||
for(var idx in id_actions_dict){
|
||||
if (!id_actions_dict.hasOwnProperty(idx)){
|
||||
continue;
|
||||
}
|
||||
var id_act = 'jupyter-notebook:'+id_actions_dict[idx];
|
||||
if(!that.actions.exists(id_act)){
|
||||
console.warn('actions', id_act, 'does not exist, still binding it in case it will be defined later...');
|
||||
}
|
||||
// Immediately-Invoked Function Expression cause JS.
|
||||
(function(that, id_act, idx){
|
||||
var el = that.element.find(idx);
|
||||
el.click(function(event){
|
||||
that.actions.call(id_act, event);
|
||||
});
|
||||
|
||||
var keybinding = that.keyboard_manager.command_shortcuts.get_action_shortcut(id_act) || that.keyboard_manager.edit_shortcuts.get_action_shortcut(id_act);
|
||||
|
||||
if (keybinding) {
|
||||
var shortcut = quickhelp.humanize_sequence(keybinding);
|
||||
var link_element = el.children('a');
|
||||
var text = link_element.text();
|
||||
link_element.text('');
|
||||
link_element.addClass('menu-shortcut-container');
|
||||
link_element.append('<span class="action">' + text + '</span>');
|
||||
link_element.append('<span class="kb">' + shortcut + '</span>');
|
||||
}
|
||||
})(that, id_act, idx);
|
||||
}
|
||||
|
||||
|
||||
// Kernel
|
||||
this.element.find('#reconnect_kernel').click(function () {
|
||||
that.notebook.kernel.reconnect();
|
||||
});
|
||||
// Help
|
||||
if (this.tour) {
|
||||
this.element.find('#notebook_tour').click(function () {
|
||||
that.tour.start();
|
||||
});
|
||||
} else {
|
||||
this.element.find('#notebook_tour').addClass("disabled");
|
||||
}
|
||||
|
||||
this.update_restore_checkpoint(null);
|
||||
|
||||
this.events.on('checkpoints_listed.Notebook', function (event, data) {
|
||||
that.update_restore_checkpoint(that.notebook.checkpoints);
|
||||
});
|
||||
|
||||
this.events.on('checkpoint_created.Notebook', function (event, data) {
|
||||
that.update_restore_checkpoint(that.notebook.checkpoints);
|
||||
});
|
||||
|
||||
this.events.on('notebook_loaded.Notebook', function() {
|
||||
var langinfo = that.notebook.metadata.language_info || {};
|
||||
that.update_nbconvert_script(langinfo);
|
||||
});
|
||||
|
||||
this.events.on('kernel_ready.Kernel', function(event, data) {
|
||||
var langinfo = data.kernel.info_reply.language_info || {};
|
||||
that.update_nbconvert_script(langinfo);
|
||||
that.add_kernel_help_links(data.kernel.info_reply.help_links || []);
|
||||
});
|
||||
};
|
||||
|
||||
MenuBar.prototype._add_celltoolbar_list = function () {
|
||||
var that = this;
|
||||
var submenu = $("#menu-cell-toolbar-submenu");
|
||||
|
||||
function preset_added(event, data) {
|
||||
var name = data.name;
|
||||
submenu.append(
|
||||
$("<li/>")
|
||||
.attr('data-name', encodeURIComponent(name))
|
||||
.append(
|
||||
$("<a/>")
|
||||
.attr('href', '#')
|
||||
.text(name)
|
||||
.click(function () {
|
||||
if (name ==='None') {
|
||||
celltoolbar.CellToolbar.global_hide();
|
||||
delete that.notebook.metadata.celltoolbar;
|
||||
} else {
|
||||
celltoolbar.CellToolbar.global_show();
|
||||
celltoolbar.CellToolbar.activate_preset(name, that.events);
|
||||
that.notebook.metadata.celltoolbar = name;
|
||||
}
|
||||
that.notebook.focus_cell();
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Setup the existing presets
|
||||
var presets = celltoolbar.CellToolbar.list_presets();
|
||||
preset_added(null, {name: i18n.msg._("None")});
|
||||
presets.map(function (name) {
|
||||
preset_added(null, {name: name});
|
||||
});
|
||||
|
||||
// Setup future preset registrations
|
||||
this.events.on('preset_added.CellToolbar', preset_added);
|
||||
|
||||
// Handle unregistered presets
|
||||
this.events.on('unregistered_preset.CellToolbar', function (event, data) {
|
||||
submenu.find("li[data-name='" + encodeURIComponent(data.name) + "']").remove();
|
||||
});
|
||||
};
|
||||
|
||||
MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
|
||||
var ul = this.element.find("#restore_checkpoint").find("ul");
|
||||
ul.empty();
|
||||
if (!checkpoints || checkpoints.length === 0) {
|
||||
ul.append(
|
||||
$("<li/>")
|
||||
.addClass("disabled")
|
||||
.append(
|
||||
$("<a/>")
|
||||
.text(i18n.msg._("No checkpoints"))
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
checkpoints.map(function (checkpoint) {
|
||||
var d = new Date(checkpoint.last_modified);
|
||||
ul.append(
|
||||
$("<li/>").append(
|
||||
$("<a/>")
|
||||
.attr("href", "#")
|
||||
.text(moment(d).format("LLLL"))
|
||||
.click(function () {
|
||||
that.notebook.restore_checkpoint_dialog(checkpoint);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
MenuBar.prototype.update_nbconvert_script = function(langinfo) {
|
||||
/**
|
||||
* Set the 'Download as foo' menu option for the relevant language.
|
||||
*/
|
||||
var el = this.element.find('#download_script');
|
||||
|
||||
// Set menu entry text to e.g. "Python (.py)"
|
||||
var langname = (langinfo.name || 'Script');
|
||||
langname = langname.charAt(0).toUpperCase()+langname.substr(1); // Capitalise
|
||||
el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
|
||||
};
|
||||
|
||||
MenuBar.prototype.add_kernel_help_links = function(help_links) {
|
||||
/** add links from kernel_info to the help menu */
|
||||
var divider = $("#kernel-help-links");
|
||||
if (divider.length === 0) {
|
||||
// insert kernel help section above about link
|
||||
var about = $("#notebook_about").parent();
|
||||
divider = $("<li>")
|
||||
.attr('id', "kernel-help-links")
|
||||
.addClass('divider');
|
||||
about.prev().before(divider);
|
||||
}
|
||||
// remove previous entries
|
||||
while (!divider.next().hasClass('divider')) {
|
||||
divider.next().remove();
|
||||
}
|
||||
if (help_links.length === 0) {
|
||||
// no help links, remove the divider
|
||||
divider.remove();
|
||||
return;
|
||||
}
|
||||
var cursor = divider;
|
||||
help_links.map(function (link) {
|
||||
cursor.after($("<li>")
|
||||
.append($("<a>")
|
||||
.attr('target', '_blank')
|
||||
.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")
|
||||
)
|
||||
.append($("<span>")
|
||||
.text(link.text)
|
||||
)
|
||||
)
|
||||
);
|
||||
cursor = cursor.next();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return {'MenuBar': MenuBar};
|
||||
});
|
||||
@ -1,414 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog',
|
||||
'base/js/notificationarea',
|
||||
'moment'
|
||||
], function($, utils, i18n, dialog, notificationarea, moment) {
|
||||
"use strict";
|
||||
|
||||
var NotificationArea = notificationarea.NotificationArea;
|
||||
|
||||
var NotebookNotificationArea = function(selector, options) {
|
||||
NotificationArea.apply(this, [selector, options]);
|
||||
this.save_widget = options.save_widget;
|
||||
this.notebook = options.notebook;
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
};
|
||||
|
||||
NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype);
|
||||
|
||||
/**
|
||||
* Initialize the default set of notification widgets.
|
||||
*
|
||||
* @method init_notification_widgets
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_notification_widgets = function () {
|
||||
this.init_kernel_notification_widget();
|
||||
this.init_notebook_notification_widget();
|
||||
this.init_trusted_notebook_notification_widget();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the notification widget for kernel status messages.
|
||||
*
|
||||
* @method init_kernel_notification_widget
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
|
||||
var that = this;
|
||||
var knw = this.widget('kernel');
|
||||
var $kernel_ind_icon = $("#kernel_indicator_icon");
|
||||
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() {
|
||||
if (that.notebook.writable) {
|
||||
$readonly_ind_icon.hide();
|
||||
} else {
|
||||
$readonly_ind_icon.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Command/Edit mode
|
||||
this.events.on('edit_mode.Notebook', function () {
|
||||
that.save_widget.update_document_title();
|
||||
$body.addClass('edit_mode');
|
||||
$body.removeClass('command_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',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',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(i18n.msg._("Kernel Created"), 500);
|
||||
});
|
||||
|
||||
this.events.on('kernel_reconnecting.Kernel', function () {
|
||||
knw.warning(i18n.msg._("Connecting to kernel"));
|
||||
});
|
||||
|
||||
this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
|
||||
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: i18n.msg._('click to reconnect')});
|
||||
});
|
||||
|
||||
this.events.on('kernel_connected.Kernel', function () {
|
||||
knw.info("Connected", 500);
|
||||
// trigger busy in the status to clear broken-link state immediately
|
||||
// a kernel_ready event will come when the kernel becomes responsive.
|
||||
$kernel_ind_icon
|
||||
.attr('class', 'kernel_busy_icon')
|
||||
.attr('title', i18n.msg._('Kernel Connected'));
|
||||
});
|
||||
|
||||
this.events.on('kernel_restarting.Kernel', function () {
|
||||
that.save_widget.update_document_title();
|
||||
knw.set_message(i18n.msg._("Restarting kernel"), 2000);
|
||||
});
|
||||
|
||||
this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
|
||||
// Only show the dialog on the first restart attempt. This
|
||||
// number gets tracked by the `Kernel` object and passed
|
||||
// along here, because we don't want to show the user 5
|
||||
// dialogs saying the same thing (which is the number of
|
||||
// times it tries restarting).
|
||||
if (info.attempt === 1) {
|
||||
|
||||
dialog.kernel_modal({
|
||||
notebook: that.notebook,
|
||||
keyboard_manager: that.keyboard_manager,
|
||||
title: i18n.msg._("Kernel Restarting"),
|
||||
body: i18n.msg._("The kernel appears to have died. It will restart automatically."),
|
||||
buttons: {
|
||||
OK : {
|
||||
class : "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
that.save_widget.update_document_title();
|
||||
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(i18n.msg._("Interrupting kernel"), 2000);
|
||||
});
|
||||
|
||||
this.events.on('kernel_disconnected.Kernel', function () {
|
||||
$kernel_ind_icon
|
||||
.attr('class', 'kernel_disconnected_icon')
|
||||
.attr('title', i18n.msg._('No Connection to Kernel'));
|
||||
});
|
||||
|
||||
this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
|
||||
// only show the dialog if this is the first failed
|
||||
// connect attempt, because the kernel will continue
|
||||
// trying to reconnect and we don't want to spam the user
|
||||
// with messages
|
||||
if (info.attempt === 1) {
|
||||
|
||||
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.");
|
||||
|
||||
var the_dialog = dialog.kernel_modal({
|
||||
title: i18n.msg._("Connection failed"),
|
||||
body: msg,
|
||||
keyboard_manager: that.keyboard_manager,
|
||||
notebook: that.notebook,
|
||||
buttons : {
|
||||
"OK": {}
|
||||
}
|
||||
});
|
||||
|
||||
// hide the dialog on reconnect if it's still visible
|
||||
var dismiss = function () {
|
||||
the_dialog.modal('hide');
|
||||
}
|
||||
that.events.on("kernel_connected.Kernel", dismiss);
|
||||
the_dialog.on("hidden.bs.modal", function () {
|
||||
// clear handler on dismiss
|
||||
that.events.off("kernel_connected.Kernel", dismiss);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
|
||||
that.save_widget.update_document_title();
|
||||
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 = 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.');
|
||||
|
||||
dialog.kernel_modal({
|
||||
title: i18n.msg._("Dead kernel"),
|
||||
body : msg,
|
||||
keyboard_manager: that.keyboard_manager,
|
||||
notebook: that.notebook,
|
||||
default_button: "Don't Restart",
|
||||
buttons : {
|
||||
"Don't Restart": {},
|
||||
"Try Restarting Now": {
|
||||
class: "btn-danger",
|
||||
click: function () {
|
||||
that.notebook.start_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
that.save_widget.update_document_title();
|
||||
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(i18n.msg._("No Kernel"));
|
||||
});
|
||||
|
||||
this.events.on('kernel_dead.Session', function (evt, info) {
|
||||
var full = info.xhr.responseJSON.message;
|
||||
var short = info.xhr.responseJSON.short_message || 'Kernel error';
|
||||
var traceback = info.xhr.responseJSON.traceback;
|
||||
|
||||
var showMsg = function () {
|
||||
var msg = $('<div/>').append($('<p/>').text(full));
|
||||
var cm, cm_elem, cm_open;
|
||||
|
||||
if (traceback) {
|
||||
cm_elem = $('<div/>')
|
||||
.css('margin-top', '1em')
|
||||
.css('padding', '1em')
|
||||
.addClass('output_scroll');
|
||||
msg.append(cm_elem);
|
||||
cm = new CodeMirror(cm_elem.get(0), {
|
||||
mode: "python",
|
||||
readOnly : true
|
||||
});
|
||||
cm.setValue(traceback);
|
||||
cm_open = $.proxy(cm.refresh, cm);
|
||||
}
|
||||
|
||||
dialog.kernel_modal({
|
||||
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' }
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
that.save_widget.update_document_title();
|
||||
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title',i18n.msg._('Kernel Dead'));
|
||||
knw.danger(short, undefined, showMsg);
|
||||
});
|
||||
|
||||
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',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',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',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',i18n.msg._('Kernel Busy'));
|
||||
set_busy_favicon(true);
|
||||
});
|
||||
|
||||
this.events.on('spec_match_found.Kernel', function (evt, data) {
|
||||
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',i18n.msg._('Kernel Busy'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the notification widget for notebook status messages.
|
||||
*
|
||||
* @method init_notebook_notification_widget
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
|
||||
var nnw = this.widget('notebook');
|
||||
|
||||
// Notebook events
|
||||
this.events.on('notebook_loading.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Loading notebook"),500);
|
||||
});
|
||||
this.events.on('notebook_loaded.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Notebook loaded"),500);
|
||||
});
|
||||
this.events.on('notebook_saving.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Saving notebook"),500);
|
||||
});
|
||||
this.events.on('notebook_saved.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Notebook saved"),2000);
|
||||
});
|
||||
this.events.on('notebook_save_failed.Notebook', function (evt, error) {
|
||||
nnw.warning(error.message || i18n.msg._("Notebook save failed"));
|
||||
});
|
||||
this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
|
||||
nnw.warning(error.message || i18n.msg._("Notebook copy failed"));
|
||||
});
|
||||
|
||||
// Checkpoint events
|
||||
this.events.on('checkpoint_created.Notebook', function (evt, data) {
|
||||
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");
|
||||
}
|
||||
nnw.set_message(msg, 2000);
|
||||
});
|
||||
this.events.on('checkpoint_failed.Notebook', function () {
|
||||
nnw.warning(i18n.msg._("Checkpoint failed"));
|
||||
});
|
||||
this.events.on('checkpoint_deleted.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Checkpoint deleted"), 500);
|
||||
});
|
||||
this.events.on('checkpoint_delete_failed.Notebook', function () {
|
||||
nnw.warning(i18n.msg._("Checkpoint delete failed"));
|
||||
});
|
||||
this.events.on('checkpoint_restoring.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Restoring to checkpoint..."), 500);
|
||||
});
|
||||
this.events.on('checkpoint_restore_failed.Notebook', function () {
|
||||
nnw.warning(i18n.msg._("Checkpoint restore failed"));
|
||||
});
|
||||
|
||||
// Autosave events
|
||||
this.events.on('autosave_disabled.Notebook', function () {
|
||||
nnw.set_message(i18n.msg._("Autosave disabled"), 2000);
|
||||
});
|
||||
this.events.on('autosave_enabled.Notebook', function (evt, interval) {
|
||||
nnw.set_message(i18n.msg.sprintf(i18n.msg._("Saving every %d sec."), interval / 1000) , 1000);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the notification widget for trusted notebook messages.
|
||||
*
|
||||
* @method init_trusted_notebook_notification_widget
|
||||
*/
|
||||
NotebookNotificationArea.prototype.init_trusted_notebook_notification_widget = function () {
|
||||
var that = this;
|
||||
var tnw = this.widget('trusted');
|
||||
|
||||
// Notebook trust events
|
||||
this.events.on('trust_changed.Notebook', function (event, trusted) {
|
||||
if (trusted) {
|
||||
tnw.set_message(i18n.msg._("Trusted"), undefined, function() {
|
||||
return false;
|
||||
}, {'title':'Javascript enabled for notebook display'});
|
||||
// don't allow 'Trusted' button to be clicked
|
||||
$(tnw.selector).attr('disabled', true);
|
||||
$(tnw.selector).css('cursor', 'help');
|
||||
} else {
|
||||
tnw.set_message(i18n.msg._("Not Trusted"), undefined, function() {
|
||||
that.notebook.trust_notebook("#notification_trusted");
|
||||
return false;
|
||||
}, {'title':'Javascript disabled for notebook display'});
|
||||
$(tnw.selector).attr('role', 'button');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {'NotebookNotificationArea': NotebookNotificationArea};
|
||||
});
|
||||
@ -1,187 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'jquery-ui'
|
||||
], function($, utils, i18n) {
|
||||
"use strict";
|
||||
|
||||
var Pager = function (pager_selector, options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* pager_selector: string
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
*/
|
||||
this.events = options.events;
|
||||
this.pager_element = $(pager_selector);
|
||||
this.pager_button_area = $('#pager-button-area');
|
||||
this._default_end_space = 100;
|
||||
this.pager_element.resizable({handles: 'n', resize: $.proxy(this._resize, this)});
|
||||
this.expanded = false;
|
||||
this.create_button_area();
|
||||
this.bind_events();
|
||||
};
|
||||
|
||||
Pager.prototype.create_button_area = function(){
|
||||
var that = this;
|
||||
this.pager_button_area.append(
|
||||
$('<a>').attr('role', "button")
|
||||
.attr('title',i18n.msg._("Open the pager in an external window"))
|
||||
.addClass('ui-button')
|
||||
.click(function(){that.detach();})
|
||||
.append(
|
||||
$('<span>').addClass("ui-icon ui-icon-extlink")
|
||||
)
|
||||
);
|
||||
this.pager_button_area.append(
|
||||
$('<a>').attr('role', "button")
|
||||
.attr('title',i18n.msg._("Close the pager"))
|
||||
.addClass('ui-button')
|
||||
.click(function(){that.collapse();})
|
||||
.append(
|
||||
$('<span>').addClass("ui-icon ui-icon-close")
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
|
||||
this.pager_element.bind('collapse_pager', function (event, extrap) {
|
||||
// Animate hiding of the pager.
|
||||
var time = (extrap && extrap.duration) ? extrap.duration : 'fast';
|
||||
that.pager_element.animate({
|
||||
height: 'toggle'
|
||||
}, {
|
||||
duration: time,
|
||||
done: function() {
|
||||
$('.end_space').css('height', that._default_end_space);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.pager_element.bind('expand_pager', function (event, extrap) {
|
||||
// Clear the pager's height attr if it's set. This allows the
|
||||
// pager to size itself according to its contents.
|
||||
that.pager_element.height('initial');
|
||||
|
||||
// Animate the showing of the pager
|
||||
var time = (extrap && extrap.duration) ? extrap.duration : 'fast';
|
||||
that.pager_element.show(time, function() {
|
||||
// Explicitly set pager height once the pager has shown itself.
|
||||
// This allows the pager-contents div to use percentage sizing.
|
||||
that.pager_element.height(that.pager_element.height());
|
||||
that._resize();
|
||||
|
||||
// HACK: Less horrible, but still horrible hack to force the
|
||||
// pager to show it's scrollbars on FireFox. ipython/ipython/#8853
|
||||
that.pager_element.css('position', 'relative');
|
||||
window.requestAnimationFrame(function() { /* Wait one frame */
|
||||
that.pager_element.css('position', '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.events.on('open_with_text.Pager', function (event, payload) {
|
||||
// FIXME: support other mime types with generic mimebundle display
|
||||
// mechanism
|
||||
if (payload.data['text/html'] && payload.data['text/html'] !== "") {
|
||||
that.clear();
|
||||
that.expand();
|
||||
that.append(payload.data['text/html']);
|
||||
} else if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
|
||||
that.clear();
|
||||
that.expand();
|
||||
that.append_text(payload.data['text/plain']);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype.collapse = function (extrap) {
|
||||
if (this.expanded === true) {
|
||||
this.expanded = false;
|
||||
this.pager_element.trigger('collapse_pager', extrap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype.expand = function (extrap) {
|
||||
if (this.expanded !== true) {
|
||||
this.expanded = true;
|
||||
this.pager_element.trigger('expand_pager', extrap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype.toggle = function () {
|
||||
if (this.expanded === true) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype.clear = function (text) {
|
||||
this.pager_element.find(".container").empty();
|
||||
};
|
||||
|
||||
Pager.prototype.detach = function(){
|
||||
var w = window.open("","_blank");
|
||||
$(w.document.head)
|
||||
.append(
|
||||
$('<link>')
|
||||
.attr('rel',"stylesheet")
|
||||
.attr('href', utils.url_path_join(utils.get_body_data('baseUrl'), "static/style/style.min.css"))
|
||||
.attr('type',"text/css")
|
||||
)
|
||||
.append(
|
||||
$('<title>').text(i18n.msg._("Jupyter Pager"))
|
||||
);
|
||||
var pager_body = $(w.document.body);
|
||||
pager_body.css('overflow','scroll');
|
||||
|
||||
pager_body.append(this.pager_element.clone().children());
|
||||
w.document.close();
|
||||
this.collapse();
|
||||
};
|
||||
|
||||
Pager.prototype.append_text = function (text) {
|
||||
/**
|
||||
* The only user content injected with this HTML call is escaped by
|
||||
* the fixConsole() method.
|
||||
*/
|
||||
this.pager_element.find(".container").append($('<pre/>').html(utils.fixConsole(utils.fixOverwrittenChars(text))));
|
||||
};
|
||||
|
||||
Pager.prototype.append = function (htm) {
|
||||
/**
|
||||
* The only user content injected with this HTML call is escaped by
|
||||
* the fixConsole() method.
|
||||
*/
|
||||
this.pager_element.find(".container").append(htm);
|
||||
};
|
||||
|
||||
|
||||
Pager.prototype._resize = function() {
|
||||
/**
|
||||
* Update document based on pager size.
|
||||
*/
|
||||
|
||||
// Make sure the padding at the end of the notebook is large
|
||||
// enough that the user can scroll to the bottom of the
|
||||
// notebook.
|
||||
$('.end_space').css('height', Math.max(this.pager_element.height(), this._default_end_space));
|
||||
};
|
||||
|
||||
return {'Pager': Pager};
|
||||
});
|
||||
@ -1,22 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Define promises for notebook events.
|
||||
|
||||
define(['base/js/events', 'base/js/promises'], function(events, promises) {
|
||||
"use strict";
|
||||
|
||||
// Promise to be resolved when the notebook is *initially* loaded.
|
||||
// The event may fire again if the notebook is reloaded later, but this
|
||||
// promise only tracks the initial load.
|
||||
promises.notebook_loaded = new Promise(function(resolve, reject) {
|
||||
events.one('notebook_loaded.Notebook', function() {
|
||||
resolve();
|
||||
});
|
||||
events.one('notebook_load_failed.Notebook', function() {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
|
||||
return promises;
|
||||
});
|
||||
@ -1,349 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog',
|
||||
'underscore'
|
||||
], function($, utils, i18n, dialog, _) {
|
||||
"use strict";
|
||||
|
||||
var platform = utils.platform;
|
||||
|
||||
var QuickHelp = function (options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
* notebook: Notebook instance
|
||||
*/
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
this.notebook = options.notebook;
|
||||
this.keyboard_manager.quick_help = this;
|
||||
this.events = options.events;
|
||||
};
|
||||
|
||||
var cmd_ctrl = 'Ctrl-';
|
||||
var platform_specific;
|
||||
|
||||
if (platform === 'MacOS') {
|
||||
// Mac OS X specific
|
||||
cmd_ctrl = 'Cmd-';
|
||||
platform_specific = [
|
||||
{ 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: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: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 = {
|
||||
// all these are unicode, will probably display badly on anything except macs.
|
||||
// these are the standard symbol that are used in MacOS native menus
|
||||
// cf https://apple.stackexchange.com/questions/55727/
|
||||
// for htmlentities and/or unicode value
|
||||
'cmd':'⌘',
|
||||
'shift':'⇧',
|
||||
'alt':'⌥',
|
||||
'up':'↑',
|
||||
'down':'↓',
|
||||
'left':'←',
|
||||
'right':'→',
|
||||
'eject':'⏏',
|
||||
'tab':'⇥',
|
||||
'backtab':'⇤',
|
||||
'capslock':'⇪',
|
||||
'esc':'esc',
|
||||
'ctrl':'⌃',
|
||||
'enter':'↩',
|
||||
'pageup':'⇞',
|
||||
'pagedown':'⇟',
|
||||
'home':'↖',
|
||||
'end':'↘',
|
||||
'altenter':'⌤',
|
||||
'space':'␣',
|
||||
'delete':'⌦',
|
||||
'backspace':'⌫',
|
||||
'apple':'',
|
||||
};
|
||||
|
||||
var default_humanize_map = {
|
||||
'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;
|
||||
|
||||
if (platform === 'MacOS'){
|
||||
humanize_map = mac_humanize_map;
|
||||
} else {
|
||||
humanize_map = default_humanize_map;
|
||||
}
|
||||
|
||||
var special_case = { pageup: i18n.msg._("PageUp"), pagedown: i18n.msg._("Page Down") };
|
||||
|
||||
function humanize_key(key){
|
||||
if (key.length === 1){
|
||||
return key.toUpperCase();
|
||||
}
|
||||
|
||||
key = humanize_map[key.toLowerCase()]||key;
|
||||
|
||||
if (key.indexOf(',') === -1){
|
||||
return ( special_case[key] ? special_case[key] : key.charAt(0).toUpperCase() + key.slice(1) );
|
||||
}
|
||||
}
|
||||
|
||||
// return an **html** string of the keyboard shortcut
|
||||
// for human eyes consumption.
|
||||
// the sequence is a string, comma separated linkt of shortcut,
|
||||
// where the shortcut is a list of dash-joined keys.
|
||||
// Each shortcut will be wrapped in <kbd> tag, and joined by comma is in a
|
||||
// sequence.
|
||||
//
|
||||
// Depending on the platform each shortcut will be normalized, with or without dashes.
|
||||
// and replace with the corresponding unicode symbol for modifier if necessary.
|
||||
function humanize_sequence(sequence){
|
||||
var joinchar = ',';
|
||||
var hum = _.map(sequence.replace(/meta/g, 'cmd').split(','), humanize_shortcut).join(joinchar);
|
||||
return hum;
|
||||
}
|
||||
|
||||
function _humanize_sequence(sequence){
|
||||
var joinchar = ',';
|
||||
var hum = _.map(sequence.replace(/meta/g, 'cmd').split(','), _humanize_shortcut).join(joinchar);
|
||||
return hum;
|
||||
}
|
||||
|
||||
function _humanize_shortcut(shortcut){
|
||||
var joinchar = '-';
|
||||
if (platform === 'MacOS'){
|
||||
joinchar = '';
|
||||
}
|
||||
return _.map(shortcut.split('-'), humanize_key ).join(joinchar);
|
||||
}
|
||||
|
||||
function humanize_shortcut(shortcut){
|
||||
return '<kbd>'+_humanize_shortcut(shortcut)+'</kbd>';
|
||||
}
|
||||
|
||||
|
||||
QuickHelp.prototype.show_keyboard_shortcuts = function () {
|
||||
/**
|
||||
* toggles display of keyboard shortcut dialog
|
||||
*/
|
||||
var that = this;
|
||||
if ( this.force_rebuild ) {
|
||||
this.shortcut_dialog.remove();
|
||||
delete(this.shortcut_dialog);
|
||||
this.force_rebuild = false;
|
||||
}
|
||||
if ( this.shortcut_dialog ){
|
||||
// if dialog is already shown, close it
|
||||
$(this.shortcut_dialog).modal("toggle");
|
||||
return;
|
||||
}
|
||||
var element = $('<div/>');
|
||||
|
||||
// The documentation
|
||||
var doc = $('<div/>').addClass('alert alert-info');
|
||||
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') {
|
||||
doc = $('<div/>').addClass('alert alert-info');
|
||||
var key_div = this.build_key_names();
|
||||
doc.append(key_div);
|
||||
element.append(doc);
|
||||
}
|
||||
|
||||
// Command mode
|
||||
var cmd_div = this.build_command_help();
|
||||
element.append(cmd_div);
|
||||
|
||||
// Edit mode
|
||||
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 : i18n.msg._("Keyboard shortcuts"),
|
||||
body : element,
|
||||
destroy : false,
|
||||
buttons : {
|
||||
Close : {}
|
||||
},
|
||||
notebook: this.notebook,
|
||||
keyboard_manager: this.keyboard_manager,
|
||||
});
|
||||
this.shortcut_dialog.addClass("modal_stretch");
|
||||
|
||||
this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
|
||||
};
|
||||
|
||||
QuickHelp.prototype.build_key_names = function () {
|
||||
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');
|
||||
var col1 = $('<div/>').addClass('col-md-6');
|
||||
var col2 = $('<div/>').addClass('col-md-6');
|
||||
n = key_names_mac.length;
|
||||
half = ~~(n/2);
|
||||
for (i=0; i<half; i++) { col1.append(
|
||||
build_one(key_names_mac[i])
|
||||
); }
|
||||
for (i=half; i<n; i++) { col2.append(
|
||||
build_one(key_names_mac[i])
|
||||
); }
|
||||
sub_div.append(col1).append(col2);
|
||||
div.append(sub_div);
|
||||
return div;
|
||||
};
|
||||
|
||||
|
||||
QuickHelp.prototype.build_command_help = function () {
|
||||
var that = this;
|
||||
var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
|
||||
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(i18n.msg._("Edit Shortcuts"))
|
||||
.addClass('btn btn-xs btn-default pull-right')
|
||||
.attr('href', '#')
|
||||
.attr('title', i18n.msg._('edit command-mode keyboard shortcuts'))
|
||||
.click(function () {
|
||||
// close this dialog
|
||||
$(that.shortcut_dialog).modal("toggle");
|
||||
// and open the next one
|
||||
$(that.shortcut_dialog).on('hidden.bs.modal', function (e) {
|
||||
that.keyboard_manager.actions.call(
|
||||
'jupyter-notebook:edit-command-mode-keyboard-shortcuts'
|
||||
);
|
||||
});
|
||||
});
|
||||
div.find('h4').append(edit_button);
|
||||
return div;
|
||||
};
|
||||
|
||||
|
||||
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>'+i18n.msg.sprintf(i18n.msg._('Edit Mode (press %s to enable)'),enterkey)+'</h4>', edit_shortcuts);
|
||||
};
|
||||
|
||||
var build_one = function (s) {
|
||||
var help = s.help;
|
||||
var shortcut = '';
|
||||
if(s.shortcut){
|
||||
shortcut = humanize_sequence(s.shortcut);
|
||||
}
|
||||
return $('<div>').addClass('quickhelp').
|
||||
append($('<span/>').addClass('shortcut_key').append($(shortcut))).
|
||||
append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
|
||||
|
||||
};
|
||||
|
||||
var build_div = function (title, shortcuts) {
|
||||
|
||||
// Remove jupyter-notebook:ignore shortcuts.
|
||||
shortcuts = shortcuts.filter(function(shortcut) {
|
||||
if (shortcut.help === 'ignore') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var i, half, n;
|
||||
var div = $('<div/>').append($(title));
|
||||
var sub_div = $('<div/>').addClass('container-fluid');
|
||||
var col1 = $('<div/>').addClass('col-md-6');
|
||||
var col2 = $('<div/>').addClass('col-md-6');
|
||||
n = shortcuts.length;
|
||||
half = ~~(n/2); // Truncate :)
|
||||
for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
|
||||
for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
|
||||
sub_div.append(col1).append(col2);
|
||||
div.append(sub_div);
|
||||
return div;
|
||||
};
|
||||
|
||||
return {'QuickHelp': QuickHelp,
|
||||
humanize_shortcut: humanize_shortcut,
|
||||
humanize_sequence: humanize_sequence,
|
||||
_humanize_sequence: _humanize_sequence,
|
||||
};
|
||||
});
|
||||
@ -1,228 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'base/js/dialog',
|
||||
'base/js/keyboard',
|
||||
'moment',
|
||||
'bidi/bidi',
|
||||
], function($, utils, i18n, dialog, keyboard, moment, bidi) {
|
||||
"use strict";
|
||||
|
||||
var SaveWidget = function (selector, options) {
|
||||
/**
|
||||
* TODO: Remove circular ref.
|
||||
*/
|
||||
this.notebook = undefined;
|
||||
this.selector = selector;
|
||||
this.events = options.events;
|
||||
this._checkpoint_date = undefined;
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.bind_events();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.bind_events = function () {
|
||||
var that = this;
|
||||
this.element.find('span.filename').click(function () {
|
||||
that.rename_notebook({notebook: that.notebook});
|
||||
});
|
||||
this.events.on('notebook_loaded.Notebook', function () {
|
||||
that.update_notebook_name();
|
||||
that.update_document_title();
|
||||
});
|
||||
this.events.on('notebook_saved.Notebook', function () {
|
||||
that.update_notebook_name();
|
||||
that.update_document_title();
|
||||
});
|
||||
this.events.on('notebook_renamed.Notebook', function () {
|
||||
that.update_notebook_name();
|
||||
that.update_document_title();
|
||||
that.update_address_bar();
|
||||
});
|
||||
this.events.on('notebook_save_failed.Notebook', function () {
|
||||
that.set_save_status(i18n.msg._('Autosave Failed!'));
|
||||
});
|
||||
this.events.on('notebook_read_only.Notebook', function () {
|
||||
that.set_save_status('(read only)');
|
||||
// disable future set_save_status
|
||||
that.set_save_status = function () {};
|
||||
});
|
||||
this.events.on('checkpoints_listed.Notebook', function (event, data) {
|
||||
that._set_last_checkpoint(data[0]);
|
||||
});
|
||||
|
||||
this.events.on('checkpoint_created.Notebook', function (event, data) {
|
||||
that._set_last_checkpoint(data);
|
||||
});
|
||||
this.events.on('set_dirty.Notebook', function (event, data) {
|
||||
that.set_autosaved(data.value);
|
||||
});
|
||||
};
|
||||
|
||||
// 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(i18n.msg._('Enter a new notebook name:'))
|
||||
).append(
|
||||
$("<br/>")
|
||||
).append(
|
||||
$('<input/>').attr('type','text').attr('size','25').addClass('form-control')
|
||||
.val(options.notebook.get_notebook_name())
|
||||
);
|
||||
var d = dialog.modal({
|
||||
title: i18n.msg._("Rename Notebook"),
|
||||
body: dialog_body,
|
||||
notebook: options.notebook,
|
||||
keyboard_manager: this.keyboard_manager,
|
||||
default_button: "Cancel",
|
||||
buttons : {
|
||||
"Cancel": {},
|
||||
"Rename": {
|
||||
class: "btn-primary",
|
||||
click: function () {
|
||||
var new_name = d.find('input').val();
|
||||
if (!options.notebook.test_notebook_name(new_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(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 || i18n.msg._('Unknown error'));
|
||||
d.find('input[type="text"]').prop('disabled', false).focus().select();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
open : function () {
|
||||
/**
|
||||
* Upon ENTER, click the OK button.
|
||||
*/
|
||||
d.find('input[type="text"]').keydown(function (event) {
|
||||
if (event.which === keyboard.keycodes.enter) {
|
||||
d.find('.btn-primary').first().click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
d.find('input[type="text"]').focus().select();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.update_notebook_name = function () {
|
||||
var nbname = this.notebook.get_notebook_name();
|
||||
nbname = bidi.applyBidi(nbname);
|
||||
this.element.find('span.filename').text(nbname);
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.update_document_title = function () {
|
||||
var nbname = this.notebook.get_notebook_name();
|
||||
document.title = nbname + ' - Jupyter Notebook';
|
||||
};
|
||||
|
||||
SaveWidget.prototype.update_address_bar = function(){
|
||||
var base_url = this.notebook.base_url;
|
||||
var path = this.notebook.notebook_path;
|
||||
var state = {path : path};
|
||||
window.history.replaceState(state, "", utils.url_path_join(
|
||||
base_url,
|
||||
"notebooks",
|
||||
utils.encode_uri_components(path))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype.set_save_status = function (msg) {
|
||||
this.element.find('span.autosave_status').text(msg);
|
||||
};
|
||||
|
||||
SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
|
||||
if (checkpoint) {
|
||||
this._checkpoint_date = new Date(checkpoint.last_modified);
|
||||
} else {
|
||||
this._checkpoint_date = null;
|
||||
}
|
||||
this._render_checkpoint();
|
||||
};
|
||||
|
||||
SaveWidget.prototype._render_checkpoint = function () {
|
||||
/** actually set the text in the element, from our _checkpoint value
|
||||
|
||||
called directly, and periodically in timeouts.
|
||||
*/
|
||||
this._schedule_render_checkpoint();
|
||||
var el = this.element.find('span.checkpoint_status');
|
||||
if (!this._checkpoint_date) {
|
||||
el.text('').attr('title', i18n.msg._('no checkpoint'));
|
||||
return;
|
||||
}
|
||||
var chkd = moment(this._checkpoint_date);
|
||||
var long_date = chkd.format('llll');
|
||||
var human_date;
|
||||
var tdelta = Math.ceil(new Date() - this._checkpoint_date);
|
||||
if (tdelta < utils.time.milliseconds.d){
|
||||
// less than 24 hours old, use relative date
|
||||
human_date = chkd.fromNow();
|
||||
} else {
|
||||
// otherwise show calendar
|
||||
// <Today | yesterday|...> at hh,mm,ss
|
||||
human_date = chkd.calendar();
|
||||
}
|
||||
|
||||
el.text(i18n.msg.sprintf(i18n.msg._('Last Checkpoint: %s'),human_date)).attr('title', long_date);
|
||||
};
|
||||
|
||||
|
||||
SaveWidget.prototype._schedule_render_checkpoint = function () {
|
||||
/** schedule the next update to relative date
|
||||
|
||||
periodically updated, so short values like 'a few seconds ago' don't get stale.
|
||||
*/
|
||||
if (!this._checkpoint_date) {
|
||||
return;
|
||||
}
|
||||
if ((this._checkpoint_timeout)) {
|
||||
clearTimeout(this._checkpoint_timeout);
|
||||
}
|
||||
var dt = Math.ceil(new Date() - this._checkpoint_date);
|
||||
this._checkpoint_timeout = setTimeout(
|
||||
$.proxy(this._render_checkpoint, this),
|
||||
utils.time.timeout_from_dt(dt)
|
||||
);
|
||||
};
|
||||
|
||||
SaveWidget.prototype.set_autosaved = function (dirty) {
|
||||
if (dirty) {
|
||||
this.set_save_status(i18n.msg._("(unsaved changes)"));
|
||||
} else {
|
||||
this.set_save_status(i18n.msg._("(autosaved)"));
|
||||
}
|
||||
};
|
||||
|
||||
return {'SaveWidget': SaveWidget};
|
||||
|
||||
});
|
||||
@ -1,232 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
define(['jquery'], function($) {
|
||||
"use strict";
|
||||
|
||||
var ScrollManager = function(notebook, options) {
|
||||
/**
|
||||
* Public constructor.
|
||||
*/
|
||||
this.notebook = notebook;
|
||||
this.element = $('#site');
|
||||
options = options || {};
|
||||
this.animation_speed = options.animation_speed || 250; //ms
|
||||
};
|
||||
|
||||
ScrollManager.prototype.onScroll = function (func, rate) {
|
||||
/**
|
||||
* Register a function to be called when the page is scrolled, throttled
|
||||
* at a particular rate limit.
|
||||
*/
|
||||
rate = rate || 100; // default rate limit
|
||||
this.element.scroll(function () {
|
||||
clearTimeout(func._timeout);
|
||||
func._timeout = setTimeout(func, rate);
|
||||
});
|
||||
};
|
||||
|
||||
ScrollManager.prototype.scroll = function (delta) {
|
||||
/**
|
||||
* Scroll the document.
|
||||
*
|
||||
* Parameters
|
||||
* ----------
|
||||
* delta: integer
|
||||
* direction to scroll the document. Positive is downwards.
|
||||
* Unit is one page length.
|
||||
*/
|
||||
this.scroll_some(delta);
|
||||
return false;
|
||||
};
|
||||
|
||||
ScrollManager.prototype.scroll_to = function(selector) {
|
||||
/**
|
||||
* Scroll to an element in the notebook.
|
||||
*/
|
||||
this.element.animate({'scrollTop': $(selector).offset().top + this.element.scrollTop() - this.element.offset().top}, this.animation_speed);
|
||||
};
|
||||
|
||||
ScrollManager.prototype.scroll_some = function(pages) {
|
||||
/**
|
||||
* Scroll up or down a given number of pages.
|
||||
*
|
||||
* Parameters
|
||||
* ----------
|
||||
* pages: integer
|
||||
* number of pages to scroll the document, may be positive or negative.
|
||||
*/
|
||||
this.element.animate({'scrollTop': this.element.scrollTop() + pages * this.element.height()}, this.animation_speed);
|
||||
};
|
||||
|
||||
ScrollManager.prototype.get_first_visible_cell = function() {
|
||||
/**
|
||||
* Gets the index of the first visible cell in the document.
|
||||
*
|
||||
* First, attempt to be smart by guessing the index of the cell we are
|
||||
* scrolled to. Then, walk from there up or down until the right cell
|
||||
* is found. To guess the index, get the top of the last cell, and
|
||||
* divide that by the number of cells to get an average cell height.
|
||||
* Then divide the scroll height by the average cell height.
|
||||
*/
|
||||
var cell_count = this.notebook.ncells();
|
||||
var first_cell_top = this.notebook.get_cell(0).element.offset().top;
|
||||
var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top;
|
||||
var avg_cell_height = (last_cell_top - first_cell_top) / cell_count;
|
||||
var i = Math.ceil(this.element.scrollTop() / avg_cell_height);
|
||||
i = Math.min(Math.max(i , 0), cell_count - 1);
|
||||
|
||||
while (this.notebook.get_cell(i).element.offset().top - first_cell_top < this.element.scrollTop() && i < cell_count - 1) {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while (this.notebook.get_cell(i).element.offset().top - first_cell_top > this.element.scrollTop() - 50 && i >= 0) {
|
||||
i -= 1;
|
||||
}
|
||||
return Math.min(i + 1, cell_count - 1);
|
||||
};
|
||||
|
||||
ScrollManager.prototype.is_cell_visible = function (cell) {
|
||||
var cell_rect = cell.element[0].getBoundingClientRect();
|
||||
var scroll_rect = this.element[0].getBoundingClientRect();
|
||||
return ((cell_rect.top <= scroll_rect.bottom) && (cell_rect.bottom >= scroll_rect.top));
|
||||
};
|
||||
|
||||
|
||||
var TargetScrollManager = function(notebook, options) {
|
||||
/**
|
||||
* Public constructor.
|
||||
*/
|
||||
ScrollManager.apply(this, [notebook, options]);
|
||||
};
|
||||
TargetScrollManager.prototype = Object.create(ScrollManager.prototype);
|
||||
|
||||
TargetScrollManager.prototype.is_target = function (index) {
|
||||
/**
|
||||
* Check if a cell should be a scroll stop.
|
||||
*
|
||||
* Returns `true` if the cell is a cell that the scroll manager
|
||||
* should scroll to. Otherwise, false is returned.
|
||||
*
|
||||
* Parameters
|
||||
* ----------
|
||||
* index: integer
|
||||
* index of the cell to test.
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
|
||||
TargetScrollManager.prototype.scroll = function (delta) {
|
||||
/**
|
||||
* Scroll the document.
|
||||
*
|
||||
* Parameters
|
||||
* ----------
|
||||
* delta: integer
|
||||
* direction to scroll the document. Positive is downwards.
|
||||
* Units are targets.
|
||||
*
|
||||
* Try to scroll to the next slide.
|
||||
*/
|
||||
var cell_count = this.notebook.ncells();
|
||||
var selected_index = this.get_first_visible_cell() + delta;
|
||||
while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) {
|
||||
selected_index += delta;
|
||||
}
|
||||
|
||||
if (selected_index < 0 || cell_count <= selected_index) {
|
||||
return ScrollManager.prototype.scroll.apply(this, [delta]);
|
||||
} else {
|
||||
this.scroll_to(this.notebook.get_cell(selected_index).element);
|
||||
|
||||
// Cancel browser keyboard scroll.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var SlideScrollManager = function(notebook, options) {
|
||||
/**
|
||||
* Public constructor.
|
||||
*/
|
||||
TargetScrollManager.apply(this, [notebook, options]);
|
||||
};
|
||||
SlideScrollManager.prototype = Object.create(TargetScrollManager.prototype);
|
||||
|
||||
SlideScrollManager.prototype.is_target = function (index) {
|
||||
var cell = this.notebook.get_cell(index);
|
||||
return cell.metadata && cell.metadata.slideshow &&
|
||||
cell.metadata.slideshow.slide_type &&
|
||||
(cell.metadata.slideshow.slide_type === "slide" ||
|
||||
cell.metadata.slideshow.slide_type === "subslide");
|
||||
};
|
||||
|
||||
|
||||
var HeadingScrollManager = function(notebook, options) {
|
||||
/**
|
||||
* Public constructor.
|
||||
*/
|
||||
ScrollManager.apply(this, [notebook, options]);
|
||||
options = options || {};
|
||||
this._level = options.heading_level || 1;
|
||||
};
|
||||
HeadingScrollManager.prototype = Object.create(ScrollManager.prototype);
|
||||
|
||||
HeadingScrollManager.prototype.scroll = function (delta) {
|
||||
/**
|
||||
* Scroll the document.
|
||||
*
|
||||
* Parameters
|
||||
* ----------
|
||||
* delta: integer
|
||||
* direction to scroll the document. Positive is downwards.
|
||||
* Units are headers.
|
||||
*
|
||||
* Get all of the header elements that match the heading level or are of
|
||||
* greater magnitude (a smaller header number).
|
||||
*/
|
||||
var headers = $();
|
||||
var i;
|
||||
for (i = 1; i <= this._level; i++) {
|
||||
headers = headers.add('#notebook-container h' + i);
|
||||
}
|
||||
|
||||
// Find the header the user is on or below.
|
||||
var first_cell_top = this.notebook.get_cell(0).element.offset().top;
|
||||
var current_scroll = this.element.scrollTop();
|
||||
var header_scroll = 0;
|
||||
i = -1;
|
||||
while (current_scroll >= header_scroll && i < headers.length) {
|
||||
if (++i < headers.length) {
|
||||
header_scroll = $(headers[i]).offset().top - first_cell_top;
|
||||
}
|
||||
}
|
||||
i--;
|
||||
|
||||
// Check if the user is below the header.
|
||||
if (i < 0 || current_scroll > $(headers[i]).offset().top - first_cell_top + 30) {
|
||||
// Below the header, count the header as a target.
|
||||
if (delta < 0) {
|
||||
delta += 1;
|
||||
}
|
||||
}
|
||||
i += delta;
|
||||
|
||||
// Scroll!
|
||||
if (0 <= i && i < headers.length) {
|
||||
this.scroll_to(headers[i]);
|
||||
return false;
|
||||
} else {
|
||||
// Default to the base's scroll behavior when target header doesn't
|
||||
// exist.
|
||||
return ScrollManager.prototype.scroll.apply(this, [delta]);
|
||||
}
|
||||
};
|
||||
|
||||
// Return namespace for require.js loads
|
||||
return {
|
||||
'ScrollManager': ScrollManager,
|
||||
'SlideScrollManager': SlideScrollManager,
|
||||
'HeadingScrollManager': HeadingScrollManager,
|
||||
'TargetScrollManager': TargetScrollManager
|
||||
};
|
||||
});
|
||||
@ -1,401 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/dialog',
|
||||
'base/js/i18n'
|
||||
], function($, dialog, i18n){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* escape a Regular expression to act as a pure search string.
|
||||
* though it will still have the case sensitivity options and all
|
||||
* the benefits
|
||||
**/
|
||||
function escapeRegExp(string){
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the model of the preview for the search and replace.
|
||||
* It might not be perfectly accurate if matches overlap...
|
||||
* Parameter:
|
||||
* sre: the string that will become the Search Regular Expression
|
||||
* arr: a list of string on which the match will be applied.
|
||||
* isCaseSensitive: should the match be CaseSensitive
|
||||
* RegExOrNot: a `RegExOrNot` object.
|
||||
* replace: the replacement string for the matching `sre`
|
||||
* Return: a tuple of 2 value:
|
||||
* 1) array of [before match, match, replacement, after match]
|
||||
* where before and after match are cut to a reasonable length after the match.
|
||||
* 2) Boolean, whether the matching has been aborted because one of the element of
|
||||
* arr have too many matches.
|
||||
**/
|
||||
var compute_preview_model = function(sre, arr, isCaseSensitive, RegExpOrNot, replace){
|
||||
var html = [];
|
||||
// and create an array of
|
||||
// before_match, match , replacement, after_match
|
||||
var aborted = false;
|
||||
var replacer_reg = new RegExpOrNot(sre);
|
||||
for(var r=0; r < arr.length; r++){
|
||||
var current_line = arr[r];
|
||||
var match_abort = getMatches(sre, current_line, isCaseSensitive, RegExpOrNot);
|
||||
aborted = aborted || match_abort[1];
|
||||
var matches = match_abort[0];
|
||||
for(var mindex=0; mindex < matches.length ; mindex++){
|
||||
var start = matches[mindex][0];
|
||||
var stop = matches[mindex][1];
|
||||
var initial = current_line.slice(start, stop);
|
||||
var replaced = initial.replace(replacer_reg, replace);
|
||||
// that might be better as a dictionary
|
||||
html.push([cutBefore(current_line.slice(0, start)),
|
||||
initial,
|
||||
replaced,
|
||||
cutAfter(current_line.slice(stop), 30-(stop-start))]);
|
||||
}
|
||||
}
|
||||
return [html, aborted];
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the preview model where things matched and their replacement values
|
||||
* are wrapped in tags with correct CSS classes.
|
||||
* Parameter:
|
||||
* body: jQuery element into which the preview will be build
|
||||
* aborted : have the model been aborted (Boolean) use to tell the user
|
||||
* that the preview might not show all the replacements
|
||||
* html: array of model created by compute_preview_model
|
||||
* replace: Boolean: whether we are actually replacing with something or just matching.
|
||||
**/
|
||||
var build_preview = function(body, aborted, html, replace){
|
||||
body.empty();
|
||||
if(aborted){
|
||||
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 {
|
||||
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/>')
|
||||
.append(html[rindex][0])
|
||||
.append($('<span/>').addClass('match').text(html[rindex][1]));
|
||||
if(replace){
|
||||
pre.append($('<span/>').addClass('insert').text(html[rindex][2]));
|
||||
pre.addClass('replace');
|
||||
}
|
||||
pre.append(html[rindex][3]);
|
||||
body.append(pre);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string, return only the beginning, with potentially an ellipsis
|
||||
* at the end.
|
||||
**/
|
||||
var cutAfter = function(string, n){
|
||||
n=n||10;
|
||||
while(n<10){
|
||||
n+=15;
|
||||
}
|
||||
if(string.length > n+3){
|
||||
return string.slice(0, n)+'...';
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string, return only the end, with potentially an ellipsis
|
||||
* at the beginning.
|
||||
**/
|
||||
var cutBefore = function(string){
|
||||
if(string.length > 33){
|
||||
return '...'+string.slice(-30);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all occurrences of `re` in `string`, match in a `caseSensitive`
|
||||
* manner or not, and determine whether `re` is a RegExp or not depending of
|
||||
* the type of object passed as `r`.
|
||||
*
|
||||
* Return a tuple
|
||||
* 1) list of matches [start, stop] indexes in the string.
|
||||
* 2) abort Boolean, if more that 100 matches and the matches were aborted.
|
||||
**/
|
||||
var getMatches = function(re, string, caseSensitive, r){
|
||||
var extra = caseSensitive ? '':'i';
|
||||
extra = '';
|
||||
try {
|
||||
re = r(re, 'g'+extra);// have to global or infinite loop
|
||||
} catch (e){
|
||||
return [[], false];
|
||||
}
|
||||
var res = [];
|
||||
var match;
|
||||
var escape_hatch = 0;
|
||||
var abort = false;
|
||||
while((match = re.exec(string)) !== null) {
|
||||
res.push([match.index, match.index+match[0].length]);
|
||||
escape_hatch++;
|
||||
if(escape_hatch > 100){
|
||||
console.warn(i18n.msg._("More than 100 matches, aborting"));
|
||||
abort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [res, abort];
|
||||
};
|
||||
|
||||
// main function
|
||||
/**
|
||||
* Search N' Replace action handler.
|
||||
**/
|
||||
var snr = function(env, event) {
|
||||
|
||||
var isRegExpButton = $('<button/>')
|
||||
.attr('type', 'button')
|
||||
.attr('id', 'isreg')
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.css('font-weight', 'bold')
|
||||
.attr('title', i18n.msg._('Use regex (JavaScript regex syntax)'))
|
||||
.text('.*');
|
||||
|
||||
var allCellsButton = $('<button/>')
|
||||
.append($('<i/>').addClass('fa fa-arrows-v'))
|
||||
.attr('id', 'findreplace_allcells_btn')
|
||||
.attr('type', 'button')
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.attr('title', i18n.msg._('Replace in all cells'));
|
||||
|
||||
var isCaseSensitiveButton = $('<button/>')
|
||||
.attr('type', 'button')
|
||||
.addClass("btn btn-default btn-sm")
|
||||
.attr('data-toggle','button')
|
||||
.attr('tabindex', '0')
|
||||
.attr('title', i18n.msg._('Match case'))
|
||||
.css('font-weight', 'bold')
|
||||
.text('Aa');
|
||||
|
||||
var search = $("<input/>")
|
||||
.addClass('form-control input-sm')
|
||||
.attr('id', 'findreplace_find_inp')
|
||||
.attr('placeholder',i18n.msg._('Find'));
|
||||
|
||||
var findFormGroup = $('<div/>').addClass('form-group');
|
||||
findFormGroup.append(
|
||||
$('<div/>').addClass('input-group')
|
||||
.append(
|
||||
$('<div/>').addClass('input-group-btn')
|
||||
.append(isCaseSensitiveButton)
|
||||
.append(isRegExpButton)
|
||||
.append(allCellsButton)
|
||||
)
|
||||
.append(search)
|
||||
)
|
||||
|
||||
var replace = $("<input/>")
|
||||
.attr('id', 'findreplace_replace_inp')
|
||||
.addClass('form-control input-sm')
|
||||
.attr('placeholder',i18n.msg._('Replace'));
|
||||
var replaceFormGroup = $('<div/>').addClass('form-group');
|
||||
replaceFormGroup.append(replace);
|
||||
|
||||
var body = $('<div/>').attr('id', 'replace-preview');
|
||||
|
||||
var form = $('<form/>').attr('id', 'find-and-replace')
|
||||
form.append(findFormGroup);
|
||||
form.append(replaceFormGroup);
|
||||
form.append(body);
|
||||
|
||||
// return whether the search is case sensitive
|
||||
var isCaseSensitive = function(){
|
||||
var value = isCaseSensitiveButton.attr('aria-pressed') == 'true';
|
||||
return value;
|
||||
};
|
||||
|
||||
// return whether the search is RegExp based, or
|
||||
// plain string matching.
|
||||
var isReg = function(){
|
||||
var value = isRegExpButton.attr('aria-pressed') == 'true';
|
||||
return value;
|
||||
};
|
||||
|
||||
var allCells = function(){
|
||||
return (allCellsButton.attr('aria-pressed') == 'true');
|
||||
};
|
||||
|
||||
|
||||
// return a Pseudo RegExp object that acts
|
||||
// either as a plain RegExp Object, or as a pure string matching.
|
||||
// automatically set the flags for case sensitivity from the UI
|
||||
var RegExpOrNot = function(str, flags){
|
||||
if (!isCaseSensitive()){
|
||||
flags = (flags || '')+'i';
|
||||
}
|
||||
if (isRegExpButton.attr('aria-pressed') === 'true'){
|
||||
return new RegExp(str, flags);
|
||||
} else {
|
||||
return new RegExp(escapeRegExp(str), flags);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var onError = function(body){
|
||||
body.empty();
|
||||
body.append($('<p/>').text(i18n.msg._('No matches, invalid or empty regular expression')));
|
||||
};
|
||||
|
||||
var get_cells = function(env){
|
||||
if(allCells()){
|
||||
return env.notebook.get_cells();
|
||||
} else {
|
||||
return env.notebook.get_selected_cells();
|
||||
}
|
||||
};
|
||||
|
||||
var get_all_text = function(cells) {
|
||||
var arr = [];
|
||||
for (var c = 0; c < cells.length; c++) {
|
||||
arr = arr.concat(cells[c].code_mirror.getValue().split('\n'));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* callback triggered anytime a change is made to the
|
||||
* request, case sensitivity, isregex, search or replace
|
||||
* modification.
|
||||
**/
|
||||
var onChange = function(){
|
||||
|
||||
var sre = search.val();
|
||||
// abort on invalid RE
|
||||
if (!sre) {
|
||||
return onError(body);
|
||||
}
|
||||
try {
|
||||
new RegExpOrNot(sre);
|
||||
} catch (e) {
|
||||
return onError(body);
|
||||
}
|
||||
|
||||
// might want to warn if replace is empty
|
||||
var replaceValue = replace.val();
|
||||
var lines = get_all_text(get_cells(env));
|
||||
|
||||
var _hb = compute_preview_model(sre, lines, isCaseSensitive(), RegExpOrNot, replaceValue);
|
||||
var html = _hb[0];
|
||||
var aborted = _hb[1];
|
||||
|
||||
build_preview(body, aborted, html, replaceValue);
|
||||
|
||||
// done on type return false not to submit form
|
||||
return false;
|
||||
};
|
||||
|
||||
var onsubmit = function(event) {
|
||||
var sre = search.val();
|
||||
var replaceValue = replace.val();
|
||||
if (!sre) {
|
||||
return false;
|
||||
}
|
||||
// should abort on invalid RegExp.
|
||||
|
||||
// need to be multi line if we want to directly replace in codemirror.
|
||||
// or need to split/replace/join
|
||||
var reg = RegExpOrNot(sre, 'gm');
|
||||
var cells = get_cells(env);
|
||||
for (var c = 0; c < cells.length; c++) {
|
||||
var cell = cells[c];
|
||||
if (!cell.is_editable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldvalue = cell.code_mirror.getValue();
|
||||
var newvalue = oldvalue.replace(reg , replaceValue);
|
||||
cell.code_mirror.setValue(newvalue);
|
||||
if (cell.cell_type === 'markdown') {
|
||||
cell.rendered = false;
|
||||
cell.render();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// wire-up the UI
|
||||
|
||||
isRegExpButton.click(function(){
|
||||
search.focus();
|
||||
setTimeout(function(){onChange();}, 100);
|
||||
});
|
||||
|
||||
isCaseSensitiveButton.click(function(){
|
||||
search.focus();
|
||||
setTimeout(function(){onChange();}, 100);
|
||||
});
|
||||
|
||||
allCellsButton.click(function(){
|
||||
replace.focus();
|
||||
setTimeout(function(){onChange();}, 100);
|
||||
});
|
||||
|
||||
|
||||
search.keypress(function (e) {
|
||||
if (e.which == 13) {//enter
|
||||
replace.focus();
|
||||
}
|
||||
});
|
||||
|
||||
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: i18n.msg._("Find and Replace"),
|
||||
body:form,
|
||||
keyboard_manager: env.notebook.keyboard_manager,
|
||||
buttons:{
|
||||
'Replace All':{ class: "btn-primary",
|
||||
click: function(event){onsubmit(event); return true;},
|
||||
id: "findreplace_replaceall_btn",
|
||||
}
|
||||
},
|
||||
open: function(){
|
||||
search.focus();
|
||||
}
|
||||
});
|
||||
|
||||
replace.keypress(function (e) {
|
||||
if (e.which == 13) {//enter
|
||||
onsubmit();
|
||||
mod.modal('hide');
|
||||
}
|
||||
});
|
||||
mod.modal('show');
|
||||
};
|
||||
|
||||
|
||||
var load = function(keyboard_manager){
|
||||
var action_all = {
|
||||
cmd: i18n.msg._('find and replace'),
|
||||
help: i18n.msg._('find and replace'),
|
||||
handler: function(env, event){
|
||||
snr(env, event);
|
||||
}
|
||||
};
|
||||
|
||||
var act_all = keyboard_manager.actions.register(action_all, 'find-and-replace', 'jupyter-notebook');
|
||||
|
||||
keyboard_manager.command_shortcuts.add_shortcuts({
|
||||
'f': 'jupyter-notebook:find-and-replace'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return {load:load};
|
||||
});
|
||||
@ -1,232 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
"jquery",
|
||||
"notebook/js/quickhelp",
|
||||
"base/js/dialog",
|
||||
"components/marked/lib/marked"
|
||||
], function (
|
||||
$,
|
||||
QH,
|
||||
dialog,
|
||||
marked
|
||||
) {
|
||||
|
||||
|
||||
/**
|
||||
* Humanize the action name to be consumed by user.
|
||||
* internally the actions name are of the form
|
||||
* <namespace>:<description-with-dashes>
|
||||
* we drop <namespace> and replace dashes for space.
|
||||
*/
|
||||
var humanize_action_id = function(str) {
|
||||
return str.split(':')[1].replace(/-/g, ' ').replace(/_/g, '-');
|
||||
};
|
||||
|
||||
/**
|
||||
* given an action id return 'command-shortcut', 'edit-shortcut' or 'no-shortcut'
|
||||
* for the action. This allows us to tag UI in order to visually distinguish
|
||||
* Wether an action have a keybinding or not.
|
||||
**/
|
||||
|
||||
var KeyBinding = createReactClass({
|
||||
displayName: 'KeyBindings',
|
||||
getInitialState: function() {
|
||||
return {shrt:''};
|
||||
},
|
||||
handleShrtChange: function (element){
|
||||
this.setState({shrt:element.target.value});
|
||||
},
|
||||
render: function(){
|
||||
var that = this;
|
||||
var available = this.props.available(this.state.shrt);
|
||||
var empty = (this.state.shrt === '');
|
||||
var binding_setter = function(){
|
||||
if (available) {
|
||||
that.props.onAddBindings(that.state.shrt, that.props.ckey);
|
||||
}
|
||||
that.state.shrt='';
|
||||
event.preventDefault();
|
||||
return false;
|
||||
};
|
||||
return React.createElement('form', {className:'jupyter-keybindings',
|
||||
onSubmit: binding_setter
|
||||
},
|
||||
React.createElement('i', {className: "pull-right fa fa-plus", alt: 'add-keyboard-shortcut',
|
||||
onClick: binding_setter
|
||||
}),
|
||||
React.createElement('input', {
|
||||
type:'text',
|
||||
placeholder:'add shortcut',
|
||||
className:'pull-right'+((available||empty)?'':' alert alert-danger'),
|
||||
value:that.state.shrt,
|
||||
onChange:that.handleShrtChange
|
||||
}),
|
||||
that.props.shortcuts ? that.props.shortcuts.map(function (item, index) {
|
||||
return React.createElement('span', {className: 'pull-right'},
|
||||
React.createElement('kbd', {}, [
|
||||
item.h,
|
||||
React.createElement('i', {className: "fa fa-times", alt: 'remove '+item.h,
|
||||
onClick:function () {
|
||||
that.props.unbind(item.raw);
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}): null,
|
||||
React.createElement('div', {title: '(' + that.props.ckey + ')' ,
|
||||
className:'jupyter-keybindings-text'}, that.props.display )
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var KeyBindingList = createReactClass({
|
||||
displayName: 'KeyBindingList',
|
||||
getInitialState: function(){
|
||||
return {data:[]};
|
||||
},
|
||||
componentDidMount: function(){
|
||||
this.setState({data:this.props.callback()});
|
||||
},
|
||||
render: function() {
|
||||
var that = this;
|
||||
var children = this.state.data.map(function (binding) {
|
||||
return React.createElement(KeyBinding, Object.assign({}, binding, {
|
||||
onAddBindings: function (shortcut, action) {
|
||||
that.props.bind(shortcut, action);
|
||||
that.setState({data:that.props.callback()});
|
||||
},
|
||||
available: that.props.available,
|
||||
unbind: function (shortcut) {
|
||||
that.props.unbind(shortcut);
|
||||
that.setState({data:that.props.callback()});
|
||||
}
|
||||
}));
|
||||
});
|
||||
children.unshift(React.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(React.createElement('div', {className:'well', key:'disclamer', id:'long-key-binding-intro', dangerouslySetInnerHTML:
|
||||
{__html:
|
||||
marked(
|
||||
|
||||
"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 shortcuts: **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 modifier keys 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. \n"+
|
||||
"\n"+
|
||||
"Changing the keybindings of edit mode is not currently available."
|
||||
)}
|
||||
}));
|
||||
return React.createElement('div',{}, children);
|
||||
}
|
||||
});
|
||||
|
||||
var get_shortcuts_data = function(notebook) {
|
||||
var actions = Object.keys(notebook.keyboard_manager.actions._actions);
|
||||
var src = [];
|
||||
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
var action_id = actions[i];
|
||||
var action = notebook.keyboard_manager.actions.get(action_id);
|
||||
|
||||
var shortcuts = notebook.keyboard_manager.command_shortcuts.get_action_shortcuts(action_id);
|
||||
var hshortcuts = [];
|
||||
if (shortcuts.length > 0) {
|
||||
hshortcuts = shortcuts.map(function (raw) {
|
||||
return {h:QH._humanize_sequence(raw),raw:raw};}
|
||||
);
|
||||
}
|
||||
src.push({
|
||||
display: humanize_action_id(action_id),
|
||||
shortcuts: hshortcuts,
|
||||
key:action_id, // react specific thing
|
||||
ckey: action_id
|
||||
});
|
||||
}
|
||||
return src;
|
||||
};
|
||||
|
||||
|
||||
var ShortcutEditor = function(notebook) {
|
||||
|
||||
if(!notebook){
|
||||
throw new Error("CommandPalette takes a notebook non-null mandatory argument");
|
||||
}
|
||||
|
||||
var body = $('<div>');
|
||||
var mod = dialog.modal({
|
||||
notebook: notebook,
|
||||
keyboard_manager: notebook.keyboard_manager,
|
||||
title : "Edit Command mode Shortcuts",
|
||||
body : body,
|
||||
buttons : {
|
||||
OK : {}
|
||||
}
|
||||
});
|
||||
|
||||
var src = get_shortcuts_data(notebook);
|
||||
|
||||
mod.addClass("modal_stretch");
|
||||
|
||||
mod.modal('show');
|
||||
ReactDOM.render(
|
||||
React.createElement(KeyBindingList, {
|
||||
callback: function () { return get_shortcuts_data(notebook);},
|
||||
bind: function (shortcut, command) {
|
||||
return notebook.keyboard_manager.command_shortcuts._persist_shortcut(shortcut, command);
|
||||
},
|
||||
unbind: function (shortcut) {
|
||||
return notebook.keyboard_manager.command_shortcuts._persist_remove_shortcut(shortcut);
|
||||
},
|
||||
available: function (shrt) { return notebook.keyboard_manager.command_shortcuts.is_available_shortcut(shrt);}
|
||||
}),
|
||||
body.get(0)
|
||||
);
|
||||
};
|
||||
return {ShortcutEditor: ShortcutEditor};
|
||||
});
|
||||
@ -1,654 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n',
|
||||
'notebook/js/cell',
|
||||
'base/js/markdown',
|
||||
'services/config',
|
||||
'notebook/js/celltoolbar',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/gfm/gfm',
|
||||
'notebook/js/codemirror-ipythongfm',
|
||||
'bidi/bidi'
|
||||
], function(
|
||||
$,
|
||||
utils,
|
||||
i18n,
|
||||
cell,
|
||||
markdown,
|
||||
configmod,
|
||||
celltoolbar,
|
||||
CodeMirror,
|
||||
gfm,
|
||||
ipgfm,
|
||||
bidi
|
||||
) {
|
||||
"use strict";
|
||||
function encodeURIandParens(uri){return encodeURI(uri).replace('(','%28').replace(')','%29')}
|
||||
|
||||
/**
|
||||
* Given a file name and a list of existing file names, returns a new file name
|
||||
* that is not in the existing list. If the file name already exists, a new one with
|
||||
* an incremented index is returned instead.
|
||||
*
|
||||
* Example:
|
||||
* addIndexToFileName('attachment.png',
|
||||
* ['attachment.png', 'attachment-3.png']) returns 'attachment-4.png'
|
||||
*
|
||||
* @param {string} fileName - original file name
|
||||
* @param {string} fileNames - other file names
|
||||
* @return {string} the original file name or one with a postfix
|
||||
* index (before the extension, if one exists)
|
||||
*/
|
||||
function addIndexToFileName(fileName, fileNames) {
|
||||
if (fileNames === undefined) {
|
||||
return fileName;
|
||||
}
|
||||
var lastDot = fileName.lastIndexOf('.');
|
||||
var pre = fileName.substr(0, lastDot);
|
||||
var optionalExt = fileName.substr(lastDot);
|
||||
|
||||
var indexMatch = '-(\\d+)';
|
||||
// Make the index match optional so we can match both 'fileName.png' and 'fileName-2.png'
|
||||
// The ?: makes it a non-capturing group.
|
||||
var optionalIndexMatch = '(?:' + indexMatch + ')?';
|
||||
|
||||
var regex = new RegExp(pre + optionalIndexMatch + optionalExt);
|
||||
|
||||
var highestIndex = 0;
|
||||
for (var existingFileName in fileNames) {
|
||||
var match = existingFileName.match(regex);
|
||||
var index = match[1];
|
||||
if (index === undefined) {
|
||||
index = 1;
|
||||
}
|
||||
else {
|
||||
index = parseInt(index);
|
||||
}
|
||||
if (index > highestIndex) {
|
||||
highestIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (highestIndex > 0) {
|
||||
return pre + "-" + (highestIndex + 1) + optionalExt;
|
||||
}
|
||||
else {
|
||||
return fileName;
|
||||
}
|
||||
};
|
||||
|
||||
var Cell = cell.Cell;
|
||||
|
||||
var TextCell = function (options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Construct a new TextCell, codemirror mode is by default 'htmlmixed',
|
||||
* and cell type is 'text' cell start as not redered.
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: dictionary
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
* notebook: Notebook instance
|
||||
*/
|
||||
options = options || {};
|
||||
|
||||
// in all TextCell/Cell subclasses
|
||||
// do not assign most of members here, just pass it down
|
||||
// in the options dict potentially overwriting what you wish.
|
||||
// they will be assigned in the base class.
|
||||
this.notebook = options.notebook;
|
||||
this.events = options.events;
|
||||
this.config = options.config;
|
||||
|
||||
// we cannot put this as a class key as it has handle to "this".
|
||||
Cell.apply(this, [{
|
||||
config: options.config,
|
||||
keyboard_manager: options.keyboard_manager,
|
||||
events: this.events}]);
|
||||
|
||||
this.cell_type = this.cell_type || 'text';
|
||||
this.rendered = false;
|
||||
};
|
||||
|
||||
TextCell.prototype = Object.create(Cell.prototype);
|
||||
|
||||
TextCell.options_default = {
|
||||
cm_config : {
|
||||
mode: 'htmlmixed',
|
||||
lineWrapping : true,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the DOM element of the TextCell
|
||||
* @method create_element
|
||||
* @private
|
||||
*/
|
||||
TextCell.prototype.create_element = function () {
|
||||
Cell.prototype.create_element.apply(this, arguments);
|
||||
var that = this;
|
||||
|
||||
var cell = $("<div>").addClass('cell text_cell');
|
||||
cell.attr('tabindex','2');
|
||||
|
||||
var prompt = $('<div/>').addClass('prompt input_prompt');
|
||||
cell.append(prompt);
|
||||
var inner_cell = $('<div/>').addClass('inner_cell');
|
||||
this.celltoolbar = new celltoolbar.CellToolbar({
|
||||
cell: this,
|
||||
notebook: this.notebook});
|
||||
inner_cell.append(this.celltoolbar.element);
|
||||
var input_area = $('<div/>').addClass('input_area').attr("aria-label", i18n.msg._("Edit Markup Text here"));
|
||||
this.code_mirror = new CodeMirror(input_area.get(0), this._options.cm_config);
|
||||
// In case of bugs that put the keyboard manager into an inconsistent state,
|
||||
// ensure KM is enabled when CodeMirror is focused:
|
||||
this.code_mirror.on('focus', function () {
|
||||
if (that.keyboard_manager) {
|
||||
that.keyboard_manager.enable();
|
||||
}
|
||||
that.code_mirror.setOption('readOnly', !that.is_editable());
|
||||
});
|
||||
this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
|
||||
// The tabindex=-1 makes this div focusable.
|
||||
var render_area = $('<div/>').addClass('text_cell_render rendered_html')
|
||||
.attr('tabindex','-1');
|
||||
inner_cell.append(input_area).append(render_area);
|
||||
cell.append(inner_cell);
|
||||
this.element = cell;
|
||||
this.inner_cell = inner_cell;
|
||||
};
|
||||
|
||||
|
||||
// Cell level actions
|
||||
|
||||
TextCell.prototype.add_attachment = function (key, mime_type, b64_data) {
|
||||
/**
|
||||
* Add a new attachment to this cell
|
||||
*/
|
||||
this.attachments[key] = {};
|
||||
this.attachments[key][mime_type] = b64_data;
|
||||
};
|
||||
|
||||
TextCell.prototype.select = function () {
|
||||
var cont = Cell.prototype.select.apply(this, arguments);
|
||||
if (cont) {
|
||||
if (this.mode === 'edit') {
|
||||
this.code_mirror.refresh();
|
||||
}
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
TextCell.prototype.unrender = function () {
|
||||
var cont = Cell.prototype.unrender.apply(this);
|
||||
if (cont) {
|
||||
var text_cell = this.element;
|
||||
if (this.get_text() === this.placeholder) {
|
||||
this.set_text('');
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
TextCell.prototype.execute = function () {
|
||||
this.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
|
||||
* @method get_text
|
||||
* @return {string} CodeMirror current text value
|
||||
*/
|
||||
TextCell.prototype.get_text = function() {
|
||||
return this.code_mirror.getValue();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text - Codemiror text value
|
||||
* @see TextCell#get_text
|
||||
* @method set_text
|
||||
* */
|
||||
TextCell.prototype.set_text = function(text) {
|
||||
this.code_mirror.setValue(text);
|
||||
this.unrender();
|
||||
this.code_mirror.refresh();
|
||||
};
|
||||
|
||||
/**
|
||||
* setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
|
||||
* @method get_rendered
|
||||
* */
|
||||
TextCell.prototype.get_rendered = function() {
|
||||
return this.element.find('div.text_cell_render').html();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method set_rendered
|
||||
*/
|
||||
TextCell.prototype.set_rendered = function(text) {
|
||||
this.element.find('div.text_cell_render').html(text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create Text cell from JSON
|
||||
* @param {json} data - JSON serialized text-cell
|
||||
* @method fromJSON
|
||||
*/
|
||||
TextCell.prototype.fromJSON = function (data) {
|
||||
Cell.prototype.fromJSON.apply(this, arguments);
|
||||
if (data.cell_type === this.cell_type) {
|
||||
if (data.attachments !== undefined) {
|
||||
this.attachments = data.attachments;
|
||||
}
|
||||
|
||||
if (data.source !== undefined) {
|
||||
this.set_text(data.source);
|
||||
// make this value the starting point, so that we can only undo
|
||||
// to this state, instead of a blank cell
|
||||
this.code_mirror.clearHistory();
|
||||
// TODO: This HTML needs to be treated as potentially dangerous
|
||||
// user input and should be handled before set_rendered.
|
||||
this.set_rendered(data.rendered || '');
|
||||
this.rendered = false;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Generate JSON from cell
|
||||
* @param {bool} gc_attachments - If true, will remove unused attachments
|
||||
* from the returned JSON
|
||||
* @return {object} cell data serialised to json
|
||||
*/
|
||||
TextCell.prototype.toJSON = function (gc_attachments) {
|
||||
if (gc_attachments === undefined) {
|
||||
gc_attachments = false;
|
||||
}
|
||||
|
||||
var data = Cell.prototype.toJSON.apply(this);
|
||||
data.source = this.get_text();
|
||||
if (data.source == this.placeholder) {
|
||||
data.source = "";
|
||||
}
|
||||
|
||||
// We deepcopy the attachments so copied cells don't share the same
|
||||
// objects
|
||||
if (Object.keys(this.attachments).length > 0) {
|
||||
if (gc_attachments) {
|
||||
// Garbage collect unused attachments : The general idea is to
|
||||
// render the text, and find used attachments like when we
|
||||
// substitute them in render()
|
||||
var that = this;
|
||||
data.attachments = {};
|
||||
// To find attachments, rendering to HTML is easier than
|
||||
// searching in the markdown source for the multiple ways you
|
||||
// can reference an image in markdown (using []() or a
|
||||
// HTML <img>)
|
||||
var text = this.get_text();
|
||||
markdown.render(text, {
|
||||
sanitize: true,
|
||||
}, function (err, html) {
|
||||
html.find('img[src^="attachment:"]').each(function (i, h) {
|
||||
h = $(h);
|
||||
var key = h.attr('src').replace(/^attachment:/, '');
|
||||
if (that.attachments.hasOwnProperty(key)) {
|
||||
data.attachments[key] = JSON.parse(JSON.stringify(
|
||||
that.attachments[key]));
|
||||
}
|
||||
|
||||
// This is to avoid having the browser do a GET request
|
||||
// on the invalid attachment: URL
|
||||
h.attr('src', '');
|
||||
});
|
||||
});
|
||||
if (data.attachments.length === 0) {
|
||||
// omit attachments dict if no attachments
|
||||
delete data.attachments;
|
||||
}
|
||||
} else {
|
||||
data.attachments = JSON.parse(JSON.stringify(this.attachments));
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
var MarkdownCell = function (options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: ConfigSection instance
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
* notebook: Notebook instance
|
||||
*/
|
||||
options = options || {};
|
||||
var config_default = utils.mergeopt(TextCell, MarkdownCell.options_default);
|
||||
this.class_config = new configmod.ConfigWithDefaults(options.config,
|
||||
config_default, 'MarkdownCell');
|
||||
TextCell.apply(this, [$.extend({}, options, {config: options.config})]);
|
||||
|
||||
this.cell_type = 'markdown';
|
||||
|
||||
// Used to keep track of drag events
|
||||
this.drag_counter = 0;
|
||||
};
|
||||
|
||||
MarkdownCell.options_default = {
|
||||
cm_config: {
|
||||
mode: 'ipythongfm',
|
||||
},
|
||||
placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
|
||||
};
|
||||
|
||||
MarkdownCell.prototype = Object.create(TextCell.prototype);
|
||||
|
||||
MarkdownCell.prototype.set_heading_level = function (level) {
|
||||
/**
|
||||
* make a markdown cell a heading
|
||||
*/
|
||||
level = level || 1;
|
||||
var source = this.get_text();
|
||||
source = source.replace(/^(#*)\s?/,
|
||||
new Array(level + 1).join('#') + ' ');
|
||||
this.set_text(source);
|
||||
this.refresh();
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownCell.prototype.select = function () {
|
||||
var cont = TextCell.prototype.select.apply(this, arguments);
|
||||
if (cont) {
|
||||
this.notebook.set_insert_image_enabled(!this.rendered);
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownCell.prototype.unrender = function () {
|
||||
var cont = TextCell.prototype.unrender.apply(this);
|
||||
this.notebook.set_insert_image_enabled(true);
|
||||
};
|
||||
|
||||
MarkdownCell.prototype.insert_inline_image_from_blob = function(blob) {
|
||||
/**
|
||||
* Insert markup for an inline image at the current cursor position.
|
||||
* This works as follow :
|
||||
* - We insert the base64-encoded blob data into the cell attachments
|
||||
* dictionary, keyed by the filename.
|
||||
* - We insert an img tag with a 'attachment:key' src that refers to
|
||||
* the attachments entry.
|
||||
*
|
||||
* Parameters:
|
||||
* file: Blob
|
||||
* The JS Blob object (e.g. from the DataTransferItem)
|
||||
*/
|
||||
var that = this;
|
||||
var pos = this.code_mirror.getCursor();
|
||||
var reader = new FileReader();
|
||||
// We can get either a named file (drag'n'drop) or a blob (copy/paste)
|
||||
// We generate names for blobs
|
||||
var key;
|
||||
if (blob.name !== undefined) {
|
||||
key = encodeURIandParens(blob.name);
|
||||
|
||||
// Add an index to the filename if we already have one with the same name
|
||||
key = addIndexToFileName(key, that.attachments);
|
||||
} else {
|
||||
key = '_auto_' + Object.keys(that.attachments).length;
|
||||
}
|
||||
|
||||
reader.onloadend = function() {
|
||||
var d = utils.parse_b64_data_uri(reader.result);
|
||||
var blobData = d[1]
|
||||
|
||||
if (blob.type != d[0]) {
|
||||
// TODO(julienr): Not sure what we should do in this case
|
||||
console.log('File type (' + blob.type + ') != data-uri ' +
|
||||
'type (' + d[0] + ')');
|
||||
}
|
||||
|
||||
// If we have the same attachment already under another key, we change the key to that.
|
||||
// This ensures we don't create two attachments if pasting the same image twice.
|
||||
|
||||
for (var savedKey in that.attachments) {
|
||||
var attachment = that.attachments[savedKey];
|
||||
if (attachment === undefined) continue;
|
||||
|
||||
var savedBlob = attachment[blob.type];
|
||||
if (savedBlob === blobData) {
|
||||
key = savedKey;
|
||||
}
|
||||
}
|
||||
|
||||
that.add_attachment(key, blob.type, blobData);
|
||||
var img_md = '';
|
||||
that.code_mirror.replaceRange(img_md, pos);
|
||||
}
|
||||
reader.readAsDataURL(blob);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method render
|
||||
*/
|
||||
MarkdownCell.prototype.render = function () {
|
||||
// We clear the dropzone here just in case the dragenter/leave
|
||||
// logic of bind_events wasn't 100% successful.
|
||||
this.drag_counter = 0;
|
||||
this.inner_cell.removeClass('dropzone');
|
||||
|
||||
var cont = TextCell.prototype.render.apply(this);
|
||||
if (cont) {
|
||||
var that = this;
|
||||
var text = this.get_text();
|
||||
var math = null;
|
||||
if (text === "") { text = this.placeholder; }
|
||||
markdown.render(text, {
|
||||
with_math: true,
|
||||
clean_tables: true,
|
||||
sanitize: true,
|
||||
}, function (err, html) {
|
||||
// add anchors to headings
|
||||
html.find(":header").addBack(":header").each(function (i, h) {
|
||||
h = $(h);
|
||||
var hash = h.text().replace(/ /g, '-');
|
||||
h.attr('id', hash);
|
||||
h.append(
|
||||
$('<a/>')
|
||||
.addClass('anchor-link')
|
||||
.attr('href', '#' + hash)
|
||||
.text('¶')
|
||||
.on('click',function(){
|
||||
setTimeout(function(){that.unrender(); that.render()}, 100)
|
||||
})
|
||||
);
|
||||
});
|
||||
// links in markdown cells should open in new tabs
|
||||
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
|
||||
// replace attachment:<key> by the corresponding entry
|
||||
// in the cell's attachments
|
||||
html.find('img[src^="attachment:"]').each(function (i, h) {
|
||||
h = $(h);
|
||||
var key = h.attr('src').replace(/^attachment:/, '');
|
||||
|
||||
if (that.attachments.hasOwnProperty(key)) {
|
||||
var att = that.attachments[key];
|
||||
var mime = Object.keys(att)[0];
|
||||
h.attr('src', 'data:' + mime + ';base64,' + att[mime]);
|
||||
} else {
|
||||
h.attr('src', '');
|
||||
}
|
||||
});
|
||||
that.set_rendered(html);
|
||||
that.typeset();
|
||||
that.events.trigger("rendered.MarkdownCell", {cell: that});
|
||||
});
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
/** @method bind_events **/
|
||||
MarkdownCell.prototype.bind_events = function () {
|
||||
TextCell.prototype.bind_events.apply(this);
|
||||
var that = this;
|
||||
|
||||
this.element.dblclick(function () {
|
||||
var cont = that.unrender();
|
||||
if (cont) {
|
||||
that.focus_editor();
|
||||
}
|
||||
});
|
||||
|
||||
var attachment_regex = /^image\/.*$/;
|
||||
|
||||
// Event handlers to allow users to insert image using either
|
||||
// drag'n'drop or copy/paste
|
||||
var div = that.code_mirror.getWrapperElement();
|
||||
$(div).on('paste', function(evt) {
|
||||
var data = evt.originalEvent.clipboardData;
|
||||
var items = data.items;
|
||||
if (items !== undefined) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var item = items[i];
|
||||
if (item.kind == 'file' && attachment_regex.test(item.type)) {
|
||||
// TODO(julienr): This does not stop code_mirror from pasting
|
||||
// the filename.
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
that.insert_inline_image_from_blob(item.getAsFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Allow drag event if the dragged file can be used as an attachment
|
||||
// If we use this.code_mirror.on to register a "dragover" handler, we
|
||||
// get an empty dataTransfer
|
||||
this.code_mirror.on("dragover", function(cm, evt) {
|
||||
if (utils.dnd_contain_file(evt)) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// We want to display a visual indicator that the drop is possible.
|
||||
// The dragleave event is fired when we hover a child element (which
|
||||
// is often immediately after we got the dragenter), so we keep track
|
||||
// of the number of dragenter/dragleave we got, as discussed here :
|
||||
// https://stackoverflow.com/q/7110353/116067
|
||||
// This doesn't seem to be 100% reliable, so we clear the dropzone
|
||||
// class when the cell is rendered as well
|
||||
this.code_mirror.on("dragenter", function(cm, evt) {
|
||||
if (utils.dnd_contain_file(evt)) {
|
||||
that.drag_counter++;
|
||||
that.inner_cell.addClass('dropzone');
|
||||
}
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
});
|
||||
|
||||
this.code_mirror.on("dragleave", function(cm, evt) {
|
||||
that.drag_counter--;
|
||||
if (that.drag_counter <= 0) {
|
||||
that.inner_cell.removeClass('dropzone');
|
||||
}
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
});
|
||||
|
||||
this.code_mirror.on("drop", function(cm, evt) {
|
||||
that.drag_counter = 0;
|
||||
that.inner_cell.removeClass('dropzone');
|
||||
|
||||
var files = evt.dataTransfer.files;
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var file = files[i];
|
||||
if (attachment_regex.test(file.type)) {
|
||||
// Prevent the default code_mirror 'drop' event handler
|
||||
// (which inserts the file content) if this is a
|
||||
// recognized media file
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
that.insert_inline_image_from_blob(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var RawCell = function (options) {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Parameters:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: ConfigSection instance
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
* notebook: Notebook instance
|
||||
*/
|
||||
options = options || {};
|
||||
var config_default = utils.mergeopt(TextCell, RawCell.options_default);
|
||||
this.class_config = new configmod.ConfigWithDefaults(options.config,
|
||||
config_default, 'RawCell');
|
||||
TextCell.apply(this, [$.extend({}, options, {config: options.config})]);
|
||||
this.cell_type = 'raw';
|
||||
};
|
||||
|
||||
RawCell.options_default = {
|
||||
highlight_modes : {
|
||||
'diff' :{'reg':[/^diff/]}
|
||||
},
|
||||
placeholder : i18n.msg._("Write raw LaTeX or other formats here, for use with nbconvert. " +
|
||||
"It will not be rendered in the notebook. " +
|
||||
"When passing through nbconvert, a Raw Cell's content is added to the output unmodified."),
|
||||
};
|
||||
|
||||
RawCell.prototype = Object.create(TextCell.prototype);
|
||||
|
||||
/** @method bind_events **/
|
||||
RawCell.prototype.bind_events = function () {
|
||||
TextCell.prototype.bind_events.apply(this);
|
||||
var that = this;
|
||||
this.element.focusout(function() {
|
||||
that.auto_highlight();
|
||||
that.render();
|
||||
});
|
||||
|
||||
this.code_mirror.on('focus', function() { that.unrender(); });
|
||||
};
|
||||
|
||||
/** @method render **/
|
||||
RawCell.prototype.render = function () {
|
||||
var cont = TextCell.prototype.render.apply(this);
|
||||
if (cont){
|
||||
var text = this.get_text();
|
||||
if (text === "") { text = this.placeholder; }
|
||||
this.set_text(text);
|
||||
this.element.removeClass('rendered');
|
||||
this.auto_highlight();
|
||||
}
|
||||
return cont;
|
||||
};
|
||||
|
||||
var textcell = {
|
||||
TextCell: TextCell,
|
||||
MarkdownCell: MarkdownCell,
|
||||
RawCell: RawCell
|
||||
};
|
||||
return textcell;
|
||||
});
|
||||
@ -1,153 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define(['jquery','base/js/i18n'], function($, i18n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A generic toolbar on which one can add button
|
||||
* @class ToolBar
|
||||
* @constructor
|
||||
* @param {Dom_object} selector
|
||||
*/
|
||||
var ToolBar = function (selector, options) {
|
||||
this.selector = selector;
|
||||
this.actions = (options||{}).actions;
|
||||
if (this.selector !== undefined) {
|
||||
this.element = $(selector);
|
||||
this.style();
|
||||
}
|
||||
};
|
||||
|
||||
ToolBar.prototype._pseudo_actions={};
|
||||
|
||||
|
||||
ToolBar.prototype.construct = function (config) {
|
||||
for(var k=0; k<config.length; k++) {
|
||||
this.add_buttons_group(config[k][0],config[k][1]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a group of button into the current toolbar.
|
||||
*
|
||||
* Use a [dict of [list of action name]] to trigger
|
||||
* on click to the button
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ... todo, maybe use a list of list to keep ordering.
|
||||
*
|
||||
* [
|
||||
* [
|
||||
* [
|
||||
* action_name_1,
|
||||
* action_name_2,
|
||||
* action_name_3,
|
||||
* ],
|
||||
* optional_group_name
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* @param list {List}
|
||||
* List of button of the group, with the following parameter for each :
|
||||
* @param list.label {string} text to show on button hover
|
||||
* @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
|
||||
* @param list.callback {function} function to be called on button click
|
||||
* @param [list.id] {String} id to give to the button
|
||||
* @param [group_id] {String} optional id to give to the group
|
||||
*
|
||||
*
|
||||
* for private usage, the key can also be strings starting with '<' and ending with '>' to inject custom element that cannot
|
||||
* be bound to an action.
|
||||
*
|
||||
*/
|
||||
// TODO JUPYTER:
|
||||
// get rid of legacy code that handle things that are not actions.
|
||||
ToolBar.prototype.add_buttons_group = function (list, group_id) {
|
||||
// handle custom call of pseudoaction binding.
|
||||
if(typeof(list) === 'string' && list.slice(0,1) === '<' && list.slice(-1) === '>'){
|
||||
var _pseudo_action;
|
||||
try{
|
||||
_pseudo_action = list.slice(1,-1);
|
||||
this.element.append(this._pseudo_actions[_pseudo_action].call(this));
|
||||
} catch (e) {
|
||||
console.warn('ouch, calling ', _pseudo_action, 'does not seem to work...:', e);
|
||||
}
|
||||
return ;
|
||||
}
|
||||
var that = this;
|
||||
var btn_group = $('<div/>').addClass("btn-group");
|
||||
if( group_id !== undefined ) {
|
||||
btn_group.attr('id',group_id);
|
||||
}
|
||||
list.forEach(function(el) {
|
||||
var action_name;
|
||||
var action;
|
||||
if(typeof(el) === 'string'){
|
||||
action = that.actions.get(el);
|
||||
action_name = el;
|
||||
} else if (el.action) {
|
||||
action = that.actions.get(el.action);
|
||||
action_name = el.action
|
||||
}
|
||||
var title = el.label;
|
||||
if(action && action.help) {
|
||||
title = i18n.msg._(action.help) || el.label;
|
||||
}
|
||||
var button = $('<button/>')
|
||||
.addClass('btn btn-default')
|
||||
.attr("aria-label", el.label)
|
||||
.attr("title", title)
|
||||
.append(
|
||||
$("<i/>").addClass(el.icon||(action||{icon:'fa-exclamation-triangle'}).icon).addClass('fa')
|
||||
);
|
||||
if (el.label) {
|
||||
var label = $('<span/>').text(i18n.msg._(el.label)).addClass('toolbar-btn-label');
|
||||
button.append(label);
|
||||
}
|
||||
var id = el.id;
|
||||
if( id !== undefined ){
|
||||
button.attr('id',id);
|
||||
}
|
||||
button.attr('data-jupyter-action', action_name);
|
||||
var fun = el.callback|| function(){
|
||||
that.actions.call(action_name);
|
||||
};
|
||||
button.click(fun);
|
||||
btn_group.append(button);
|
||||
});
|
||||
$(this.selector).append(btn_group);
|
||||
return btn_group;
|
||||
};
|
||||
|
||||
ToolBar.prototype.style = function () {
|
||||
this.element.addClass('toolbar');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show and hide toolbar
|
||||
* @method toggle
|
||||
*/
|
||||
ToolBar.prototype.toggle = function () {
|
||||
this.element.toggle();
|
||||
};
|
||||
|
||||
/**
|
||||
* A simple class to hold information defining one toolbar button.
|
||||
* @class ToolBar
|
||||
* @constructor
|
||||
* @param action {String} name of a Jupyter action taken when pressed
|
||||
* @param options.label {String} short label to display on the button
|
||||
*/
|
||||
var Button = function(action, options) {
|
||||
this.action = action;
|
||||
this.label = (options||{}).label;
|
||||
};
|
||||
|
||||
return {
|
||||
'ToolBar': ToolBar,
|
||||
'Button': Button
|
||||
};
|
||||
});
|
||||
@ -1,324 +0,0 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/i18n'
|
||||
], function($, utils, i18n) {
|
||||
"use strict";
|
||||
|
||||
// tooltip constructor
|
||||
var Tooltip = function (events) {
|
||||
var that = this;
|
||||
this.events = events;
|
||||
this.time_before_tooltip = 1200;
|
||||
|
||||
// handle to html
|
||||
this.tooltip = $('#tooltip');
|
||||
this._hidden = true;
|
||||
|
||||
// variable for consecutive call
|
||||
this._old_cell = null;
|
||||
this._old_request = null;
|
||||
this._consecutive_counter = 0;
|
||||
|
||||
// 'sticky ?'
|
||||
this._sticky = false;
|
||||
|
||||
// display tooltip if the docstring is empty?
|
||||
this._hide_if_no_docstring = false;
|
||||
|
||||
// contain the button in the upper right corner
|
||||
this.buttons = $('<div/>').addClass('tooltipbuttons');
|
||||
|
||||
// will contain the docstring
|
||||
this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
|
||||
|
||||
// build the buttons menu on the upper right
|
||||
// expand the tooltip to see more
|
||||
var expandlink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button')
|
||||
.attr('id', 'expanbutton').attr('title', i18n.msg._('Grow the tooltip vertically (press shift-tab twice)')).click(function () {
|
||||
that.expand();
|
||||
event.preventDefault();
|
||||
}).append(
|
||||
$('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
|
||||
|
||||
// open in pager
|
||||
var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', i18n.msg._('show the current docstring in pager (press shift-tab 4 times)'));
|
||||
var morespan = $('<span/>').text(i18n.msg._('Open in Pager')).addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
|
||||
morelink.append(morespan);
|
||||
morelink.click(function () {
|
||||
that.showInPager(that._old_cell);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// close the tooltip
|
||||
var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
|
||||
var closespan = $('<span/>').text(i18n.msg._('Close')).addClass('ui-icon').addClass('ui-icon-close');
|
||||
closelink.append(closespan);
|
||||
closelink.click(function () {
|
||||
that.remove_and_cancel_tooltip(true);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this._clocklink = $('<a/>').attr('href', "#");
|
||||
this._clocklink.attr('role', "button");
|
||||
this._clocklink.addClass('ui-button');
|
||||
this._clocklink.attr('title', i18n.msg._('Tooltip will linger for 10 seconds while you type'));
|
||||
var clockspan = $('<span/>').text(i18n.msg._('Close'));
|
||||
clockspan.addClass('ui-icon');
|
||||
clockspan.addClass('ui-icon-clock');
|
||||
this._clocklink.append(clockspan);
|
||||
this._clocklink.click(function () {
|
||||
that.cancel_stick();
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
//construct the tooltip
|
||||
// add in the reverse order you want them to appear
|
||||
this.buttons.append(closelink);
|
||||
this.buttons.append(expandlink);
|
||||
this.buttons.append(morelink);
|
||||
this.buttons.append(this._clocklink);
|
||||
this._clocklink.hide();
|
||||
|
||||
|
||||
// we need a phony element to make the small arrow
|
||||
// of the tooltip in css
|
||||
// we will move the arrow later
|
||||
this.arrow = $('<div/>').addClass('pretooltiparrow');
|
||||
this.tooltip.append(this.buttons);
|
||||
this.tooltip.append(this.arrow);
|
||||
this.tooltip.append(this.text);
|
||||
|
||||
// function that will be called if you press tab 1, 2, 3... times in a row
|
||||
this.tabs_functions = [function (cell, text, cursor) {
|
||||
that._request_tooltip(cell, text, cursor);
|
||||
}, function () {
|
||||
that.expand();
|
||||
}, function () {
|
||||
that.stick();
|
||||
}, function (cell) {
|
||||
that.cancel_stick();
|
||||
that.showInPager(cell);
|
||||
}];
|
||||
// call after all the tabs function above have bee call to clean their effects
|
||||
// if necessary
|
||||
this.reset_tabs_function = function (cell, text) {
|
||||
this._old_cell = (cell) ? cell : null;
|
||||
this._old_request = (text) ? text : null;
|
||||
this._consecutive_counter = 0;
|
||||
};
|
||||
};
|
||||
|
||||
Tooltip.prototype.is_visible = function () {
|
||||
return !this._hidden;
|
||||
};
|
||||
|
||||
Tooltip.prototype.showInPager = function (cell) {
|
||||
/**
|
||||
* reexecute last call in pager by appending ? to show back in pager
|
||||
*/
|
||||
this.events.trigger('open_with_text.Pager', this._reply.content);
|
||||
this.remove_and_cancel_tooltip();
|
||||
};
|
||||
|
||||
// grow the tooltip vertically
|
||||
Tooltip.prototype.expand = function () {
|
||||
this.text.removeClass('smalltooltip');
|
||||
this.text.addClass('bigtooltip');
|
||||
$('#expanbutton').hide('slow');
|
||||
};
|
||||
|
||||
// deal with all the logic of hiding the tooltip
|
||||
// and reset its status
|
||||
Tooltip.prototype._hide = function () {
|
||||
this._hidden = true;
|
||||
this.tooltip.fadeOut('fast');
|
||||
$('#expanbutton').show('slow');
|
||||
this.text.removeClass('bigtooltip');
|
||||
this.text.addClass('smalltooltip');
|
||||
// keep scroll top to be sure to always see the first line
|
||||
this.text.scrollTop(0);
|
||||
this.code_mirror = null;
|
||||
};
|
||||
|
||||
// return true on successfully removing a visible tooltip; otherwise return
|
||||
// false.
|
||||
Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
|
||||
/**
|
||||
* note that we don't handle closing directly inside the calltip
|
||||
* as in the completer, because it is not focusable, so won't
|
||||
* get the event.
|
||||
*/
|
||||
this.cancel_pending();
|
||||
if (!this._hidden) {
|
||||
if (force || !this._sticky) {
|
||||
this.cancel_stick();
|
||||
this._hide();
|
||||
}
|
||||
this.reset_tabs_function();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// cancel autocall done after '(' for example.
|
||||
Tooltip.prototype.cancel_pending = function () {
|
||||
if (this._tooltip_timeout !== null) {
|
||||
clearTimeout(this._tooltip_timeout);
|
||||
this._tooltip_timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
// will trigger tooltip after timeout
|
||||
Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
|
||||
var that = this;
|
||||
this._tooltip_timeout = setTimeout(function () {
|
||||
that.request(cell, hide_if_no_docstring);
|
||||
}, that.time_before_tooltip);
|
||||
};
|
||||
|
||||
// easy access for julia monkey patching.
|
||||
Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
|
||||
|
||||
Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
|
||||
var callbacks = $.proxy(this._show, this);
|
||||
var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
|
||||
};
|
||||
|
||||
// make an immediate completion request
|
||||
Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
|
||||
/**
|
||||
* request(codecell)
|
||||
* Deal with extracting the text from the cell and counting
|
||||
* call in a row
|
||||
*/
|
||||
this.cancel_pending();
|
||||
var editor = cell.code_mirror;
|
||||
var cursor = editor.getCursor();
|
||||
var text = cell.get_text();
|
||||
var cursor_pos = utils.js_idx_to_char_idx(editor.indexFromPos(cursor), text);
|
||||
|
||||
this._hide_if_no_docstring = hide_if_no_docstring;
|
||||
|
||||
if(editor.somethingSelected()){
|
||||
// get only the most recent selection.
|
||||
text = editor.getSelection();
|
||||
}
|
||||
|
||||
// need a permanent handle to code_mirror for future auto recall
|
||||
this.code_mirror = editor;
|
||||
|
||||
// now we treat the different number of keypress
|
||||
// first if same cell, same text, increment counter by 1
|
||||
if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
|
||||
this._consecutive_counter++;
|
||||
} else {
|
||||
// else reset
|
||||
this.cancel_stick();
|
||||
this.reset_tabs_function (cell, text);
|
||||
}
|
||||
|
||||
this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
|
||||
|
||||
// then if we are at the end of list function, reset
|
||||
if (this._consecutive_counter == this.tabs_functions.length) {
|
||||
this.reset_tabs_function (cell, text, cursor);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
// cancel the option of having the tooltip to stick
|
||||
Tooltip.prototype.cancel_stick = function () {
|
||||
clearTimeout(this._stick_timeout);
|
||||
this._stick_timeout = null;
|
||||
this._clocklink.hide('slow');
|
||||
this._sticky = false;
|
||||
};
|
||||
|
||||
// put the tooltip in a sticky state for 10 seconds
|
||||
// it won't be removed by remove_and_cancel() unless you called with
|
||||
// the first parameter set to true.
|
||||
// remove_and_cancel_tooltip(true)
|
||||
Tooltip.prototype.stick = function (time) {
|
||||
time = (time !== undefined) ? time : 10;
|
||||
var that = this;
|
||||
this._sticky = true;
|
||||
this._clocklink.show('slow');
|
||||
this._stick_timeout = setTimeout(function () {
|
||||
that._sticky = false;
|
||||
that._clocklink.hide('slow');
|
||||
}, time * 1000);
|
||||
};
|
||||
|
||||
// should be called with the kernel reply to actually show the tooltip
|
||||
Tooltip.prototype._show = function (reply) {
|
||||
/**
|
||||
* move the bubble if it is not hidden
|
||||
* otherwise fade it
|
||||
*/
|
||||
this._reply = reply;
|
||||
var content = reply.content;
|
||||
if (!content.found) {
|
||||
// object not found, nothing to show
|
||||
return;
|
||||
}
|
||||
this.name = content.name;
|
||||
|
||||
// do some math to have the tooltip arrow on more or less on left or right
|
||||
// position of the editor
|
||||
var cm_pos = $(this.code_mirror.getWrapperElement()).position();
|
||||
var cell_pos = $(this.code_mirror.getWrapperElement().offsetParent).position();
|
||||
|
||||
// anchor and head positions are local within CodeMirror element
|
||||
var anchor = this.code_mirror.cursorCoords(false, 'local');
|
||||
var head = this.code_mirror.cursorCoords(true, 'local');
|
||||
// locate the target at the center of anchor, head
|
||||
var center_left = (head.left + anchor.left) / 2;
|
||||
// locate the left edge of the tooltip, at most 450 px left of the arrow
|
||||
var edge_left = Math.max(center_left - 450, 0);
|
||||
// locate the arrow at the cursor. A 24 px offset seems necessary.
|
||||
var arrow_left = center_left - edge_left - 24;
|
||||
|
||||
// locate left, top within container element
|
||||
var left = (cell_pos.left + cm_pos.left + edge_left) + 'px';
|
||||
var top = (cell_pos.top + cm_pos.top + head.bottom + 10) + 'px';
|
||||
|
||||
if (this._hidden === false) {
|
||||
this.tooltip.animate({
|
||||
left: left,
|
||||
top: top
|
||||
});
|
||||
} else {
|
||||
this.tooltip.css({
|
||||
left: left
|
||||
});
|
||||
this.tooltip.css({
|
||||
top: top
|
||||
});
|
||||
}
|
||||
this.arrow.animate({
|
||||
'left': arrow_left + 'px'
|
||||
});
|
||||
|
||||
this._hidden = false;
|
||||
this.tooltip.fadeIn('fast');
|
||||
this.text.children().remove();
|
||||
|
||||
// This should support rich data types, but only text/plain for now
|
||||
// Any HTML within the docstring is escaped by the fixConsole() method.
|
||||
var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
|
||||
this.text.append(pre);
|
||||
// keep scroll top to be sure to always see the first line
|
||||
this.text.scrollTop(0);
|
||||
};
|
||||
|
||||
return {'Tooltip': Tooltip};
|
||||
});
|
||||