diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 30e355f81..895e57db5 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -9,35 +9,42 @@ define([ 'codemirror/mode/meta', ], function($, CodeMirror, moment){ "use strict"; - + + /** + * Load a single extension. + * @param {string} extension - extension path. + * @return {Promise} that resolves to an extension module handle + */ + var load_extension = function (extension) { + return new Promise(function(resolve, reject) { + require([extension], function(module) { + console.log("Loaded extension: " + extension); + resolve(module); + }, function(err) { + reject(err); + }); + }); + }; + + /** + * Load multiple extensions. + * Takes n-args, where each arg is a string path to the extension. + * @return {Promise} that resolves to a list of loaded module handles. + */ var load_extensions = function () { // load one or more Jupyter notebook extensions with requirejs - + var extensions = []; var extension_names = arguments; for (var i = 0; i < extension_names.length; i++) { extensions.push("nbextensions/" + arguments[i]); } - - require(extensions, - function () { - for (var i = 0; i < arguments.length; i++) { - var ext = arguments[i]; - var ext_name = extension_names[i]; - // success callback - console.log("Loaded extension: " + ext_name); - if (ext && ext.load_ipython_extension !== undefined) { - ext.load_ipython_extension(); - } - } - }, - function (err) { - // failure callback - console.log("Failed to load extension(s):", err.requireModules, err); - } - ); + + return Promise.all(extensions.map(load_extension)).catch(function(err) { + console.error("Failed to load extension" + (err.requireModules.length>1?'s':'') + ":", err.requireModules, err); + }); }; - + /** * Wait for a config section to load, and then load the extensions specified * in a 'load_extensions' key inside it. @@ -195,7 +202,7 @@ define([ //Map from terminal commands to CSS classes var ansi_colormap = { "01":"ansibold", - + "30":"ansiblack", "31":"ansired", "32":"ansigreen", @@ -204,7 +211,7 @@ define([ "35":"ansipurple", "36":"ansicyan", "37":"ansigray", - + "40":"ansibgblack", "41":"ansibgred", "42":"ansibggreen", @@ -214,7 +221,7 @@ define([ "46":"ansibgcyan", "47":"ansibggray" }; - + function _process_numbers(attrs, numbers) { // process ansi escapes var n = numbers.shift(); @@ -230,7 +237,7 @@ define([ console.log("Not enough fields for VT100 color", numbers); return; } - + var index_or_rgb = numbers.shift(); var r,g,b; if (index_or_rgb == "5") { @@ -337,7 +344,7 @@ define([ // all ANSI codes that do not end with "m". var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g; txt = txt.replace(ignored_re, ""); - + // color ansi codes txt = ansispan(txt); return txt; @@ -371,7 +378,7 @@ define([ test.remove(); return Math.floor(points*pixel_per_point); }; - + var always_new = function (constructor) { /** * wrapper around contructor to avoid requiring `var a = new constructor()` @@ -404,13 +411,13 @@ define([ url = url.replace(/\/\/+/, '/'); return url; }; - + var url_path_split = function (path) { /** * Like os.path.split for URLs. * Always returns two strings, the directory path and the base filename */ - + var idx = path.lastIndexOf('/'); if (idx === -1) { return ['', path]; @@ -418,7 +425,7 @@ define([ return [ path.slice(0, idx), path.slice(idx + 1) ]; } }; - + var parse_url = function (url) { /** * an `a` element with an href allows attr-access to the parsed segments of a URL @@ -434,7 +441,7 @@ define([ a.href = url; return a; }; - + var encode_uri_components = function (uri) { /** * encode just the components of a multi-segment uri, @@ -442,7 +449,7 @@ define([ */ return uri.split('/').map(encodeURIComponent).join('/'); }; - + var url_join_encode = function () { /** * join a sequence of url components with '/', @@ -485,7 +492,7 @@ define([ return val; return decodeURIComponent(val); }; - + var to_absolute_cursor_pos = function (cm, cursor) { /** * get the absolute cursor position from CodeMirror's col, ch @@ -499,7 +506,7 @@ define([ } return cursor_pos; }; - + var from_absolute_cursor_pos = function (cm, cursor_pos) { /** * turn absolute cursor position into CodeMirror col, ch cursor @@ -523,7 +530,7 @@ define([ ch : line.length - 1, }; }; - + // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { @@ -550,7 +557,7 @@ define([ if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; return OSName; })(); - + var get_url_param = function (name) { // get a URL parameter. I cannot believe we actually need this. // Based on http://stackoverflow.com/a/25359264/938949 @@ -559,7 +566,7 @@ define([ return decodeURIComponent(match[1] || ''); } }; - + var is_or_has = function (a, b) { /** * Is b a child of a or a itself? @@ -583,13 +590,13 @@ define([ return false; } }; - + var mergeopt = function(_class, options, overwrite){ options = options || {}; overwrite = overwrite || {}; return $.extend(true, {}, _class.options_default, options, overwrite); }; - + var ajax_error_msg = function (jqXHR) { /** * Return a JSON error message if there is one, @@ -614,7 +621,7 @@ define([ }; var requireCodeMirrorMode = function (mode, callback, errback) { - /** + /** * find a predefined mode or detect from CM metadata then * require and callback with the resolveable mode string: mime or * custom name @@ -648,10 +655,10 @@ define([ }, errback ); }; - + /** Error type for wrapped XHR errors. */ var XHR_ERROR = 'XhrError'; - + /** * Wraps an AJAX error as an Error object. */ @@ -664,7 +671,7 @@ define([ wrapped_error.xhr_error = error; return wrapped_error; }; - + var promising_ajax = function(url, settings) { /** * Like $.ajax, but returning an ES6 promise. success and error settings @@ -717,8 +724,8 @@ define([ /** * Tries to load a class * - * Tries to load a class from a module using require.js, if a module - * is specified, otherwise tries to load a class from the global + * Tries to load a class from a module using require.js, if a module + * is specified, otherwise tries to load a class from the global * registry, if the global registry is provided. */ return new Promise(function(resolve, reject) { @@ -764,15 +771,15 @@ define([ var reject = function(message, log) { /** * Creates a wrappable Promise rejection function. - * + * * Creates a function that returns a Promise.reject with a new WrappedError - * that has the provided message and wraps the original error that + * that has the provided message and wraps the original error that * caused the promise to reject. */ - return function(error) { + return function(error) { var wrapped_error = new WrappedError(message, error); - if (log) console.error(wrapped_error); - return Promise.reject(wrapped_error); + if (log) console.error(wrapped_error); + return Promise.reject(wrapped_error); }; }; @@ -802,14 +809,14 @@ define([ return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]); }); }; - + var time = {}; time.milliseconds = {}; time.milliseconds.s = 1000; time.milliseconds.m = 60 * time.milliseconds.s; time.milliseconds.h = 60 * time.milliseconds.m; time.milliseconds.d = 24 * time.milliseconds.h; - + time.thresholds = { // moment.js thresholds in milliseconds s: moment.relativeTimeThreshold('s') * time.milliseconds.s, @@ -817,14 +824,14 @@ define([ h: moment.relativeTimeThreshold('h') * time.milliseconds.h, d: moment.relativeTimeThreshold('d') * time.milliseconds.d, }; - + time.timeout_from_dt = function (dt) { /** compute a timeout based on dt - + input and output both in milliseconds - + use moment's relative time thresholds: - + - 10 seconds if in 'seconds ago' territory - 1 minute if in 'minutes ago' - 1 hour otherwise @@ -837,7 +844,7 @@ define([ return time.milliseconds.h; } }; - + var utils = { load_extensions: load_extensions, load_extensions_from_config: load_extensions_from_config, @@ -879,4 +886,4 @@ define([ }; return utils; -}); +});