diff --git a/README.md b/README.md index 28c7f5f..9ad332a 100644 --- a/README.md +++ b/README.md @@ -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. ![Extensions](nbextensions/config/icon.png) diff --git a/configure_nbextensions.py b/configure_nbextensions.py index c6c8584..d386764 100644 --- a/configure_nbextensions.py +++ b/configure_nbextensions.py @@ -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']) diff --git a/extensions/nbextensions.py b/extensions/nbextensions.py deleted file mode 100644 index 6564635..0000000 --- a/extensions/nbextensions.py +++ /dev/null @@ -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), - ]) diff --git a/nbextensions/config/config_menu/config_menu.yaml b/nbextensions/config/config_menu/config_menu.yaml deleted file mode 100644 index 573abf6..0000000 --- a/nbextensions/config/config_menu/config_menu.yaml +++ /dev/null @@ -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 diff --git a/nbextensions/config/config_menu/icon.png b/nbextensions/config/config_menu/icon.png deleted file mode 100644 index 5b4a3f8..0000000 Binary files a/nbextensions/config/config_menu/icon.png and /dev/null differ diff --git a/nbextensions/config/config_menu/main.js b/nbextensions/config/config_menu/main.js deleted file mode 100644 index 6f44f3c..0000000 --- a/nbextensions/config/config_menu/main.js +++ /dev/null @@ -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 = $('
  • ').append( - $('', { - 'target' : '_blank', - 'title' : 'Opens in a new window', - 'href' : Jupyter.notebook.base_url + 'nbextensions/', - }) - .append(' ') - .append($('', {'class' : 'fa fa-cogs menu-icon pull-right'})) - .append($('').html('nbextensions config')) - ); - - var edit_menu = $('#edit_menu'); - edit_menu.append($('
  • ').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; -}); diff --git a/nbextensions/config/icon.png b/nbextensions/config/icon.png deleted file mode 100644 index 87b7d17..0000000 Binary files a/nbextensions/config/icon.png and /dev/null differ diff --git a/nbextensions/config/kse_components.js b/nbextensions/config/kse_components.js deleted file mode 100644 index 7ecaed7..0000000 --- a/nbextensions/config/kse_components.js +++ /dev/null @@ -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 = $('
    ') - .addClass('kse-editor') - .attr('id', 'kse-editor') - .data({ - 'kse_sequence': [], - 'kse_info': {}, - 'kse_mode': 'command', - 'kse_undefined_key': false - }); - - var form = $('
    ') - .addClass('form') - .appendTo(editor); - - $('
    ') - .addClass('form-group') - .appendTo(form); - - var form_group = $('
    ') - .addClass('form-group has-feedback') - .appendTo(form); - - var input_group = $('
    ') - .addClass('input-group') - .addClass('kse-input-group') - .appendTo(form_group); - - // reset button - var btn = $('') - .addClass('btn btn-default') - .addClass('kse-input-group-reset') - .attr({ - 'title': 'Restart', - 'type': 'button' - }) - .append( - $('') - .addClass('fa fa-repeat') - ) - .on('click', function () { - editor.data({ - 'kse_sequence': [], - 'kse_undefined_key': false - }); - editor_update_input_group(editor); - $(this).blur(); - textcontrol.focus(); - }); - $('
    ') - .addClass('input-group-btn') - .append(btn) - .appendTo(input_group); - - // pretty-displayed shortcut - $('
    ') - .addClass('input-group-addon') - .addClass('kse-input-group-pretty') - .addClass('kse-editor-to') - .appendTo(input_group); - - var textcontrol = $('') - .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 = $('') - .addClass('fa fa-lg'); - $('') - .addClass('form-control-feedback') - .append(form_fdbck) - .appendTo(form_group); - - // help for input group - $('') - .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(','); - - $('

    ') - .html( - 'Unfortunately, Jupyter\'s handling of shortcuts containing ' + - 'commas (,) 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; - $('

    ') - .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)) { - $('

    ') - .append(quickhelp.humanize_sequence(conflicting_shortcut)) - .append($('').text(conflicts[conflicting_shortcut])) - .appendTo(help_block); - } - } - } - } - - if (editor.data('kse_undefined_key')) { - var warning = $('') - .addClass('form-group has-feedback has-warning kse-undefined') - .append( - $('') - .addClass('help-block') - .append( - $('

    ').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 - }; -}); diff --git a/nbextensions/config/main.css b/nbextensions/config/main.css deleted file mode 100644 index d56bcda..0000000 --- a/nbextensions/config/main.css +++ /dev/null @@ -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; -} diff --git a/nbextensions/config/main.js b/nbextensions/config/main.js deleted file mode 100644 index bf3f5e7..0000000 --- a/nbextensions/config/main.js +++ /dev/null @@ -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

  • , and move/remove buttons - */ - function wrap_list_input (list_input) { - var btn_remove = $('', {'class': 'btn btn-default input-group-addon nbext-list-el-btn-remove'}); - btn_remove.append($('', {'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 $('
  • ', {'class' : 'nbext-list-element input-group'}).append( - $('').append( - $('') - ), - [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 = $('
    '); - input.append( - $('') - .css(utils.platform === 'MasOS' ? {'letter-spacing': '1px'} : {}) - ); - input.append($('
    ').append( - $('
    ').append( - $('', { - 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 = $('
    ', {'class' : 'nbext-list-wrap'}); - input.append( - $('