mirror of
https://github.com/wassname/jupyter_contrib_nbextensions.git
synced 2026-06-27 16:10:24 +08:00
Merge pull request #644 from jcb91/split_configurator
replace config extension
This commit is contained in:
@@ -30,7 +30,9 @@ Documentation
|
||||
=============
|
||||
|
||||
In the 4.x Jupyter repository, all extensions that are maintained and active have a markdown readme file for
|
||||
documentation and a yaml file to allow them being configured using the 'nbextensions' server extension.
|
||||
documentation and a yaml file to allow them being configured using the
|
||||
[`jupyter_nbextensions_configurator`](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator)
|
||||
server extension.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from __future__ import print_function
|
||||
from jupyter_core.paths import jupyter_config_dir, jupyter_data_dir
|
||||
from notebook import version_info
|
||||
from jupyter_nbextensions_configurator.application import main as jnc_app_main
|
||||
from traitlets.config.loader import Config, JSONFileConfigLoader
|
||||
import os
|
||||
import sys
|
||||
@@ -153,12 +154,8 @@ update_config(py_config)
|
||||
json_file = 'jupyter_notebook_config.json'
|
||||
config = load_json_config(json_file)
|
||||
|
||||
# Add server extension of /nbextension/ configuration tool and template path
|
||||
# Add template path
|
||||
newconfig = Config()
|
||||
if version_info[1] > 1:
|
||||
newconfig.NotebookApp.nbserver_extensions = {"nbextensions": True}
|
||||
else:
|
||||
newconfig.NotebookApp.server_extensions = ["nbextensions"]
|
||||
newconfig.NotebookApp.extra_template_paths = [os.path.join(jupyter_data_dir(),'templates') ]
|
||||
config.merge(newconfig)
|
||||
config.version = 1
|
||||
@@ -167,3 +164,6 @@ save_json_config(json_file, config)
|
||||
# Update notebook PY configuration
|
||||
py_config = os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.py')
|
||||
update_config(py_config)
|
||||
|
||||
# Configure the jupyter_nbextensions_configurator serverextension
|
||||
jnc_app_main(['enable', '--user', '--debug'])
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) IPython-Contrib Team.
|
||||
|
||||
"""Notebook Server Extension to activate, deactivate and configure javascript
|
||||
notebook extensions"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import posixpath
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
from notebook.base.handlers import IPythonHandler
|
||||
from notebook.notebookapp import NotebookApp
|
||||
from notebook.utils import url_path_join as ujoin
|
||||
from notebook.utils import path2url
|
||||
from tornado import web
|
||||
from traitlets.config.configurable import MultipleInstanceError
|
||||
from yaml.scanner import ScannerError
|
||||
|
||||
# attempt to use LibYaml if available
|
||||
try:
|
||||
from yaml import CSafeLoader as SafeLoader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader
|
||||
|
||||
absolute_url_re = re.compile(r'^(f|ht)tps?://')
|
||||
|
||||
|
||||
def get_nbextensions_path():
|
||||
"""
|
||||
Return the nbextensions search path
|
||||
|
||||
gets the search path for
|
||||
- the current NotebookApp instance
|
||||
or, if we can't instantiate one
|
||||
- the default config used, found by spawning a subprocess
|
||||
"""
|
||||
# Attempt to get the path for the currently-running config, or the default
|
||||
# if one isn't running.
|
||||
# If there's already a non-NotebookApp traitlets app running, e.g. when
|
||||
# we're inside an IPython kernel there's a KernelApp instantiated, then
|
||||
# attempting to get a NotebookApp instance will raise a
|
||||
# MultipleInstanceError.
|
||||
try:
|
||||
return NotebookApp.instance().nbextensions_path
|
||||
except MultipleInstanceError:
|
||||
# So, we spawn a new python process to get paths for the default config
|
||||
cmd = "from {0} import {1}; [print(p) for p in {1}()]".format(
|
||||
get_nbextensions_path.__module__, get_nbextensions_path.__name__)
|
||||
|
||||
return subprocess.check_output([
|
||||
sys.executable, '-c', cmd
|
||||
]).decode(sys.stdout.encoding).split('\n')
|
||||
|
||||
|
||||
def get_configurable_nbextensions(
|
||||
nbextension_dirs=None, exclude_dirs=['mathjax'], log=None):
|
||||
"""Build a list of configurable nbextensions based on YAML descriptor files
|
||||
|
||||
descriptor files must:
|
||||
- be located under one of nbextension_dirs
|
||||
- have the extension '.yaml'
|
||||
- containing (at minimum) the following keys:
|
||||
- Type: must be 'IPython Notebook Extension' or
|
||||
'Jupyter Notebook Extension'
|
||||
- Main: url of the nbextension's main javascript file, relative to yaml
|
||||
"""
|
||||
|
||||
if nbextension_dirs is None:
|
||||
nbextension_dirs = get_nbextensions_path()
|
||||
|
||||
extension_list = []
|
||||
required_keys = {'Type', 'Main'}
|
||||
valid_types = {'IPython Notebook Extension', 'Jupyter Notebook Extension'}
|
||||
do_log = (log is not None)
|
||||
# Traverse through nbextension subdirectories to find all yaml files
|
||||
for root_nbext_dir in nbextension_dirs:
|
||||
if do_log:
|
||||
log.debug(
|
||||
'Looking for nbextension yaml descriptor files in {}'.format(
|
||||
root_nbext_dir))
|
||||
for direct, dirs, files in os.walk(root_nbext_dir, followlinks=True):
|
||||
# filter to exclude directories
|
||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||
for filename in files:
|
||||
if not filename.endswith('.yaml'):
|
||||
continue
|
||||
yaml_path = os.path.join(direct, filename)
|
||||
yaml_relpath = os.path.relpath(yaml_path, root_nbext_dir)
|
||||
with open(yaml_path, 'r') as stream:
|
||||
try:
|
||||
extension = yaml.load(stream, Loader=SafeLoader)
|
||||
except ScannerError:
|
||||
if do_log:
|
||||
log.warning(
|
||||
'Failed to load yaml file {}'.format(
|
||||
yaml_relpath))
|
||||
continue
|
||||
if not isinstance(extension, dict):
|
||||
continue
|
||||
if any(key not in extension for key in required_keys):
|
||||
continue
|
||||
if extension['Type'].strip() not in valid_types:
|
||||
continue
|
||||
extension.setdefault('Compatibility', '?.x')
|
||||
extension.setdefault('Section', 'notebook')
|
||||
|
||||
# generate relative URLs within the nbextensions namespace,
|
||||
# from urls relative to the yaml file
|
||||
yaml_dir_url = path2url(os.path.dirname(yaml_relpath))
|
||||
key_map = [
|
||||
('Link', 'readme'),
|
||||
('Icon', 'icon'),
|
||||
('Main', 'require'),
|
||||
]
|
||||
for from_key, to_key in key_map:
|
||||
# str needed in python 3, otherwise it ends up bytes
|
||||
from_val = str(extension.get(from_key, ''))
|
||||
if not from_val:
|
||||
continue
|
||||
if absolute_url_re.match(from_val):
|
||||
extension[to_key] = from_val
|
||||
else:
|
||||
extension[to_key] = posixpath.normpath(
|
||||
ujoin(yaml_dir_url, from_val))
|
||||
# strip .js extension in require path
|
||||
extension['require'] = os.path.splitext(
|
||||
extension['require'])[0]
|
||||
|
||||
if do_log:
|
||||
log.debug(
|
||||
'Found nbextension {!r} in {}'.format(
|
||||
extension.setdefault('Name', extension['require']),
|
||||
yaml_relpath,
|
||||
)
|
||||
)
|
||||
|
||||
extension_list.append(extension)
|
||||
return extension_list
|
||||
|
||||
|
||||
class NBExtensionHandler(IPythonHandler):
|
||||
"""Render the notebook extension configuration interface."""
|
||||
|
||||
@web.authenticated
|
||||
def get(self):
|
||||
extension_list = get_configurable_nbextensions(log=self.log)
|
||||
# dump to JSON, replacing any single quotes with HTML representation
|
||||
extension_list_json = json.dumps(extension_list).replace("'", "'")
|
||||
|
||||
self.write(self.render_template(
|
||||
'nbextensions.html',
|
||||
base_url=self.base_url,
|
||||
extension_list=extension_list_json,
|
||||
page_title="Notebook Extension Configuration"
|
||||
))
|
||||
|
||||
|
||||
class RenderExtensionHandler(IPythonHandler):
|
||||
"""Render given markdown file"""
|
||||
|
||||
@web.authenticated
|
||||
def get(self, path):
|
||||
if not path.endswith('.md'):
|
||||
# for all non-markdown items, we redirect to the actual file
|
||||
return self.redirect(self.base_url + path)
|
||||
self.write(self.render_template(
|
||||
'rendermd.html',
|
||||
base_url=self.base_url,
|
||||
md_url=path,
|
||||
page_title=path,
|
||||
))
|
||||
|
||||
|
||||
def load_jupyter_server_extension(nbapp):
|
||||
webapp = nbapp.web_app
|
||||
base_url = webapp.settings['base_url']
|
||||
|
||||
webapp.add_handlers(".*$", [
|
||||
(ujoin(base_url, r"/nbextensions"), NBExtensionHandler),
|
||||
(ujoin(base_url, r"/nbextensions/"), NBExtensionHandler),
|
||||
(ujoin(base_url, r"/nbextensions/config/rendermd/(.*)"),
|
||||
RenderExtensionHandler),
|
||||
])
|
||||
@@ -1,6 +0,0 @@
|
||||
Type: IPython Notebook Extension
|
||||
Name: NbExtensions menu item
|
||||
Description: Add an edit-menu item to open the NbExtensions config page
|
||||
Main: main.js
|
||||
Compatibility: 4.x
|
||||
Icon: icon.png
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,29 +0,0 @@
|
||||
// extension by jcb91
|
||||
// Tiny extension to add an edit-menu item to open the NbExtensions config page
|
||||
|
||||
define(['jquery', 'base/js/namespace'], function ($, Jupyter) {
|
||||
"use strict";
|
||||
|
||||
var load_ipython_extension = function () {
|
||||
var menu_item = $('<li/>').append(
|
||||
$('<a/>', {
|
||||
'target' : '_blank',
|
||||
'title' : 'Opens in a new window',
|
||||
'href' : Jupyter.notebook.base_url + 'nbextensions/',
|
||||
})
|
||||
.append(' ')
|
||||
.append($('<i/>', {'class' : 'fa fa-cogs menu-icon pull-right'}))
|
||||
.append($('<span/>').html('nbextensions config'))
|
||||
);
|
||||
|
||||
var edit_menu = $('#edit_menu');
|
||||
edit_menu.append($('<li/>').addClass('divider'));
|
||||
edit_menu.append(menu_item);
|
||||
};
|
||||
|
||||
// export the extension so it can be loaded correctly
|
||||
var extension = {
|
||||
load_ipython_extension : load_ipython_extension
|
||||
};
|
||||
return extension;
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 398 KiB |
@@ -1,364 +0,0 @@
|
||||
define([
|
||||
'bootstrap', // for modals
|
||||
'jquery',
|
||||
'base/js/dialog',
|
||||
'base/js/utils',
|
||||
'base/js/keyboard',
|
||||
'notebook/js/quickhelp',
|
||||
'./quickhelp_shim'
|
||||
], function(
|
||||
bs,
|
||||
$,
|
||||
dialog,
|
||||
utils,
|
||||
keyboard,
|
||||
quickhelp,
|
||||
quickhelp_shim
|
||||
){
|
||||
"use strict";
|
||||
|
||||
function only_modifier_event (event) {
|
||||
// adapted from base/js/keyboard
|
||||
/**
|
||||
* Return `true` if the event only contains modifiers keys, false
|
||||
* otherwise
|
||||
*/
|
||||
var key = keyboard.inv_keycodes[event.which];
|
||||
return ((event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) &&
|
||||
(key === 'alt'|| key === 'ctrl'|| key === 'meta'|| key === 'shift'));
|
||||
}
|
||||
|
||||
function editor_build () {
|
||||
var editor = $('#kse-editor');
|
||||
if (editor.length > 0) {
|
||||
return editor;
|
||||
}
|
||||
|
||||
editor = $('<div/>')
|
||||
.addClass('kse-editor')
|
||||
.attr('id', 'kse-editor')
|
||||
.data({
|
||||
'kse_sequence': [],
|
||||
'kse_info': {},
|
||||
'kse_mode': 'command',
|
||||
'kse_undefined_key': false
|
||||
});
|
||||
|
||||
var form = $('<form/>')
|
||||
.addClass('form')
|
||||
.appendTo(editor);
|
||||
|
||||
$('<div/>')
|
||||
.addClass('form-group')
|
||||
.appendTo(form);
|
||||
|
||||
var form_group = $('<div/>')
|
||||
.addClass('form-group has-feedback')
|
||||
.appendTo(form);
|
||||
|
||||
var input_group = $('<div/>')
|
||||
.addClass('input-group')
|
||||
.addClass('kse-input-group')
|
||||
.appendTo(form_group);
|
||||
|
||||
// reset button
|
||||
var btn = $('<a/>')
|
||||
.addClass('btn btn-default')
|
||||
.addClass('kse-input-group-reset')
|
||||
.attr({
|
||||
'title': 'Restart',
|
||||
'type': 'button'
|
||||
})
|
||||
.append(
|
||||
$('<i/>')
|
||||
.addClass('fa fa-repeat')
|
||||
)
|
||||
.on('click', function () {
|
||||
editor.data({
|
||||
'kse_sequence': [],
|
||||
'kse_undefined_key': false
|
||||
});
|
||||
editor_update_input_group(editor);
|
||||
$(this).blur();
|
||||
textcontrol.focus();
|
||||
});
|
||||
$('<div/>')
|
||||
.addClass('input-group-btn')
|
||||
.append(btn)
|
||||
.appendTo(input_group);
|
||||
|
||||
// pretty-displayed shortcut
|
||||
$('<div/>')
|
||||
.addClass('input-group-addon')
|
||||
.addClass('kse-input-group-pretty')
|
||||
.addClass('kse-editor-to')
|
||||
.appendTo(input_group);
|
||||
|
||||
var textcontrol = $('<input/>')
|
||||
.addClass('form-control')
|
||||
.addClass('kse-input-group-input')
|
||||
.attr({
|
||||
'type': 'text',
|
||||
'placeholder': 'click here to edit the shortcut'
|
||||
})
|
||||
.on('keydown', editor_handle_shortcut_keydown)
|
||||
.on('focus', function (evt) {
|
||||
$(this).attr('placeholder', 'press keys to add to the shortcut');
|
||||
})
|
||||
.on('blur', function (evt) {
|
||||
$(this).attr('placeholder', 'click here to edit the shortcut');
|
||||
})
|
||||
.appendTo(input_group);
|
||||
|
||||
// feedback icon
|
||||
var form_fdbck = $('<i/>')
|
||||
.addClass('fa fa-lg');
|
||||
$('<span/>')
|
||||
.addClass('form-control-feedback')
|
||||
.append(form_fdbck)
|
||||
.appendTo(form_group);
|
||||
|
||||
// help for input group
|
||||
$('<span/>')
|
||||
.addClass('help-block')
|
||||
.appendTo(form_group);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
function editor_update_input_group (editor, seq) {
|
||||
seq = seq || editor.data('kse_sequence');
|
||||
var shortcut = seq.join(',');
|
||||
var mode = editor.data('kse_mode');
|
||||
var have_seq = seq.length > 0;
|
||||
var valid = have_seq;
|
||||
|
||||
// empty help block
|
||||
var feedback = editor.find('.form-group.has-feedback:first');
|
||||
var help_block = feedback.find('.help-block');
|
||||
help_block.empty();
|
||||
|
||||
var ii;
|
||||
var has_comma = false;
|
||||
for (ii = 0; !has_comma && (ii < seq.length); ii++) {
|
||||
has_comma = seq[ii].indexOf(',') >= 0;
|
||||
}
|
||||
|
||||
if (has_comma) {
|
||||
valid = false;
|
||||
// use HTML Unicode escape for a comma, to get it to look right in the pretty version
|
||||
shortcut = $.map(seq, function (elem, idx) {
|
||||
return elem.replace(',', ',');
|
||||
}).join(',');
|
||||
|
||||
$('<p/>')
|
||||
.html(
|
||||
'Unfortunately, Jupyter\'s handling of shortcuts containing ' +
|
||||
'commas (<kbd>,</kbd>) is fundamentally flawed, ' +
|
||||
'as the comma is used as the key-separator character ☹. ' +
|
||||
'Please try something else for your rebind!'
|
||||
)
|
||||
.appendTo(help_block);
|
||||
}
|
||||
else if (have_seq) {
|
||||
var conflicts = {};
|
||||
var tree;
|
||||
|
||||
// get existing shortcuts
|
||||
if (Jupyter.keyboard_manager !== undefined) {
|
||||
var startkey = seq.slice(0, 1)[0];
|
||||
if (mode === 'command') {
|
||||
tree = Jupyter.keyboard_manager.command_shortcuts.get_shortcut(startkey);
|
||||
}
|
||||
else {
|
||||
tree = Jupyter.keyboard_manager.edit_shortcuts.get_shortcut(startkey);
|
||||
// deal with codemirror shortcuts specially, since they're not included in kbm
|
||||
for (var jj = 0; jj < quickhelp.cm_shortcuts.length; jj++) {
|
||||
var cm_shrt = quickhelp.cm_shortcuts[jj];
|
||||
if (keyboard.normalize_shortcut(cm_shrt.shortcut) === startkey) {
|
||||
tree = cm_shrt.help;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for conflicting shortcuts.
|
||||
// Start at 1 because we got tree from startkey
|
||||
for (ii = 1; (ii < seq.length) && (tree !== undefined); ii++) {
|
||||
// check for exsiting definitions at current specificity
|
||||
if (typeof(tree) === 'string') {
|
||||
valid = false;
|
||||
conflicts[seq.slice(0, ii).join(',')] = tree;
|
||||
break;
|
||||
}
|
||||
tree = tree[seq[ii]];
|
||||
}
|
||||
|
||||
// check whether any more-specific shortcuts were defined
|
||||
if ((ii === seq.length) && (tree !== undefined)) {
|
||||
valid = false;
|
||||
var flatten_conflict_tree = function flatten_conflict_tree (obj, key) {
|
||||
if (typeof(obj) === 'string') {
|
||||
conflicts[key] = obj;
|
||||
}
|
||||
else for (var subkey in obj) {
|
||||
if (obj.hasOwnProperty(subkey)) {
|
||||
flatten_conflict_tree(obj[key], [key, subkey].join(','));
|
||||
}
|
||||
}
|
||||
};
|
||||
flatten_conflict_tree(tree, seq.join(','));
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
var plural = Object.keys(conflicts).length != 1;
|
||||
$('<p/>')
|
||||
.append(quickhelp.humanize_sequence(seq.join(',')))
|
||||
.append(
|
||||
' conflicts with the' + (plural ? ' following' : '') +
|
||||
' existing shortcut' + (plural ? 's' : '') + ':'
|
||||
)
|
||||
.appendTo(help_block);
|
||||
|
||||
for (var conflicting_shortcut in conflicts) {
|
||||
if (conflicts.hasOwnProperty(conflicting_shortcut)) {
|
||||
$('<p/>')
|
||||
.append(quickhelp.humanize_sequence(conflicting_shortcut))
|
||||
.append($('<code/>').text(conflicts[conflicting_shortcut]))
|
||||
.appendTo(help_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.data('kse_undefined_key')) {
|
||||
var warning = $('<span/>')
|
||||
.addClass('form-group has-feedback has-warning kse-undefined')
|
||||
.append(
|
||||
$('<span/>')
|
||||
.addClass('help-block')
|
||||
.append(
|
||||
$('<p/>').text('Unrecognised key! (code ' + editor.data('kse_undefined_key' ) + ')')
|
||||
)
|
||||
);
|
||||
|
||||
var existing = editor.find('.kse-undefined');
|
||||
if (existing.length > 0) {
|
||||
existing.replaceWith(warning);
|
||||
}
|
||||
else {
|
||||
warning.insertAfter(feedback);
|
||||
}
|
||||
setTimeout(function () {
|
||||
warning.remove();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// disable reset button if no sequence
|
||||
editor.find('.kse-input-group-reset')
|
||||
.toggleClass('disabled', !have_seq);
|
||||
|
||||
editor.find('.kse-input-group-pretty')
|
||||
.html(shortcut ? quickhelp.humanize_sequence(shortcut) : '<new shortcut>');
|
||||
|
||||
feedback
|
||||
.toggleClass('has-error', !valid && have_seq)
|
||||
.toggleClass('has-success', valid && have_seq)
|
||||
.find('.form-control-feedback .fa')
|
||||
.toggleClass('fa-remove', !valid && have_seq)
|
||||
.toggleClass('fa-check', valid && have_seq);
|
||||
}
|
||||
|
||||
function editor_handle_shortcut_keydown (evt) {
|
||||
var elem = $(evt.delegateTarget);
|
||||
if (!only_modifier_event(evt)) {
|
||||
var shortcut = keyboard.normalize_shortcut(keyboard.event_to_shortcut(evt));
|
||||
var editor = elem.closest('#kse-editor');
|
||||
var seq = editor.data('kse_sequence');
|
||||
var has_undefined_key = (shortcut.toLowerCase().indexOf('undefined') !== -1);
|
||||
editor.data('kse_undefined_key', has_undefined_key);
|
||||
if (has_undefined_key) {
|
||||
// deal with things like ~ appearing on apple alt-n, or ¨ on alt-u
|
||||
editor.find('.kse-input-group-input').val('');
|
||||
editor.data('kse_undefined_key', evt.which || true);
|
||||
}
|
||||
else {
|
||||
seq.push(shortcut);
|
||||
}
|
||||
editor_update_input_group(editor, seq);
|
||||
}
|
||||
}
|
||||
|
||||
function modal_build (editor, modal_options) {
|
||||
var modal = $('#kse-editor-modal');
|
||||
if (modal.length > 0) {
|
||||
return modal;
|
||||
}
|
||||
|
||||
var default_modal_options = {
|
||||
'destroy': false,
|
||||
'show': false,
|
||||
'title': 'Edit keyboard shortcut',
|
||||
'body': editor,
|
||||
'buttons': {
|
||||
'OK': {'class': 'btn-primary'},
|
||||
'Cancel': {}
|
||||
},
|
||||
'open': function (evt) {
|
||||
$(this).find('.kse-input-group-input').focus();
|
||||
}
|
||||
};
|
||||
if (Jupyter.notebook !== undefined) {
|
||||
default_modal_options.notebook = Jupyter.notebook;
|
||||
}
|
||||
if (Jupyter.keyboard_manager !== undefined) {
|
||||
default_modal_options.keyboard_manager= Jupyter.keyboard_manager;
|
||||
}
|
||||
modal_options = $.extend({}, default_modal_options, modal_options);
|
||||
|
||||
modal = dialog.modal(modal_options);
|
||||
|
||||
modal
|
||||
.addClass('modal_stretch')
|
||||
.attr('id', 'kse-editor-modal');
|
||||
|
||||
// Add a data-target attribute to ensure buttons only target the editor modal
|
||||
modal.find('.close,.modal-footer button')
|
||||
.attr('data-target', '#kse-editor-modal');
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass it an option dictionary with any of the bootstrap or base/js/dialog
|
||||
* modal options, plus the following optional properties:
|
||||
* - description: html for the form group preceding the editor group,
|
||||
* useful as a description
|
||||
*/
|
||||
function KSE_modal (modal_options) {
|
||||
var editor = editor_build();
|
||||
editor.data({'kse_sequence': [], 'kse_undefined_key': false});
|
||||
editor_update_input_group(editor);
|
||||
var modal = modal_build(editor, modal_options);
|
||||
|
||||
editor.on('keydown', '.kse-input-group-input', function (evt) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
|
||||
if (modal_options.description) {
|
||||
modal.find('.modal-body .form-group:first').html(modal_options.description);
|
||||
}
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
return {
|
||||
editor_build : editor_build,
|
||||
editor_update_input_group: editor_update_input_group,
|
||||
modal_build : modal_build,
|
||||
KSE_modal : KSE_modal
|
||||
};
|
||||
});
|
||||
@@ -1,157 +0,0 @@
|
||||
/* nbext-page-title-wrap and nbext-page-title
|
||||
are styled to match the notebook save widget */
|
||||
.nbext-page-title-wrap {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.nbext-page-title {
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
padding: 3px;
|
||||
margin-left: 16px;
|
||||
border: none;
|
||||
font-size: 146.5%;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.nbext-showhide-incompat {
|
||||
padding-bottom: 0.5em;
|
||||
/* the selector is hidden by default, revealed by js if any incompatible
|
||||
extensions are found */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* styles for the extension-selector nav links */
|
||||
|
||||
.nbext-selector {
|
||||
border-bottom: 1px solid #888;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li > a {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li > a > .nbext-active-toggle {
|
||||
padding-right: 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li > a > .nbext-active-toggle:before {
|
||||
/* fa-square-o */
|
||||
content: "\f096";
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li > a > .nbext-activated:before {
|
||||
/* fa-check-square-o */
|
||||
content: "\f046";
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li.disabled > a > .nbext-active-toggle {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li.disabled > a > .nbext-active-toggle:before {
|
||||
/* fa-ban */
|
||||
content: "\f05e (incompatible)";
|
||||
}
|
||||
|
||||
.nbext-selector > nav > .nav > li.disabled > a > .nbext-activated:before {
|
||||
/* fa-exclamation-circle */
|
||||
content: "\f06a (incompatible, enabled)";
|
||||
}
|
||||
|
||||
/* bugfix for using .container with padding: we need 15px extra to width. */
|
||||
@media (min-width: 768px) and (max-width: 783px) {
|
||||
.container {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
/* styles for the readme */
|
||||
|
||||
.nbext-readme > .nbext-readme-contents {
|
||||
/* padding
|
||||
top 0 to ensure box shadow of child is hidden (with overflow-y: hidden)
|
||||
side & bottom sufficient to show box shadow of child */
|
||||
padding: 0 12px 12px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.nbext-readme > h3 {
|
||||
padding: 0 12px; /* to match nbext-readme-contents */
|
||||
}
|
||||
|
||||
.nbext-readme > .nbext-readme-contents:not(:empty) {
|
||||
margin-top: 0.5em;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.nbext-readme > .nbext-readme-contents > .rendered_html {
|
||||
padding: 0.5em 1em;
|
||||
/* z-index & margin-top to clip box shadow behind parent */
|
||||
margin-top: 0;
|
||||
z-index: -10;
|
||||
-webkit-box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
-moz-box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
}
|
||||
|
||||
/* styles for elements in an extension's UI */
|
||||
|
||||
.nbext-activate-btns .btn {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.nbext-activate-btns .btn[disabled] {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.nbext-icon {
|
||||
margin-left: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nbext-icon img {
|
||||
max-height: 120px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.nbext-icon,
|
||||
.nbext-desc,
|
||||
.nbext-compat-div,
|
||||
.nbext-activate-btns,
|
||||
.nbext-params {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.nbext-params > .panel-heading {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
/* styles for list inputs */
|
||||
|
||||
.nbext-list-element,
|
||||
.nbext-list-element-placeholder {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nbext-list-element-placeholder {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
border: 1px solid #fcefa1;
|
||||
background-color: #fbf9ee;
|
||||
color: #363636;
|
||||
}
|
||||
|
||||
.nbext-list-element:only-child > .handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nbext-list-element:only-child > .handle + .form-control {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.input-group .nbext-list-btn-add {
|
||||
font-size: inherit;
|
||||
}
|
||||
@@ -1,972 +0,0 @@
|
||||
// Copyright (c) IPython-Contrib Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Show notebook extension configuration
|
||||
|
||||
define([
|
||||
'jqueryui',
|
||||
'require',
|
||||
'base/js/namespace',
|
||||
'base/js/page',
|
||||
'base/js/utils',
|
||||
'services/config',
|
||||
'base/js/events',
|
||||
'notebook/js/quickhelp',
|
||||
'nbextensions/config/render/render',
|
||||
'nbextensions/config/kse_components'
|
||||
], function(
|
||||
$,
|
||||
require,
|
||||
Jupyter,
|
||||
page,
|
||||
utils,
|
||||
configmod,
|
||||
events,
|
||||
quickhelp,
|
||||
rendermd,
|
||||
kse_comp
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
var base_url = utils.get_body_data('baseUrl');
|
||||
var first_load_done = false; // flag used to not push history on first load
|
||||
var extensions_dict = {}; // dictionary storing extensions by their 'require' value
|
||||
|
||||
/**
|
||||
* create configs var from json files on server.
|
||||
* we still need to call configs[].load later to actually fetch them though!
|
||||
*/
|
||||
var configs = {
|
||||
'notebook' : new configmod.ConfigSection('notebook', {base_url: base_url}),
|
||||
'edit' : new configmod.ConfigSection('edit', {base_url: base_url}),
|
||||
'tree' : new configmod.ConfigSection('tree', {base_url: base_url}),
|
||||
'common' : new configmod.ConfigSection('common', {base_url: base_url}),
|
||||
};
|
||||
|
||||
// the prefix added to all parameter input id's
|
||||
var param_id_prefix = 'input_';
|
||||
|
||||
/**
|
||||
* check whether a dot-notation key exists in a given ConfigSection object
|
||||
*
|
||||
* @param {ConfigSection} conf - the config section to query
|
||||
* @param {string} key - the (dot-notation) key to check for
|
||||
* @return {Boolean} - `true` if the key exists, `false` otherwise
|
||||
*/
|
||||
function conf_dot_key_exists(conf, key) {
|
||||
var obj = conf.data;
|
||||
key = key.split('.');
|
||||
while (key.length > 0) {
|
||||
var partkey = key.shift();
|
||||
if (!obj.hasOwnProperty(partkey)) {
|
||||
return false;
|
||||
}
|
||||
obj = obj[partkey];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value for a dot-notation key in a given ConfigSection object
|
||||
*
|
||||
* @param {ConfigSection} conf - the config section to query
|
||||
* @param {string} key - the (dot-notation) key to get the value of
|
||||
* @return - the value associated with the given key
|
||||
*/
|
||||
function conf_dot_get (conf, key) {
|
||||
var obj = conf.data;
|
||||
key = key.split('.');
|
||||
while (key.length > 0) {
|
||||
obj = obj[key.shift()];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the value for a dot-notation key in a given ConfigSection object
|
||||
*
|
||||
* @param {ConfigSection} conf - the config section to update
|
||||
* @param {string} key - the (dot-notation) key to update the value of
|
||||
* @param value - the new value to set. null results in removal of the key
|
||||
* @return - the return value of the ConfigSection.update call
|
||||
*/
|
||||
function conf_dot_update (conf, key, value) {
|
||||
key = key.split('.');
|
||||
var root = {};
|
||||
var curr = root;
|
||||
while (key.length > 1) {
|
||||
curr = curr[key.shift()] = {};
|
||||
}
|
||||
curr[key.shift()] = value;
|
||||
return conf.update(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update server's json config file to reflect changed activate state
|
||||
*/
|
||||
function set_config_active (extension, state) {
|
||||
state = state === undefined ? true : state;
|
||||
console.log('Notebook extension "' + extension.Name + '"', state ? 'enabled' : 'disabled');
|
||||
var to_load = {};
|
||||
to_load[extension.require] = (state ? true : null);
|
||||
configs[extension.Section].update({load_extensions: to_load});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update buttons to reflect changed activate state
|
||||
*/
|
||||
function set_buttons_active (extension, state) {
|
||||
state = (state === true);
|
||||
|
||||
extension.selector_link.find('.nbext-active-toggle').toggleClass('nbext-activated', state);
|
||||
|
||||
var btns = $(extension.ui).find('.nbext-activate-btns').children();
|
||||
btns.eq(0)
|
||||
.prop('disabled', state)
|
||||
.toggleClass('btn-default disabled', state)
|
||||
.toggleClass('btn-primary', !state);
|
||||
btns.eq(1)
|
||||
.prop('disabled', !state)
|
||||
.toggleClass('btn-default disabled', !state)
|
||||
.toggleClass('btn-primary', state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle button click event to activate/deactivate extension
|
||||
*/
|
||||
function handle_buttons_click (evt) {
|
||||
var btn = $(evt.target);
|
||||
var state = btn.is(':first-child');
|
||||
var extension = btn.closest('.nbext-ext-row').data('extension');
|
||||
set_buttons_active(extension, state);
|
||||
set_config_active(extension, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the useful value (dependent on element type) from an input element
|
||||
*/
|
||||
function get_input_value (input) {
|
||||
input = $(input);
|
||||
var input_type = input.data('param_type');
|
||||
|
||||
switch (input_type) {
|
||||
case 'hotkey':
|
||||
return input.find('.hotkey').data('pre-humanized');
|
||||
case 'list':
|
||||
var val = [];
|
||||
input.find('.nbext-list-element').children().not('a').each(
|
||||
function () {
|
||||
// "this" is the current child element of input in the loop
|
||||
val.push(get_input_value(this));
|
||||
}
|
||||
);
|
||||
return val;
|
||||
case 'checkbox':
|
||||
return input.prop('checked') ? true : false;
|
||||
default:
|
||||
return input.val();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the useful value (dependent on element type) from a js value
|
||||
*/
|
||||
function set_input_value (input, new_value) {
|
||||
input = $(input);
|
||||
var input_type = input.data('param_type');
|
||||
switch (input_type) {
|
||||
case 'hotkey':
|
||||
input.find('.hotkey')
|
||||
.html(quickhelp.humanize_sequence(new_value))
|
||||
.data('pre-humanized', new_value);
|
||||
break;
|
||||
case 'list':
|
||||
var ul = input.children('ul');
|
||||
ul.empty();
|
||||
var list_element_param = input.data('list_element_param');
|
||||
for (var ii = 0; ii < new_value.length; ii++) {
|
||||
var list_element_input = build_param_input(list_element_param);
|
||||
list_element_input.on('change', handle_input);
|
||||
set_input_value(list_element_input, new_value[ii]);
|
||||
ul.append(wrap_list_input(list_element_input));
|
||||
}
|
||||
break;
|
||||
case 'checkbox':
|
||||
input.prop('checked', new_value ? true : false);
|
||||
break;
|
||||
default:
|
||||
input.val(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle form input for extension parameters, updating parameters in
|
||||
* server's json config file
|
||||
*/
|
||||
function handle_input (evt) {
|
||||
var input = $(evt.target);
|
||||
|
||||
// list elements should alter their parent's config
|
||||
if (input.closest('.nbext-list-wrap').length > 0) {
|
||||
input = input.closest('.nbext-list-wrap');
|
||||
}
|
||||
// hotkeys need to find the correct tag
|
||||
else if (input.hasClass('hotkey')) {
|
||||
input = input.closest('.input-group');
|
||||
}
|
||||
|
||||
// get param name by cutting off prefix
|
||||
var configkey = input.attr('id').substring(param_id_prefix.length);
|
||||
var configval = get_input_value(input);
|
||||
var configsection = input.data('section');
|
||||
console.log(configsection + '.' + configkey, '->', configval);
|
||||
conf_dot_update(configs[configsection], configkey, configval);
|
||||
return configval;
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap a single list-element input with the <li>, and move/remove buttons
|
||||
*/
|
||||
function wrap_list_input (list_input) {
|
||||
var btn_remove = $('<a/>', {'class': 'btn btn-default input-group-addon nbext-list-el-btn-remove'});
|
||||
btn_remove.append($('<i/>', {'class': 'fa fa-fw fa-trash'}));
|
||||
btn_remove.on('click', function () {
|
||||
var list_el = $(this).closest('li');
|
||||
var list_input = list_el.closest('.nbext-list-wrap');
|
||||
list_el.remove();
|
||||
list_input.change(); // trigger change event
|
||||
});
|
||||
|
||||
return $('<li/>', {'class' : 'nbext-list-element input-group'}).append(
|
||||
$('<a class="btn btn-default input-group-addon handle"/>').append(
|
||||
$('<i class="fa fa-fw fa-arrows-v"/>')
|
||||
),
|
||||
[list_input, btn_remove]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return an element used to edit a parameter
|
||||
*/
|
||||
function build_param_input (param) {
|
||||
var input_type = (param.input_type || 'text').toLowerCase();
|
||||
var input;
|
||||
|
||||
switch (input_type) {
|
||||
case 'hotkey':
|
||||
input = $('<div class="input-group"/>');
|
||||
input.append(
|
||||
$('<span class="form-control form-control-static hotkey"/>')
|
||||
.css(utils.platform === 'MasOS' ? {'letter-spacing': '1px'} : {})
|
||||
);
|
||||
input.append($('<div class="input-group-btn"/>').append(
|
||||
$('<div class="btn-group"/>').append(
|
||||
$('<a/>', {
|
||||
type: 'button',
|
||||
class: 'btn btn-primary',
|
||||
text: 'Change'
|
||||
}).on('click', function() {
|
||||
var description = 'Change ' +
|
||||
param.description +
|
||||
' from ' +
|
||||
quickhelp.humanize_sequence(get_input_value(input)) +
|
||||
' to:';
|
||||
var modal = kse_comp.KSE_modal({
|
||||
'description': description,
|
||||
'buttons': {
|
||||
'OK': {
|
||||
'class': 'btn-primary',
|
||||
'click': function () {
|
||||
var editor = $(this).find('#kse-editor');
|
||||
var new_value = (editor.data('kse_sequence') || []).join(',');
|
||||
set_input_value(input, new_value);
|
||||
// trigger write to config
|
||||
input.find('.hotkey').change();
|
||||
}
|
||||
},
|
||||
'Cancel': {}
|
||||
},
|
||||
});
|
||||
modal.modal('show');
|
||||
})
|
||||
)
|
||||
));
|
||||
break;
|
||||
case 'list':
|
||||
input = $('<div/>', {'class' : 'nbext-list-wrap'});
|
||||
input.append(
|
||||
$('<ul/>', {'class': 'list-unstyled'})
|
||||
.sortable({
|
||||
handle: '.handle',
|
||||
containment: 'window',
|
||||
placeholder: 'nbext-list-element-placeholder',
|
||||
update: function(event, ui) {
|
||||
ui.item.closest('.nbext-list-wrap').change();
|
||||
}
|
||||
})
|
||||
);
|
||||
var list_element_param = param.list_element || {};
|
||||
// add the requested list param type to the list element using
|
||||
// jquery data api
|
||||
input.data('list_element_param', list_element_param);
|
||||
|
||||
// add a button to add list elements
|
||||
var add_button = $('<a/>')
|
||||
.addClass('btn btn-default input-group-btn nbext-list-btn-add')
|
||||
.append($('<i/>', {'class': 'fa fa-plus'}).text(' new item'))
|
||||
.on('click', function () {
|
||||
$(this).parent().siblings('ul').append(
|
||||
wrap_list_input(
|
||||
build_param_input(list_element_param)
|
||||
.on('change', handle_input)
|
||||
)
|
||||
).closest('.nbext-list-wrap').change();
|
||||
});
|
||||
input.append($('<div class="input-group"/>').append(add_button));
|
||||
break;
|
||||
case 'textarea':
|
||||
input = $('<textarea/>');
|
||||
break;
|
||||
case 'number':
|
||||
input = $('<input/>', {'type': input_type});
|
||||
if (param.step !== undefined) input.attr('step', param.step);
|
||||
if (param.min !== undefined) input.attr('min', param.min);
|
||||
if (param.max !== undefined) input.attr('max', param.max);
|
||||
break;
|
||||
default:
|
||||
// detect html5 input tag support using scheme from
|
||||
// http://diveintohtml5.info/detect.html#input-types
|
||||
// If the browser supports the requested particular input type,
|
||||
// the type property will retain the value you set.
|
||||
// If the browser does not support the requested input type,
|
||||
// it will ignore the value you set
|
||||
// and the type property will still be "text".
|
||||
input = document.createElement('input');
|
||||
input.setAttribute('type', input_type);
|
||||
// wrap in jquery
|
||||
input = $(input);
|
||||
}
|
||||
// add the param type to the element using jquery data api
|
||||
input.data('param_type', input_type);
|
||||
input.data('section', param.section);
|
||||
var non_form_control_input_types = ['checkbox', 'list', 'hotkey'];
|
||||
if (non_form_control_input_types.indexOf(input_type) < 0) {
|
||||
input.addClass('form-control');
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build and return a div containing the buttons to activate/deactivate an
|
||||
* extension with the given id.
|
||||
*/
|
||||
function build_activate_buttons () {
|
||||
var div_buttons = $('<div class="btn-group nbext-activate-btns"/>');
|
||||
|
||||
var btn_activate = $('<button/>', {
|
||||
'type': 'button',
|
||||
'class': 'btn btn-primary'
|
||||
}).text('Activate').on('click', handle_buttons_click);
|
||||
btn_activate.appendTo(div_buttons);
|
||||
|
||||
var btn_deactivate = $('<button/>', {
|
||||
'type': 'button',
|
||||
'class': 'btn btn-default'
|
||||
}).text('Deactivate').on('click', handle_buttons_click);
|
||||
btn_deactivate.appendTo(div_buttons);
|
||||
|
||||
btn_deactivate.prop('disabled', true);
|
||||
return div_buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* show/hide compatibility text, along with en/disabling the nav link
|
||||
*/
|
||||
function set_hide_incompat (hide_incompat) {
|
||||
$('.nbext-compat-div').toggle(!hide_incompat);
|
||||
$('.nbext-selector .nbext-incompatible')
|
||||
.toggleClass('disabled', hide_incompat)
|
||||
.attr('title', hide_incompat ? 'possibly incompatible' : '');
|
||||
set_input_value(
|
||||
$('#' + param_id_prefix + 'nbext_hide_incompat'), hide_incompat);
|
||||
|
||||
var selector = $('.nbext-selector');
|
||||
if (selector.find('li.active').first().hasClass('disabled')) {
|
||||
selector.find('li:not(.disabled) a').first().click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if the extension's readme is a relative url with extension .md,
|
||||
* render the referenced markdown file
|
||||
* otherwise
|
||||
* add an anchor element to the extension's description
|
||||
*/
|
||||
function load_readme (extension) {
|
||||
var readme_div = $('.nbext-readme .nbext-readme-contents').empty();
|
||||
var readme_title = $('.nbext-readme > h3').empty();
|
||||
if (!extension.readme) return;
|
||||
|
||||
var url = extension.readme;
|
||||
var is_absolute = /^(f|ht)tps?:\/\//i.test(url);
|
||||
if (is_absolute || (utils.splitext(url)[1] !== '.md')) {
|
||||
// provide a link only
|
||||
var desc = extension.ui.find('.nbext-desc');
|
||||
var link = desc.find('.nbext-readme-more-link');
|
||||
if (link.length === 0) {
|
||||
desc.append(' ');
|
||||
link = $('<a/>')
|
||||
.addClass('nbext-readme-more-link')
|
||||
.text('more...')
|
||||
.attr('href', url)
|
||||
.appendTo(desc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// relative urls are in nbextensions namespace
|
||||
url = require.toUrl(utils.url_path_join(base_url, 'nbextensions', url));
|
||||
readme_title.text(url);
|
||||
// add rendered markdown to readme_div. Use pre-fetched if present
|
||||
if (extension.readme_content) {
|
||||
rendermd.render_markdown(extension.readme_content, url)
|
||||
.addClass('rendered_html')
|
||||
.appendTo(readme_div);
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'text',
|
||||
success: function (md_contents) {
|
||||
rendermd.render_markdown(md_contents, url)
|
||||
.addClass('rendered_html')
|
||||
.appendTo(readme_div);
|
||||
// We can't rely on picking up the rendered html,
|
||||
// since render_markdown returns
|
||||
// before the actual rendering work is complete
|
||||
extension.readme_content = md_contents;
|
||||
// attempt to scroll to a location hash, if there is one.
|
||||
var hash = window.location.hash.replace(/^#/, '');
|
||||
if (hash) {
|
||||
// Allow time for markdown to render
|
||||
setTimeout( function () {
|
||||
// use filter to avoid breaking jQuery selector syntax with weird id
|
||||
var hdr = readme_div.find(':header').filter(function (idx, elem) {
|
||||
return elem.id === hash;
|
||||
});
|
||||
if (hdr.length > 0) {
|
||||
var site = $('#site');
|
||||
var adjust = hdr.offset().top - site.offset().top;
|
||||
if (adjust > 0) {
|
||||
site.animate(
|
||||
{scrollTop: site.scrollTop() + adjust},
|
||||
undefined, // time
|
||||
undefined, // easing function
|
||||
function () {
|
||||
if (hdr.effect !== undefined) {
|
||||
hdr.effect('highlight', {color: '#faf2cc'});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
var error_div = $('<div class="text-danger bg-danger"/>')
|
||||
.text(textStatus + ' : ' + jqXHR.status + ' ' + errorThrown)
|
||||
.appendTo(readme_div);
|
||||
if (jqXHR.status === 404) {
|
||||
$('<p/>')
|
||||
.text('no markdown file at ' + url)
|
||||
.appendTo(error_div);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* open the user interface the extension corresponding to the given
|
||||
* link
|
||||
* @param extension the extension
|
||||
* @param opts options for the reveal animation
|
||||
*/
|
||||
function open_ext_ui (extension, opts) {
|
||||
var default_opts = {duration: 100};
|
||||
opts = $.extend(true, {}, default_opts, opts);
|
||||
|
||||
/**
|
||||
* Set window search string to allow reloading settings for a given
|
||||
* extension.
|
||||
* Use history.pushState if available, to avoid reloading the page
|
||||
*/
|
||||
var new_search = '?nbextension=' + encodeURIComponent(extension.require).replace(/%2F/g, '/');
|
||||
if (first_load_done) {
|
||||
if (window.history.pushState) {
|
||||
window.history.pushState(extension.require, undefined, new_search);
|
||||
}
|
||||
else {
|
||||
window.location.search = new_search;
|
||||
}
|
||||
}
|
||||
first_load_done = true;
|
||||
|
||||
// ensure extension.ui exists
|
||||
if (extension.ui === undefined) {
|
||||
// use display: none since hide(0) doesn't do anything
|
||||
// for elements that aren't yet part of the DOM
|
||||
extension.ui = build_extension_ui(extension)
|
||||
.css('display', 'none')
|
||||
.insertBefore('.nbext-readme');
|
||||
|
||||
var ext_active = extension.selector_link.find('.nbext-active-toggle').hasClass('nbext-activated');
|
||||
set_buttons_active(extension, ext_active);
|
||||
}
|
||||
|
||||
$('.nbext-selector li')
|
||||
.removeClass('active');
|
||||
extension.selector_link.closest('li').addClass('active');
|
||||
|
||||
$('.nbext-ext-row')
|
||||
.not(extension.ui)
|
||||
.slideUp(default_opts);
|
||||
extension.ui.slideDown(opts);
|
||||
load_readme(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the nav links
|
||||
* open the user interface the extension corresponding to the clicked
|
||||
* link, and scroll it into view
|
||||
*/
|
||||
function selector_nav_link_callback (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
var a = $(evt.currentTarget);
|
||||
var extension = a.data('extension');
|
||||
if (a.closest('li').hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
open_ext_ui(extension, {
|
||||
complete: function () {
|
||||
// scroll to ensure at least title is visible
|
||||
var site = $('#site');
|
||||
var title = extension.ui.children('h3:first');
|
||||
var adjust = (title.offset().top - site.offset().top) + (2 * title.outerHeight(true) - site.innerHeight());
|
||||
if (adjust > 0) {
|
||||
site.animate({scrollTop: site.scrollTop() + adjust});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the nav links' activation checkboxes
|
||||
*/
|
||||
function selector_checkbox_callback (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
var a = $(evt.currentTarget).closest('a');
|
||||
if (!a.closest('li').hasClass('disabled')) {
|
||||
var extension = a.data('extension');
|
||||
var state = !$(evt.currentTarget).hasClass('nbext-activated');
|
||||
set_buttons_active(extension, state);
|
||||
set_config_active(extension, state);
|
||||
open_ext_ui(extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* build and return UI elements for a set of parameters
|
||||
*/
|
||||
function build_params_ui (params) {
|
||||
// Assemble and add params
|
||||
var div_param_list = $('<div/>')
|
||||
.addClass('list-group');
|
||||
|
||||
for (var pp in params) {
|
||||
var param = params[pp];
|
||||
var param_name = param.name;
|
||||
if (!param_name) {
|
||||
console.error('nbext param: unnamed parameter declared!');
|
||||
continue;
|
||||
}
|
||||
|
||||
var param_div = $('<div/>')
|
||||
.addClass('form-group list-group-item')
|
||||
.appendTo(div_param_list);
|
||||
|
||||
var param_id = param_id_prefix + param_name;
|
||||
|
||||
// use param name / description as label
|
||||
$('<label/>')
|
||||
.attr('for', param_id)
|
||||
.html(
|
||||
param.hasOwnProperty('description') ? param.description : param_name
|
||||
)
|
||||
.appendTo(param_div);
|
||||
|
||||
// input to configure the param
|
||||
var input = build_param_input(param);
|
||||
input.on('change', handle_input);
|
||||
input.attr('id', param_id);
|
||||
var prepend_input_types = ['checkbox'];
|
||||
if (prepend_input_types.indexOf(param.input_type) < 0) {
|
||||
param_div.append(input);
|
||||
}
|
||||
else {
|
||||
param_div.prepend(' ');
|
||||
param_div.prepend(input);
|
||||
}
|
||||
|
||||
// set input value from config or default, if poss
|
||||
if (conf_dot_key_exists(configs[param.section], param_name)) {
|
||||
var configval = conf_dot_get(configs[param.section], param_name);
|
||||
console.log(
|
||||
'nbext param:', param_name,
|
||||
'from config:', configval
|
||||
);
|
||||
set_input_value(input, configval);
|
||||
}
|
||||
else if (param.hasOwnProperty('default')) {
|
||||
set_input_value(input, param.default);
|
||||
console.log(
|
||||
'nbext param:', param_name,
|
||||
'default:', param.default
|
||||
);
|
||||
}
|
||||
else {
|
||||
console.log('nbext param:', param_name);
|
||||
}
|
||||
}
|
||||
return div_param_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* build and return UI elements for a single extension
|
||||
*/
|
||||
function build_extension_ui (extension) {
|
||||
var ext_row = $('<div/>')
|
||||
.data('extension', extension)
|
||||
.addClass('row nbext-ext-row');
|
||||
|
||||
try {
|
||||
/**
|
||||
* Name.
|
||||
* Take advantage of column wrapping by using the col-xs-12 class
|
||||
* to ensure the name takes up a whole row-width on its own,
|
||||
* so that the subsequent columns wrap onto a new line.
|
||||
*/
|
||||
var ext_name_head = $('<h3>')
|
||||
.addClass('col-xs-12')
|
||||
.html(extension.Name)
|
||||
.appendTo(ext_row);
|
||||
|
||||
/**
|
||||
* Columns
|
||||
*/
|
||||
var col_left = $('<div/>')
|
||||
.addClass('col-xs-12')
|
||||
.appendTo(ext_row);
|
||||
|
||||
// Icon
|
||||
if (extension.icon) {
|
||||
col_left
|
||||
.addClass('col-sm-8 col-sm-pull-4 col-md-6 col-md-pull-6');
|
||||
// right precedes left in markup, so that it appears first when
|
||||
// the columns are wrapped each onto a single line.
|
||||
// The push and pull CSS classes are then used to get them to
|
||||
// be left/right correctly when next to each other
|
||||
var col_right = $('<div>')
|
||||
.addClass('col-xs-12 col-sm-4 col-sm-push-8 col-md-6 col-md-push-6')
|
||||
.insertBefore(col_left);
|
||||
$('<div/>')
|
||||
.addClass('nbext-icon')
|
||||
.append(
|
||||
$('<img>')
|
||||
.attr({
|
||||
// extension.icon is in nbextensions namespace
|
||||
'src': utils.url_path_join(base_url, 'nbextensions', extension.icon),
|
||||
'alt': extension.Name + ' icon'
|
||||
})
|
||||
)
|
||||
.appendTo(col_right);
|
||||
}
|
||||
|
||||
// Description
|
||||
var div_desc = $('<div/>')
|
||||
.addClass('nbext-desc')
|
||||
.appendTo(col_left);
|
||||
if (extension.hasOwnProperty('Description')) {
|
||||
$('<p/>')
|
||||
.html(extension.Description)
|
||||
.appendTo(div_desc);
|
||||
}
|
||||
|
||||
// Compatibility
|
||||
var compat_txt = extension.Compatibility || '?.x';
|
||||
var compat_idx = compat_txt.toLowerCase().indexOf(
|
||||
Jupyter.version.substring(0, 2) + 'x');
|
||||
if (!extension.is_compatible) {
|
||||
ext_row.addClass('nbext-incompatible');
|
||||
compat_txt = $('<span/>')
|
||||
.addClass('bg-danger text-danger')
|
||||
.text(compat_txt);
|
||||
}
|
||||
else {
|
||||
compat_txt = $('<span/>')
|
||||
.append(
|
||||
compat_txt.substring(0, compat_idx)
|
||||
)
|
||||
.append(
|
||||
$('<span/>')
|
||||
.addClass('bg-success text-success')
|
||||
.text(compat_txt.substring(compat_idx, compat_idx + 3))
|
||||
)
|
||||
.append(compat_txt.substring(compat_idx + 3, compat_txt.length));
|
||||
}
|
||||
$('<div/>')
|
||||
.addClass('nbext-compat-div')
|
||||
.text('compatibility: ')
|
||||
.append(compat_txt)
|
||||
.appendTo(col_left);
|
||||
|
||||
// Activate/Deactivate buttons
|
||||
build_activate_buttons().appendTo(col_left);
|
||||
|
||||
// Parameters
|
||||
if (extension.Parameters.length > 0) {
|
||||
for (var ii = 0; ii < extension.Parameters.length; ii++) {
|
||||
extension.Parameters[ii].section = extension.Section;
|
||||
}
|
||||
$('<div/>')
|
||||
.addClass('panel panel-default nbext-params col-xs-12')
|
||||
.append(
|
||||
$('<div/>')
|
||||
.addClass('panel-heading')
|
||||
.text('Parameters')
|
||||
)
|
||||
.append(
|
||||
build_params_ui(extension.Parameters)
|
||||
)
|
||||
.appendTo(ext_row);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('nbext error loading extension', extension.Name);
|
||||
console.error(err);
|
||||
$('<div/>')
|
||||
.addClass('alert alert-warning')
|
||||
.css('margin-top', '5px')
|
||||
.append(
|
||||
$('<p/>')
|
||||
.text('error loading extension ' + extension.Name)
|
||||
)
|
||||
.appendTo(ext_row);
|
||||
}
|
||||
finally {
|
||||
return ext_row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* build html body listing all extensions.
|
||||
*/
|
||||
function build_page () {
|
||||
add_css('./main.css');
|
||||
|
||||
var nbext_config_page = new page.Page();
|
||||
|
||||
// prepare for rendermd usage
|
||||
rendermd.add_markdown_css();
|
||||
|
||||
build_param_input({
|
||||
input_type: 'checkbox',
|
||||
section: 'common'
|
||||
})
|
||||
.attr('id', param_id_prefix + 'nbext_hide_incompat')
|
||||
.on('change', function (evt) {
|
||||
set_hide_incompat(handle_input(evt));
|
||||
})
|
||||
.prependTo('.nbext-showhide-incompat');
|
||||
|
||||
nbext_config_page.show_header();
|
||||
events.trigger('resize-header.Page');
|
||||
|
||||
var config_promises = [];
|
||||
for (var section in configs) {
|
||||
config_promises.push(configs[section].loaded);
|
||||
configs[section].load();
|
||||
}
|
||||
Promise.all(config_promises).then(function () {
|
||||
build_extension_list();
|
||||
nbext_config_page.show();
|
||||
});
|
||||
|
||||
return nbext_config_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the window.popstate event, used to handle switching to the
|
||||
* correct selected extension
|
||||
*/
|
||||
function popstateCallback (evt) {
|
||||
var require_url;
|
||||
if (evt === undefined) {
|
||||
// attempt to select an extension specified by a URL search parameter
|
||||
var queries = window.location.search.replace(/^\?/, '').split('&');
|
||||
for (var ii = 0; ii < queries.length; ii++) {
|
||||
var keyValuePair = queries[ii].split('=');
|
||||
if (decodeURIComponent(keyValuePair[0]) === 'nbextension') {
|
||||
require_url = decodeURIComponent(keyValuePair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (evt.state === null) {
|
||||
return; // as a result of setting window.location.hash
|
||||
}
|
||||
else {
|
||||
require_url = evt.state;
|
||||
}
|
||||
var selected_link;
|
||||
if (extensions_dict[require_url] === undefined || extensions_dict[require_url].selector_link.hasClass('disabled')) {
|
||||
selected_link = $('.nbext-selector').find('li:not(.disabled)').last().children('a');
|
||||
}
|
||||
else {
|
||||
selected_link = extensions_dict[require_url].selector_link;
|
||||
}
|
||||
selected_link.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* build html body listing all extensions.
|
||||
*
|
||||
* Since this function uses the contents of config.data,
|
||||
* it should only be called after config.load() has been executed
|
||||
*/
|
||||
function build_extension_list () {
|
||||
// get list of extensions from body data supplied by the python backend
|
||||
var extension_list = $('body').data('extension-list') || [];
|
||||
// add enabled-but-unconfigurable extensions to the list
|
||||
// construct a set of enabled extension urls from the configs
|
||||
// this is used later to add unconfigurable extensions to the list
|
||||
var unconfigurable_enabled_extensions = {};
|
||||
var section;
|
||||
for (section in configs) {
|
||||
unconfigurable_enabled_extensions[section] = $.extend({}, configs[section].data.load_extensions);
|
||||
}
|
||||
var i, extension;
|
||||
for (i = 0; i < extension_list.length; i++) {
|
||||
extension = extension_list[i];
|
||||
extension.Section = (extension.Section || 'notebook').toString();
|
||||
extension.Name = (extension.Name || (extension.Section + ':' + extension.require)).toString();
|
||||
// extension *is* configurable
|
||||
delete unconfigurable_enabled_extensions[extension.Section][extension.require];
|
||||
}
|
||||
// add any remaining unconfigurable extensions as stubs
|
||||
for (section in configs) {
|
||||
for (var require_url in unconfigurable_enabled_extensions[section]) {
|
||||
extension_list.push({
|
||||
Name: section + ' : ' + require_url,
|
||||
Description: 'This extension is enabled in the ' + section + ' json config, ' +
|
||||
"but doesn't provide a yaml file to tell us how to configure it. " +
|
||||
"You can disable it from here, but if you do, it won't show up in " +
|
||||
'this list again after you reload the page.',
|
||||
Section: section,
|
||||
require: require_url,
|
||||
unconfigurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var container = $('#site > .container');
|
||||
|
||||
var selector = $('.nbext-selector');
|
||||
var cols = selector.find('ul');
|
||||
|
||||
// sort extensions alphabetically
|
||||
extension_list.sort(function (a, b) {
|
||||
var an = (a.Name || '').toLowerCase();
|
||||
var bn = (b.Name || '').toLowerCase();
|
||||
if (an < bn) return -1;
|
||||
if (an > bn) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// fill the columns with nav links
|
||||
var col_length = Math.ceil(extension_list.length / cols.length);
|
||||
for (i = 0; i < extension_list.length; i++) {
|
||||
extension = extension_list[i];
|
||||
extensions_dict[extension.require] = extension;
|
||||
console.log('Notebook extension "' + extension.Name + '" found');
|
||||
|
||||
extension.is_compatible = (extension.Compatibility || '?.x').toLowerCase().indexOf(
|
||||
Jupyter.version.substring(0, 2) + 'x') >= 0;
|
||||
extension.Parameters = extension.Parameters || [];
|
||||
if (!extension.is_compatible) {
|
||||
// reveal the checkbox since we've found an incompatible nbext
|
||||
$('.nbext-showhide-incompat').show();
|
||||
}
|
||||
extension.selector_link = $('<a/>')
|
||||
.data('extension', extension)
|
||||
.html(extension.Name)
|
||||
.toggleClass('text-warning bg-warning', extension.unconfigurable === true)
|
||||
.prepend(
|
||||
$('<i>')
|
||||
.addClass('fa fa-fw nbext-active-toggle')
|
||||
);
|
||||
$('<li/>')
|
||||
.toggleClass('nbext-incompatible', !extension.is_compatible)
|
||||
.append(extension.selector_link)
|
||||
.appendTo(cols[Math.floor(i / col_length)]);
|
||||
|
||||
var ext_active = false;
|
||||
var conf = configs[extension.Section];
|
||||
if (conf === undefined) {
|
||||
console.error("nbextension '" + extension.Name + "' specifies unknown Section of '" + extension.Section + "'. Can't determine active status.");
|
||||
}
|
||||
else if (conf.data.hasOwnProperty('load_extensions')) {
|
||||
ext_active = (conf.data.load_extensions[extension.require] === true);
|
||||
}
|
||||
set_buttons_active(extension, ext_active);
|
||||
}
|
||||
// attach click handlers
|
||||
$('.nbext-active-toggle')
|
||||
.on('click', selector_checkbox_callback)
|
||||
.closest('a')
|
||||
.on('click', selector_nav_link_callback);
|
||||
|
||||
// en/disable incompatible extensions
|
||||
var hide_incompat = true;
|
||||
if (configs['common'].data.hasOwnProperty('nbext_hide_incompat')) {
|
||||
hide_incompat = configs['common'].data.nbext_hide_incompat;
|
||||
console.log(
|
||||
'nbext_hide_incompat loaded from config as: ',
|
||||
hide_incompat
|
||||
);
|
||||
}
|
||||
set_hide_incompat(hide_incompat);
|
||||
|
||||
window.addEventListener('popstate', popstateCallback);
|
||||
setTimeout(popstateCallback, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSS file to page
|
||||
*
|
||||
* @param name filename
|
||||
*/
|
||||
function add_css (name) {
|
||||
var link = document.createElement('link');
|
||||
link.type = 'text/css';
|
||||
link.rel = 'stylesheet';
|
||||
link.href = require.toUrl(name);
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
|
||||
return {
|
||||
build_page: build_page
|
||||
};
|
||||
});
|
||||
@@ -1,208 +0,0 @@
|
||||
define([
|
||||
'underscore',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'notebook/js/quickhelp',
|
||||
'codemirror/lib/codemirror'
|
||||
], function(
|
||||
_,
|
||||
$,
|
||||
utils,
|
||||
quickhelp,
|
||||
CodeMirror
|
||||
){
|
||||
"use strict";
|
||||
// This is essentially a duplicate of the quickhelp module, used to
|
||||
// patch the existing quickhelp with definitions which aren't exported.
|
||||
|
||||
var platform = utils.platform;
|
||||
|
||||
var cmd_ctrl = 'Ctrl-';
|
||||
var platform_specific;
|
||||
|
||||
if (platform === 'MacOS') {
|
||||
// Mac OS X specific
|
||||
cmd_ctrl = 'Cmd-';
|
||||
platform_specific = [
|
||||
{ shortcut: "Cmd-Up", help:"go to cell start" },
|
||||
{ shortcut: "Cmd-Down", help:"go to cell end" },
|
||||
{ shortcut: "Alt-Left", help:"go one word left" },
|
||||
{ shortcut: "Alt-Right", help:"go one word right" },
|
||||
{ shortcut: "Alt-Backspace", help:"delete word before" },
|
||||
{ shortcut: "Alt-Delete", help:"delete word after" },
|
||||
];
|
||||
} else {
|
||||
// PC specific
|
||||
platform_specific = [
|
||||
{ shortcut: "Ctrl-Home", help:"go to cell start" },
|
||||
{ shortcut: "Ctrl-Up", help:"go to cell start" },
|
||||
{ shortcut: "Ctrl-End", help:"go to cell end" },
|
||||
{ shortcut: "Ctrl-Down", help:"go to cell end" },
|
||||
{ shortcut: "Ctrl-Left", help:"go one word left" },
|
||||
{ shortcut: "Ctrl-Right", help:"go one word right" },
|
||||
{ shortcut: "Ctrl-Backspace", help:"delete word before" },
|
||||
{ shortcut: "Ctrl-Delete", help:"delete word after" },
|
||||
];
|
||||
}
|
||||
|
||||
var cm_shortcuts = [
|
||||
{ shortcut:"Tab", help:"code completion or indent" },
|
||||
{ shortcut:"Shift-Tab", help:"tooltip" },
|
||||
{ shortcut: cmd_ctrl + "]", help:"indent" },
|
||||
{ shortcut: cmd_ctrl + "[", help:"dedent" },
|
||||
{ shortcut: cmd_ctrl + "a", help:"select all" },
|
||||
{ shortcut: cmd_ctrl + "z", help:"undo" },
|
||||
{ shortcut: cmd_ctrl + "Shift-z", help:"redo" },
|
||||
{ shortcut: cmd_ctrl + "y", help:"redo" },
|
||||
].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 http://apple.stackexchange.com/questions/55727/
|
||||
// for htmlentities and/or unicode value
|
||||
'cmd': '⌘',
|
||||
'shift': '⇧',
|
||||
'alt': '⌥',
|
||||
'up': '↑',
|
||||
'down': '↓',
|
||||
'left': '←',
|
||||
'right': '→',
|
||||
'eject': '⏏',
|
||||
'tab': '⇥',
|
||||
'backtab': '⇤',
|
||||
'capslock': '⇪',
|
||||
'esc': 'esc',
|
||||
'altesc': '⎋',
|
||||
'ctrl': '⌃',
|
||||
'enter': '↩',
|
||||
'pageup': '⇞',
|
||||
'pagedown': '⇟',
|
||||
'home': '↖',
|
||||
'end': '↘',
|
||||
'altenter': '⌤',
|
||||
'space': '␣',
|
||||
'delete': '⌦',
|
||||
'backspace': '⌫',
|
||||
'apple': '',
|
||||
};
|
||||
|
||||
var default_humanize_map = {
|
||||
'shift': 'Shift',
|
||||
'alt': 'Alt',
|
||||
'up': 'Up',
|
||||
'down': 'Down',
|
||||
'left': 'Left',
|
||||
'right': 'Right',
|
||||
'tab': 'Tab',
|
||||
'capslock': 'Caps Lock',
|
||||
'esc': 'Esc',
|
||||
'ctrl': 'Ctrl',
|
||||
'enter': 'Enter',
|
||||
'pageup': 'Page Up',
|
||||
'pagedown': 'Page Down',
|
||||
'home': 'Home',
|
||||
'end': 'End',
|
||||
'space': 'Space',
|
||||
'backspace': 'Backspace',
|
||||
};
|
||||
|
||||
var humanize_map;
|
||||
|
||||
if (platform === 'MacOS'){
|
||||
humanize_map = mac_humanize_map;
|
||||
} else {
|
||||
humanize_map = default_humanize_map;
|
||||
}
|
||||
|
||||
var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
|
||||
|
||||
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 sepparated 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_shortcut(shortcut){
|
||||
var joinchar = '-';
|
||||
if (platform === 'MacOS'){
|
||||
joinchar = '';
|
||||
}
|
||||
var sh = _.map(shortcut.split('-'), humanize_key ).join(joinchar);
|
||||
return '<kbd>'+sh+'</kbd>';
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
var quickhelp_shiv = {
|
||||
cmd_ctrl : cmd_ctrl,
|
||||
platform_specific : platform_specific,
|
||||
cm_shortcuts : cm_shortcuts,
|
||||
mac_humanize_map : mac_humanize_map,
|
||||
default_humanize_map : default_humanize_map,
|
||||
humanize_map : humanize_map,
|
||||
special_case : special_case,
|
||||
humanize_key : humanize_key,
|
||||
humanize_sequence : humanize_sequence,
|
||||
humanize_shortcut : humanize_shortcut,
|
||||
build_one : build_one,
|
||||
build_div : build_div
|
||||
};
|
||||
_.defaults(quickhelp, quickhelp_shiv);
|
||||
});
|
||||
@@ -1,272 +0,0 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This extension provides a web page
|
||||
(which you can find by going to the '/nbextensions' URL)
|
||||
which allows you to activate or deactivate installed notebook extensions,
|
||||
if they provide a `YAML` file description.
|
||||
|
||||
Activating an extension means it is loaded automatically when working with a
|
||||
notebook document.
|
||||
|
||||
If you encounter problems with this config page, please create an issue at the
|
||||
[ipython-contrib](https://github.com/ipython-contrib/IPython-notebook-extensions)
|
||||
repository.
|
||||
|
||||

|
||||
|
||||
The config page is realized using a notebook server extension, new in IPython 3.x.
|
||||
In order to work, this extension (`nbextensions/config`) needs to be installed.
|
||||
|
||||
In addition, any notebook extensions it will configure will require a YAML
|
||||
description file under the `nbextensions` directory
|
||||
(see installation notes, below) in order to be found.
|
||||
|
||||
You can see a video of the config extension in action on youtube:
|
||||
|
||||
<div class="embed-responsive embed-responsive-4by3">
|
||||

|
||||
[](https://youtu.be/h9DEfxZSz2M)
|
||||
</div>
|
||||
|
||||
Setup procedure
|
||||
===============
|
||||
|
||||
The most recent version
|
||||
can be found here:
|
||||
[master.zip](https://github.com/ipython-contrib/IPython-notebook-extensions/archive/master.zip)
|
||||
|
||||
https://binstar.org/juhasch/nbextensions
|
||||
|
||||
1. Installation
|
||||
---------------
|
||||
|
||||
There are several ways to install the IPython-contrib extensions package:
|
||||
|
||||
a) Using `setup.py` 777
|
||||
After downloading the most recent version from GitHub [master.zip](https://github.com/ipython-contrib/IPython-notebook-extensions/archive/master.zip),
|
||||
unpack the archive and run `python setup.py install`. This will copy all extensions to your local Jupyter installation
|
||||
and configure the extensions to work.
|
||||
|
||||
b) Installing the Anaconda package
|
||||
When using the Anaconda Python installation (highly recommended), you can install the package using the
|
||||
`conda install -c https://conda.binstar.org/juhasch nbextensions` command. Alternatively, you can download the latest master
|
||||
version and do a `conda build IPython-notebook-extensions` yourself to build the Anaconda package.
|
||||
|
||||
c) Manual installation (see below)
|
||||
|
||||
The easiest way is to do a) and install the extensions using the `python setup.py install` command.
|
||||
|
||||
|
||||
Having restarted the server after the installation, you should be able to see the configuration page by going to the URL `/nbextensions`.
|
||||
Otherwise, you can use the detailed instructions below - good luck!
|
||||
|
||||
**Note**: The notebook server is not allowed to run during installation.
|
||||
|
||||
1a. Manual Installation
|
||||
-----------------------
|
||||
|
||||
* copy the `nbextensions` folder to `~/Library/Jupyter` (on MacOs, for help locating Jupyter data dir on other OS look in 3.)
|
||||
* copy the `extensions` folder to `~/Library/Jupyter`
|
||||
* copy the `templates` folder to `~/Library/Jupyter`
|
||||
|
||||
|
||||
2. Configuration
|
||||
----------------
|
||||
|
||||
There are two configuration steps required:
|
||||
|
||||
1. Edit your `.jupyter/jupyter_notebook_config.py` file (on MacOs, for help locating the Jupyter config dir on other OS look in 3.):
|
||||
```
|
||||
from jupyter_core.paths import jupyter_config_dir, jupyter_data_dir
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(jupyter_data_dir(), 'extensions'))
|
||||
|
||||
c = get_config()
|
||||
c.NotebookApp.extra_template_paths = [os.path.join(jupyter_data_dir(),'templates') ]
|
||||
```
|
||||
And `.jupyter/jupyter_notebook_config.py`
|
||||
```
|
||||
from jupyter_core.paths import jupyter_config_dir, jupyter_data_dir
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(jupyter_data_dir(), 'extensions'))
|
||||
|
||||
c = get_config()
|
||||
c.Exporter.template_path = [os.path.join(jupyter_data_dir(), 'templates') ]
|
||||
```
|
||||
This makes sure the Python extensions are found in the `~/Library/Jupyter/extensions` directory and the
|
||||
additional templates are found in `~/Library/Jupyter/templates`
|
||||
|
||||
2. Configure the server extension, and pre-/postprocessessors:
|
||||
Edit the `.jupyter/jupyter_notebook.json` to look like this
|
||||
```
|
||||
{
|
||||
"Exporter": {
|
||||
"preprocessors": [
|
||||
"pre_codefolding.CodeFoldingPreprocessor",
|
||||
"pre_pymarkdown.PyMarkdownPreprocessor"
|
||||
]
|
||||
},
|
||||
"NbConvertApp": {
|
||||
"postprocessor_class": "post_embedhtml.EmbedPostProcessor"
|
||||
},
|
||||
"NotebookApp": {
|
||||
"server_extensions": [
|
||||
"nbextensions"
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
Edit the `.jupyter/jupyter_nbconvert.json` to look like this:
|
||||
```
|
||||
{
|
||||
"Exporter": {
|
||||
"preprocessors": [
|
||||
"pre_codefolding.CodeFoldingPreprocessor",
|
||||
"pre_pymarkdown.PyMarkdownPreprocessor"
|
||||
]
|
||||
},
|
||||
"NbConvertApp": {
|
||||
"postprocessor_class": "post_embedhtml.EmbedPostProcessor"
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
This will load the `nbextensions` server extension in the notebook, and add several pre/- and postprocessors that
|
||||
are required by notebook extensions to export notebooks using `jupter nbconvert`
|
||||
(namely Codefolding, Python Mmarkdown, Runtools).
|
||||
|
||||
|
||||
|
||||
3. Help with locating files
|
||||
---------------------------
|
||||
|
||||
If you're having problems with where the different files are supposed to go,
|
||||
here's an attempt at an explanation.
|
||||
Jupyter/IPython 4.x works differently than IPython 3.x:
|
||||
|
||||
* The notebook was split from IPython. You need to install both to run the
|
||||
notebook with IPython now.
|
||||
The easiest way is to use Anaconda and do a `conda install jupyter`
|
||||
* There are no profiles anymore.
|
||||
You can specify environment variables to change the default, see
|
||||
[Jupyter ML](https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!topic/jupyter/7q02jjksvFU).
|
||||
* The configuration has moved to a new place. To find out where, see below.
|
||||
* There is a kind of automatic upgrade of the configuration files from IPython 3.x to Jupyter.
|
||||
|
||||
|
||||
### So where are all the config files now?
|
||||
To find where the configuration files are, start IPython and run the following:
|
||||
|
||||
```Python
|
||||
from __future__ import print_function
|
||||
from jupyter_core.paths import jupyter_config_dir, jupyter_config_path
|
||||
print(jupyter_config_dir())
|
||||
print(jupyter_config_path())
|
||||
```
|
||||
|
||||
`jupyter_config_dir()` shows you where your *local* configuration files are,
|
||||
`jupyter_config_path()` shows you where Jupyter will look for *global* configuration files.
|
||||
For the notebook, there are two files that will be used:
|
||||
`jupyter_notebook_config.py` and `jupyter_notebook_config.json`.
|
||||
|
||||
The `nbextensions` directory has moved to a different location and can be found
|
||||
in one of these directories:
|
||||
|
||||
```Python
|
||||
from __future__ import print_function
|
||||
from jupyter_core.paths import jupyter_data_dir, jupyter_path
|
||||
print(jupyter_data_dir())
|
||||
print(jupyter_path())
|
||||
```
|
||||
|
||||
|
||||
Internals
|
||||
=========
|
||||
|
||||
The configuration for which nbextensions are enabled is stored in `jupyter_config_dir()/nbconfig/notebook.json`.
|
||||
|
||||
You can generate a table of currently activated extensions by executing the following in a notebook cell:
|
||||
|
||||
```Python
|
||||
from IPython.html.services.config import ConfigManager
|
||||
from IPython.display import HTML
|
||||
ip = get_ipython()
|
||||
cm = ConfigManager(parent=ip, profile_dir=ip.profile_dir.location)
|
||||
extensions =cm.get('notebook')
|
||||
table = ""
|
||||
for ext in extensions['load_extensions']:
|
||||
table += "<tr><td>%s</td>\n" % (ext)
|
||||
|
||||
top = """
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Extension name</th>
|
||||
</tr>
|
||||
"""
|
||||
bottom = """
|
||||
</table>
|
||||
"""
|
||||
HTML(top + table + bottom)
|
||||
```
|
||||
|
||||
If you reload the notebook after enabling a notebook extension, the extension
|
||||
should be loaded. You can also check the Javascript console to confirm.
|
||||
|
||||
YAML file format
|
||||
----------------
|
||||
A notebook extensions is found, when a special YAML file describing the extensions is found.
|
||||
The YAML file can have any name with the extension `YAML`, and describes the notebook extension. Note that keys (in bold) are case-sensitive.
|
||||
|
||||
* **Type** - identifier, must be 'IPython Notebook Extension'
|
||||
* **Name** - unique name of the extension
|
||||
* **Description** - short explanation of the extension
|
||||
* **Link** - a url for more documentation
|
||||
* **Icon** - a url for a small icon (rendered 120px high, should preferably end up 400px wide. Recall HDPI displays may benefit from a 2x resolution icon).
|
||||
* **Main** - main javascript file that is loaded, typically 'main.js'
|
||||
* **Compatibility** - IPython version compatibility, e.g. '3.x' or '4.x' or '3.x 4.x'
|
||||
* **Parameters** - Optional list of configuration parameters. Each item is a dictionary with (some of) the following keys:
|
||||
* **name** - (mandatory) this is the name used to store the configuration variable in the config json, so should be unique among all extensions
|
||||
* **description** - description of the configuration parameter
|
||||
* **default** - a default value used to populate the tag on the nbextensions config page. Note that this is more of a hint to the user than anything functional - since it's only set in the yaml file, the javascript implementing the extension in question might actually use a different default, depending on the implementation.
|
||||
* **input_type** - controls the type of html tag used to render the parameter on the configuration page. Valid values include 'text', 'textarea', 'checkbox', [html5 input tags such as 'number', 'url', 'color', ...], plus a final type of 'list'
|
||||
* **list_element** - for parameters with input_type 'list', this is used in place of 'input_type' to render each element of the list
|
||||
* finally, extras such as **min** **step** **max** may be used by 'number' tags for validation
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
Type: IPython Notebook Extension
|
||||
Name: Limit Output
|
||||
Description: This extension limits the number of characters that can be printed below a codecell
|
||||
Link: https://github.com/ipython-contrib/IPython-notebook-extensions/wiki/limit-output
|
||||
Icon: icon.png
|
||||
Main: main.js
|
||||
Compatibility: 3.x 4.x
|
||||
Parameters:
|
||||
- name: limit_output
|
||||
description: Number of characters to limit output to
|
||||
input_type: number
|
||||
default: 10000
|
||||
step: 1
|
||||
min: 0
|
||||
```
|
||||
|
||||
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
If an extension doesn't work, here are some ways you can check what is wrong:
|
||||
|
||||
1. Clear your browser cache or start a private browser tab.
|
||||
2. Verify the extension can be loaded by the IPython notebook, for example,
|
||||
load the javascript file directly:
|
||||
`http://127.0.0.1:8888/nbextensions/usability/runtools/main.js`
|
||||
3. Check for error messages in the JavaScript console of the browser.
|
||||
4. Check for any error messages in the server output logs
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
// Copyright (c) IPython-Contrib Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Render markdown url
|
||||
|
||||
define([
|
||||
'require',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
'base/js/page',
|
||||
'base/js/security',
|
||||
'notebook/js/mathjaxutils',
|
||||
'notebook/js/codemirror-ipythongfm',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/gfm/gfm',
|
||||
'codemirror/addon/runmode/runmode',
|
||||
'components/marked/lib/marked'
|
||||
], function(
|
||||
require,
|
||||
$,
|
||||
utils,
|
||||
page,
|
||||
security,
|
||||
mathjaxutils,
|
||||
ipgfm,
|
||||
CodeMirror,
|
||||
gfm,
|
||||
runMode,
|
||||
marked
|
||||
){
|
||||
/**
|
||||
* Custom marked options,
|
||||
* lifted from notebook/js/notebook
|
||||
*/
|
||||
var custom_marked_options = {
|
||||
gfm : true,
|
||||
tables: true,
|
||||
// FIXME: probably want central config for CodeMirror theme when we have js config
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var magic_match;
|
||||
if (lang.toLowerCase() === 'jupyter') {
|
||||
var magic_specs = {
|
||||
'javascript' : /^%%javascript/,
|
||||
'perl' : /^%%perl/,
|
||||
'ruby' : /^%%ruby/,
|
||||
'python' : /^%%python3?/,
|
||||
'shell' : /^%%bash/,
|
||||
'r' : /^%%R/,
|
||||
'text/x-cython' : /^%%cython/,
|
||||
'latex' : /^%%latex/
|
||||
};
|
||||
for (var mode in magic_specs) {
|
||||
magic_match = code.match(magic_specs[mode]);
|
||||
if (magic_match !== null) {
|
||||
magic_match = magic_match[0];
|
||||
lang = mode;
|
||||
code = code.substr(magic_match.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (magic_match) {
|
||||
$(el).prepend($('<span/>').text(magic_match));
|
||||
}
|
||||
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);
|
||||
callback(err, code);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* return a URL constructed by joining together each relative URL given as an argument, applying '..'
|
||||
*/
|
||||
var join_relative_urls = function () {
|
||||
var url = [], root = '';
|
||||
for (var i in arguments) {
|
||||
var url_parts = arguments[i];
|
||||
// reset url if we encounter a relative URL starting at domain root (i.e. beginning with '/')
|
||||
if (url_parts.length > 0 && url_parts[0] === '/') {
|
||||
url = [];
|
||||
root = '/';
|
||||
}
|
||||
url_parts = url_parts.split('/');
|
||||
url.pop(); // relative urls don't include the resource, so pop it
|
||||
for (var j in url_parts) {
|
||||
switch (url_parts[j]) {
|
||||
case '':
|
||||
case '.':
|
||||
continue;
|
||||
case '..':
|
||||
url.pop();
|
||||
break;
|
||||
default:
|
||||
url.push(url_parts[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return root + url.join('/');
|
||||
};
|
||||
|
||||
/**
|
||||
* Render given markdown into html, returning as a jquery element.
|
||||
* Optionally absolutify relative href/src attributes using the parameter relative_url_root
|
||||
*/
|
||||
var render_markdown = function(md_contents, relative_url_root) {
|
||||
var div = $('<div>');
|
||||
// the bulk of this functon is adapted from
|
||||
// notebook/js/textcell.Markdowncell.render
|
||||
// with the addition of code to absolutify relative href/src attributes
|
||||
if (md_contents) {
|
||||
var text_and_math = mathjaxutils.remove_math(md_contents);
|
||||
var text = text_and_math[0];
|
||||
var math = text_and_math[1];
|
||||
var options = custom_marked_options;
|
||||
if (relative_url_root) {
|
||||
// patch the renderer to fix relative paths to be absolute
|
||||
var renderer = new marked.Renderer();
|
||||
var base_renderer_link = renderer.link;
|
||||
renderer.link = function (href, title, text) {
|
||||
if (!/^#|mailto:|(f|ht)tps?:\/\//i.test(href)) {
|
||||
href = join_relative_urls(relative_url_root, href);
|
||||
}
|
||||
return base_renderer_link.call(this, href, title, text);
|
||||
};
|
||||
base_renderer_image = renderer.image;
|
||||
renderer.image = function (href, title, text) {
|
||||
if (!/^(f|ht)tps?:\/\//i.test(href)) {
|
||||
href = join_relative_urls(relative_url_root, href);
|
||||
}
|
||||
return base_renderer_image.call(this, href, title, text);
|
||||
};
|
||||
options = $.extend(custom_marked_options, {renderer: renderer});
|
||||
}
|
||||
marked(text, options, function (err, html) {
|
||||
html = mathjaxutils.replace_math(html, math);
|
||||
html = security.sanitize_html(html);
|
||||
html = $($.parseHTML(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('¶')
|
||||
);
|
||||
});
|
||||
// links in markdown cells should open in new tabs
|
||||
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
|
||||
div.html(html);
|
||||
});
|
||||
}
|
||||
return div;
|
||||
};
|
||||
|
||||
var render_markdown_page = function() {
|
||||
// add css first so hopefully it'll be loaded in time
|
||||
add_markdown_css();
|
||||
|
||||
page = new page.Page();
|
||||
page.show_header();
|
||||
|
||||
var base_url = utils.get_body_data('baseUrl');
|
||||
var md_url = $('body').data('md-url');
|
||||
var url = base_url + md_url;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'text', // or 'html', 'xml', 'more'
|
||||
success: function(md_contents) {
|
||||
$("#render-container").append(render_markdown(md_contents));
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$(".nbext-page-title-wrap").append(
|
||||
$('<span class="nbext-page-title text-danger"/>').text(
|
||||
textStatus + ' : ' + jqXHR.status + ' ' + errorThrown
|
||||
)
|
||||
);
|
||||
$("#render-container").addClass("text-danger bg-danger");
|
||||
var body_txt = "";
|
||||
switch (jqXHR.status) {
|
||||
case 404:
|
||||
body_txt = 'no markdown file at ' + url;
|
||||
break;
|
||||
}
|
||||
$("#render-container").append(body_txt);
|
||||
},
|
||||
complete: function(jqXHR, textStatus) {
|
||||
page.show();
|
||||
// See http://stackoverflow.com/questions/13735912
|
||||
var el = $(window.location.hash);
|
||||
if (el.length > 0) el[0].scrollIntoView();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add CSS file to page
|
||||
*
|
||||
* @param url where to get css from. Will be wrapped by require.toUrl
|
||||
*/
|
||||
var add_css = function (url) {
|
||||
var link = document.createElement("link");
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
link.href = require.toUrl(url);
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the specific markdown CSS file to page
|
||||
*/
|
||||
var add_markdown_css = function () {
|
||||
add_css('./rendermd.css');
|
||||
};
|
||||
|
||||
// expose functions
|
||||
return {
|
||||
add_markdown_css : add_markdown_css,
|
||||
custom_marked_options : custom_marked_options,
|
||||
join_relative_urls : join_relative_urls,
|
||||
render_markdown : render_markdown,
|
||||
render_markdown_page : render_markdown_page
|
||||
};
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
/* CSS overrides for the rendermd page */
|
||||
|
||||
/* the render container should match the notebook container*/
|
||||
#render-container {
|
||||
padding: 15px;
|
||||
-moz-box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
-webkit-box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
|
||||
min-height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
/* rendermd-page-title-wrap and rendermd-page-title
|
||||
are styled to match the notebook save widget */
|
||||
.rendermd-page-title-wrap {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.rendermd-page-title {
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
padding: 3px;
|
||||
margin-left: 16px;
|
||||
border: none;
|
||||
font-size: 146.5%;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.rendered_html pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.rendered_html code {
|
||||
background-color: #f9f2f4;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.rendered_html pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rendered_html ol li,
|
||||
.rendered_html ul li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
* + .embed-responsive {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
/* bootstrap's embed-responsive-item class, as applied to images */
|
||||
.embed-responsive > img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.embed-responsive > a {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 37.5%;
|
||||
left: 37.5%;
|
||||
right: 37.5%;
|
||||
bottom: 37.5%;
|
||||
z-index: 100;
|
||||
background: transparent url(video_play.svg) no-repeat center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.embed-responsive > a:hover {
|
||||
top: 35%;
|
||||
left: 35%;
|
||||
right: 35%;
|
||||
bottom: 35%;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata9"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs7" /><path
|
||||
style="opacity:0.5;fill:#ffffff;fill-opacity:1;stroke-width:1.77165353;stroke-miterlimit:4;stroke-dasharray:none;stroke:none"
|
||||
d="M 256,2e-6 A 256,256 0 0 0 0,256 256,256 0 0 0 256,512 256,256 0 0 0 512,256 256,256 0 0 0 256,2e-6 Z m 0,42 A 214,214 0 0 1 470,256 214,214 0 0 1 256,470 214,214 0 0 1 42,256 214,214 0 0 1 256,42.000002 Z"
|
||||
id="path4225" /><path
|
||||
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke-width:1.77165353;stroke-miterlimit:4;stroke-dasharray:none;stroke:none"
|
||||
d="M 256 42 A 214 214 0 0 0 42 256 A 214 214 0 0 0 256 470 A 214 214 0 0 0 470 256 A 214 214 0 0 0 256 42 z M 176 120 L 378 255.49414 L 176 392 L 176 120 z "
|
||||
id="path4227" /><path
|
||||
style="opacity:0.5;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 176,392 0,-272 202,135.49438 z"
|
||||
id="path4229" /></svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -92,6 +92,7 @@ setup(name='Python-contrib-nbextensions',
|
||||
license='BSD',
|
||||
install_requires=[
|
||||
'jupyter_core',
|
||||
'jupyter_nbextensions_configurator',
|
||||
'nbconvert',
|
||||
'notebook',
|
||||
'psutil >= 2.2.1',
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
{% extends "page.html" %}
|
||||
|
||||
{% block title %}{{page_title}}{% endblock %}
|
||||
|
||||
{% block stylesheet %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block params %}
|
||||
|
||||
data-base-url='{{base_url}}'
|
||||
data-extension-list='{{extension_list}}'
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block headercontainer %}
|
||||
|
||||
<div class="pull-left nbext-page-title-wrap">
|
||||
<span class="nbext-page-title">
|
||||
Configuration for Notebook Extensions
|
||||
(<a href="{{base_url}}nbextensions/config/rendermd/nbextensions/config/readme.md">more information</a>)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block site %}
|
||||
|
||||
<div id="notebook-container" class="container">
|
||||
<div class="row nbext-row container-fluid nbext-selector">
|
||||
<h4>Configurable extensions</h4>
|
||||
<div class="nbext-showhide-incompat">
|
||||
disable configuration for extensions without explicit compatibility
|
||||
(they may break your notebook environment, but can be useful to show for extension development)
|
||||
</div>
|
||||
<nav class="row">
|
||||
<ul class="nav nav-pills nav-stacked col-md-3"></ul>
|
||||
<ul class="nav nav-pills nav-stacked col-md-3"></ul>
|
||||
<ul class="nav nav-pills nav-stacked col-md-3"></ul>
|
||||
<ul class="nav nav-pills nav-stacked col-md-3"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="row nbext-readme">
|
||||
<h3></h3>
|
||||
<div class="nbext-readme-contents"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
{{super()}}
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
require(['jquery'], function (jq) {
|
||||
// hack to fix notebook 4.2.1
|
||||
// see https://github.com/jupyter/notebook/pull/1399
|
||||
if (jq === undefined) {
|
||||
require.undef('jquery');
|
||||
require.undef('jquery-ui');
|
||||
require.undef('jqueryui');
|
||||
require.undef('bootstrap');
|
||||
}
|
||||
require(['nbextensions/config/main'], function (nbext_config_module) {
|
||||
nbext_config_module.build_page();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,55 +0,0 @@
|
||||
{% extends "page.html" %}
|
||||
|
||||
{% block title %}{{page_title}}{% endblock %}
|
||||
|
||||
{% block stylesheet %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block params %}
|
||||
|
||||
data-base-url='{{base_url}}'
|
||||
data-md-url='{{md_url}}'
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block headercontainer %}
|
||||
|
||||
<div class="pull-left rendermd-page-title-wrap">
|
||||
<span class="rendermd-page-title">{{ page_title }}</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block site %}
|
||||
|
||||
{# the "rendered_html" class, will make everything look like notebook-formatted markdown cells. The custom css loaded by render.js is used to add backgrounds for <code> and <pre> blocks and snippets #}
|
||||
<div id="render-container" class="container rendered_html"></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
{{super()}}
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
require(['jquery'], function (jq) {
|
||||
// hack to fix notebook 4.2.1
|
||||
// see https://github.com/jupyter/notebook/pull/1399
|
||||
if (jq === undefined) {
|
||||
require.undef('jquery');
|
||||
require.undef('jquery-ui');
|
||||
require.undef('jqueryui');
|
||||
require.undef('bootstrap');
|
||||
}
|
||||
require(["nbextensions/config/render/render"], function(rendermd_module) {
|
||||
rendermd_module.render_markdown_page();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user