diff --git a/.eslintrc.js b/.eslintrc.js index 322567959..78b7288cb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,15 +1,11 @@ module.exports = { - "env": { - "browser": true, - "jquery": true, - "jest/globals": true, - }, - "extends": [ - "eslint:recommended", - ], - "parser": "@babel/eslint-parser", - "plugins": [ - "jest", - ], - "rules": {}, + env: { + browser: true, + jquery: true, + "jest/globals": true, + }, + extends: ["eslint:recommended"], + parser: "@babel/eslint-parser", + plugins: ["jest"], + rules: {}, }; diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a0209189..c94bfcd98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,7 +58,6 @@ repos: # separate run of pre-commit where we configure a line spacing of 4 # for these file formats. - html - - javascript # Misc autoformatting and linting - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/binderhub/static/js/index.js b/binderhub/static/js/index.js index b934ba516..120294f30 100644 --- a/binderhub/static/js/index.js +++ b/binderhub/static/js/index.js @@ -1,50 +1,58 @@ /* If this file gets over 200 lines of code long (not counting docs / comments), start using a framework -*/ -import ClipboardJS from 'clipboard'; -import 'event-source-polyfill'; + */ +import ClipboardJS from "clipboard"; +import "event-source-polyfill"; -import { BinderRepository } from '@jupyterhub/binderhub-client'; -import { updatePathText } from './src/path'; -import { nextHelpText } from './src/loading'; -import { updateFavicon } from './src/favicon'; +import { BinderRepository } from "@jupyterhub/binderhub-client"; +import { updatePathText } from "./src/path"; +import { nextHelpText } from "./src/loading"; +import { updateFavicon } from "./src/favicon"; -import 'xterm/css/xterm.css'; +import "xterm/css/xterm.css"; // Include just the bootstrap components we use -import 'bootstrap/js/dropdown'; -import 'bootstrap/dist/css/bootstrap.min.css'; -import 'bootstrap/dist/css/bootstrap-theme.min.css'; - -import '../index.css'; -import { setUpLog } from './src/log'; -import { updateUrls } from './src/urls'; -import { BASE_URL } from './src/constants'; -import { getBuildFormValues } from './src/form'; -import { updateRepoText } from './src/repo'; +import "bootstrap/js/dropdown"; +import "bootstrap/dist/css/bootstrap.min.css"; +import "bootstrap/dist/css/bootstrap-theme.min.css"; +import "../index.css"; +import { setUpLog } from "./src/log"; +import { updateUrls } from "./src/urls"; +import { BASE_URL } from "./src/constants"; +import { getBuildFormValues } from "./src/form"; +import { updateRepoText } from "./src/repo"; function build(providerSpec, log, fitAddon, path, pathType) { updateFavicon(BASE_URL + "favicon_building.ico"); // split provider prefix off of providerSpec - const spec = providerSpec.slice(providerSpec.indexOf('/') + 1); + const spec = providerSpec.slice(providerSpec.indexOf("/") + 1); // Update the text of the loading page if it exists - if ($('div#loader-text').length > 0) { - $('div#loader-text p.launching').text("Starting repository: " + decodeURIComponent(spec)) + if ($("div#loader-text").length > 0) { + $("div#loader-text p.launching").text( + "Starting repository: " + decodeURIComponent(spec), + ); } - $('#build-progress .progress-bar').addClass('hidden'); + $("#build-progress .progress-bar").addClass("hidden"); log.clear(); - $('.on-build').removeClass('hidden'); + $(".on-build").removeClass("hidden"); - const buildToken = $("#build-token").data('token'); + const buildToken = $("#build-token").data("token"); // If BASE_URL is absolute, use that as the base for build endpoint URL. // Else, first resolve BASE_URL relative to current URL, then use *that* as the // base for the build endpoint url. - const buildEndpointUrl = new URL("build", new URL(BASE_URL, window.location.href)); - const image = new BinderRepository(providerSpec, buildEndpointUrl, buildToken); - - image.onStateChange('*', function(data) { + const buildEndpointUrl = new URL( + "build", + new URL(BASE_URL, window.location.href), + ); + const image = new BinderRepository( + providerSpec, + buildEndpointUrl, + buildToken, + ); + + image.onStateChange("*", function (data) { if (data.message !== undefined) { log.writeAndStore(data.message); fitAddon.fit(); @@ -53,48 +61,55 @@ function build(providerSpec, log, fitAddon, path, pathType) { } }); - image.onStateChange('waiting', function() { - $('#phase-waiting').removeClass('hidden'); + image.onStateChange("waiting", function () { + $("#phase-waiting").removeClass("hidden"); }); - image.onStateChange('building', function() { - $('#phase-building').removeClass('hidden'); + image.onStateChange("building", function () { + $("#phase-building").removeClass("hidden"); log.show(); }); - image.onStateChange('pushing', function() { - $('#phase-pushing').removeClass('hidden'); + image.onStateChange("pushing", function () { + $("#phase-pushing").removeClass("hidden"); }); - image.onStateChange('failed', function() { - $('#build-progress .progress-bar').addClass('hidden'); - $('#phase-failed').removeClass('hidden'); + image.onStateChange("failed", function () { + $("#build-progress .progress-bar").addClass("hidden"); + $("#phase-failed").removeClass("hidden"); $("#loader").addClass("paused"); // If we fail for any reason, show an error message and logs updateFavicon(BASE_URL + "favicon_fail.ico"); log.show(); - if ($('div#loader-text').length > 0) { - $('#loader').addClass("error"); - $('div#loader-text p.launching').html('Error loading ' + spec + '!
See logs below for details.'); + if ($("div#loader-text").length > 0) { + $("#loader").addClass("error"); + $("div#loader-text p.launching").html( + "Error loading " + spec + "!
See logs below for details.", + ); } image.close(); }); - image.onStateChange('built', function() { - $('#phase-already-built').removeClass('hidden'); - $('#phase-launching').removeClass('hidden'); + image.onStateChange("built", function () { + $("#phase-already-built").removeClass("hidden"); + $("#phase-launching").removeClass("hidden"); updateFavicon(BASE_URL + "favicon_success.ico"); }); - image.onStateChange('ready', function(data) { + image.onStateChange("ready", function (data) { image.close(); // If data.url is an absolute URL, it'll be used. Else, it'll be interpreted // relative to current page's URL. const serverUrl = new URL(data.url, window.location.href); // user server is ready, redirect to there - window.location.href = image.getFullRedirectURL(serverUrl, data.token, path, pathType); + window.location.href = image.getFullRedirectURL( + serverUrl, + data.token, + path, + pathType, + ); }); image.fetch(); @@ -102,62 +117,71 @@ function build(providerSpec, log, fitAddon, path, pathType) { } function indexMain() { - const [log, fitAddon] = setUpLog(); + const [log, fitAddon] = setUpLog(); - // setup badge dropdown and default values. - updateUrls(); + // setup badge dropdown and default values. + updateUrls(); - $("#provider_prefix_sel li").click(function(event){ - event.preventDefault(); + $("#provider_prefix_sel li").click(function (event) { + event.preventDefault(); - $("#provider_prefix-selected").text($(this).text()); - $("#provider_prefix").val($(this).attr("value")); - updateRepoText(); - updateUrls(); - }); + $("#provider_prefix-selected").text($(this).text()); + $("#provider_prefix").val($(this).attr("value")); + updateRepoText(); + updateUrls(); + }); - $("#url-or-file-btn").find("a").click(function (evt) { + $("#url-or-file-btn") + .find("a") + .click(function (evt) { evt.preventDefault(); $("#url-or-file-selected").text($(this).text()); updatePathText(); updateUrls(); }); - updatePathText(); - updateRepoText(); + updatePathText(); + updateRepoText(); - $('#repository').on('keyup paste change', function() {updateUrls();}); + $("#repository").on("keyup paste change", function () { + updateUrls(); + }); - $('#ref').on('keyup paste change', function() {updateUrls();}); + $("#ref").on("keyup paste change", function () { + updateUrls(); + }); - $('#filepath').on('keyup paste change', function() {updateUrls();}); + $("#filepath").on("keyup paste change", function () { + updateUrls(); + }); - $('#toggle-badge-snippet').on('click', function() { - const badgeSnippets = $('#badge-snippets'); - if (badgeSnippets.hasClass('hidden')) { - badgeSnippets.removeClass('hidden'); - $("#badge-snippet-caret").removeClass("glyphicon-triangle-right"); - $("#badge-snippet-caret").addClass("glyphicon-triangle-bottom"); - } else { - badgeSnippets.addClass('hidden'); - $("#badge-snippet-caret").removeClass("glyphicon-triangle-bottom"); - $("#badge-snippet-caret").addClass("glyphicon-triangle-right"); - } + $("#toggle-badge-snippet").on("click", function () { + const badgeSnippets = $("#badge-snippets"); + if (badgeSnippets.hasClass("hidden")) { + badgeSnippets.removeClass("hidden"); + $("#badge-snippet-caret").removeClass("glyphicon-triangle-right"); + $("#badge-snippet-caret").addClass("glyphicon-triangle-bottom"); + } else { + badgeSnippets.addClass("hidden"); + $("#badge-snippet-caret").removeClass("glyphicon-triangle-bottom"); + $("#badge-snippet-caret").addClass("glyphicon-triangle-right"); + } - return false; - }); + return false; + }); - $('#build-form').submit(function() { - const formValues = getBuildFormValues(); - updateUrls(formValues); - build( - formValues.providerPrefix + '/' + formValues.repo + '/' + formValues.ref, - log, fitAddon, - formValues.path, - formValues.pathType - ); - return false; - }); + $("#build-form").submit(function () { + const formValues = getBuildFormValues(); + updateUrls(formValues); + build( + formValues.providerPrefix + "/" + formValues.repo + "/" + formValues.ref, + log, + fitAddon, + formValues.path, + formValues.pathType, + ); + return false; + }); } function loadingMain(providerSpec) { @@ -167,32 +191,32 @@ function loadingMain(providerSpec) { // that is good because it is the real value and '/'s will be trimmed in `launch` const params = new URL(location.href).searchParams; let pathType, path; - path = params.get('urlpath'); + path = params.get("urlpath"); if (path) { - pathType = 'url'; + pathType = "url"; } else { - path = params.get('labpath'); + path = params.get("labpath"); if (path) { - pathType = 'lab'; + pathType = "lab"; } else { - path = params.get('filepath'); + path = params.get("filepath"); if (path) { - pathType = 'file'; + pathType = "file"; } } } build(providerSpec, log, fitAddon, path, pathType); // Looping through help text every few seconds - const launchMessageInterval = 6 * 1000 + const launchMessageInterval = 6 * 1000; setInterval(nextHelpText, launchMessageInterval); // If we have a long launch, add a class so we display a long launch msg - const launchTimeout = 120 * 1000 + const launchTimeout = 120 * 1000; setTimeout(() => { - $('div#loader-links p.text-center').addClass("longLaunch"); + $("div#loader-links p.text-center").addClass("longLaunch"); nextHelpText(); - }, launchTimeout) + }, launchTimeout); return false; } @@ -202,6 +226,6 @@ window.loadingMain = loadingMain; window.indexMain = indexMain; // Load the clipboard after the page loads so it can find the buttons it needs -window.onload = function() { - new ClipboardJS('.clipboard'); +window.onload = function () { + new ClipboardJS(".clipboard"); }; diff --git a/binderhub/static/js/src/badge.js b/binderhub/static/js/src/badge.js index 815a32ef4..8ec5d105f 100644 --- a/binderhub/static/js/src/badge.js +++ b/binderhub/static/js/src/badge.js @@ -7,10 +7,9 @@ export function makeBadgeMarkup(badgeBaseUrl, baseUrl, url, syntax) { badgeImageUrl = window.location.origin + baseUrl + "badge_logo.svg"; } - if (syntax === 'markdown') { + if (syntax === "markdown") { return "[![Binder](" + badgeImageUrl + ")](" + url + ")"; - } else if (syntax === 'rst') { + } else if (syntax === "rst") { return ".. image:: " + badgeImageUrl + "\n :target: " + url; - } } diff --git a/binderhub/static/js/src/constants.js b/binderhub/static/js/src/constants.js index 497ffa9c7..b1950ae0a 100644 --- a/binderhub/static/js/src/constants.js +++ b/binderhub/static/js/src/constants.js @@ -1,3 +1,2 @@ - -export const BASE_URL = $('#base-url').data().url; -export const BADGE_BASE_URL = $('#badge-base-url').data().url; +export const BASE_URL = $("#base-url").data().url; +export const BADGE_BASE_URL = $("#badge-base-url").data().url; diff --git a/binderhub/static/js/src/favicon.js b/binderhub/static/js/src/favicon.js index 1353226dc..b4a7dead1 100644 --- a/binderhub/static/js/src/favicon.js +++ b/binderhub/static/js/src/favicon.js @@ -4,14 +4,14 @@ * @param {String} href Path to Favicon to use */ function updateFavicon(href) { - let link = document.querySelector("link[rel*='icon']"); - if(!link) { - link = document.createElement('link'); - document.getElementsByTagName('head')[0].appendChild(link); - } - link.type = 'image/x-icon'; - link.rel = 'shortcut icon'; - link.href = href; + let link = document.querySelector("link[rel*='icon']"); + if (!link) { + link = document.createElement("link"); + document.getElementsByTagName("head")[0].appendChild(link); + } + link.type = "image/x-icon"; + link.rel = "shortcut icon"; + link.href = href; } export { updateFavicon }; diff --git a/binderhub/static/js/src/favicon.test.js b/binderhub/static/js/src/favicon.test.js index dfd515d3a..c73cab495 100644 --- a/binderhub/static/js/src/favicon.test.js +++ b/binderhub/static/js/src/favicon.test.js @@ -1,29 +1,35 @@ import { updateFavicon } from "./favicon"; afterEach(() => { - // Clear out HEAD after each test run, so our DOM is clean. - // Jest does *not* clear out the DOM between test runs on the same file! - document.querySelector("head").innerHTML = ''; + // Clear out HEAD after each test run, so our DOM is clean. + // Jest does *not* clear out the DOM between test runs on the same file! + document.querySelector("head").innerHTML = ""; }); test("Setting favicon when there is none works", () => { - expect(document.querySelector("link[rel*='icon']")).toBeNull(); + expect(document.querySelector("link[rel*='icon']")).toBeNull(); - updateFavicon("https://example.com/somefile.png"); + updateFavicon("https://example.com/somefile.png"); - expect(document.querySelector("link[rel*='icon']").href).toBe("https://example.com/somefile.png"); + expect(document.querySelector("link[rel*='icon']").href).toBe( + "https://example.com/somefile.png", + ); }); test("Setting favicon multiple times works without leaking link tags", () => { - expect(document.querySelector("link[rel*='icon']")).toBeNull(); + expect(document.querySelector("link[rel*='icon']")).toBeNull(); - updateFavicon("https://example.com/somefile.png"); + updateFavicon("https://example.com/somefile.png"); - expect(document.querySelector("link[rel*='icon']").href).toBe("https://example.com/somefile.png"); - expect(document.querySelectorAll("link[rel*='icon']").length).toBe(1); + expect(document.querySelector("link[rel*='icon']").href).toBe( + "https://example.com/somefile.png", + ); + expect(document.querySelectorAll("link[rel*='icon']").length).toBe(1); - updateFavicon("https://example.com/some-other-file.png"); + updateFavicon("https://example.com/some-other-file.png"); - expect(document.querySelector("link[rel*='icon']").href).toBe("https://example.com/some-other-file.png"); - expect(document.querySelectorAll("link[rel*='icon']").length).toBe(1); + expect(document.querySelector("link[rel*='icon']").href).toBe( + "https://example.com/some-other-file.png", + ); + expect(document.querySelectorAll("link[rel*='icon']").length).toBe(1); }); diff --git a/binderhub/static/js/src/form.js b/binderhub/static/js/src/form.js index 44be45986..60469d1ab 100644 --- a/binderhub/static/js/src/form.js +++ b/binderhub/static/js/src/form.js @@ -1,30 +1,36 @@ -import { getPathType } from './path'; - +import { getPathType } from "./path"; export function getBuildFormValues() { - const providerPrefix = $('#provider_prefix').val().trim(); - let repo = $('#repository').val().trim(); - if (providerPrefix !== 'git') { - repo = repo.replace(/^(https?:\/\/)?gist.github.com\//, ''); - repo = repo.replace(/^(https?:\/\/)?github.com\//, ''); - repo = repo.replace(/^(https?:\/\/)?gitlab.com\//, ''); + const providerPrefix = $("#provider_prefix").val().trim(); + let repo = $("#repository").val().trim(); + if (providerPrefix !== "git") { + repo = repo.replace(/^(https?:\/\/)?gist.github.com\//, ""); + repo = repo.replace(/^(https?:\/\/)?github.com\//, ""); + repo = repo.replace(/^(https?:\/\/)?gitlab.com\//, ""); } // trim trailing or leading '/' on repo - repo = repo.replace(/(^\/)|(\/?$)/g, ''); + repo = repo.replace(/(^\/)|(\/?$)/g, ""); // git providers encode the URL of the git repository as the repo // argument. - if (repo.includes("://") || providerPrefix === 'gl') { + if (repo.includes("://") || providerPrefix === "gl") { repo = encodeURIComponent(repo); } - let ref = $('#ref').val().trim() || $("#ref").attr("placeholder"); - if (providerPrefix === 'zenodo' || providerPrefix === 'figshare' || providerPrefix === 'dataverse' || - providerPrefix === 'hydroshare') { + let ref = $("#ref").val().trim() || $("#ref").attr("placeholder"); + if ( + providerPrefix === "zenodo" || + providerPrefix === "figshare" || + providerPrefix === "dataverse" || + providerPrefix === "hydroshare" + ) { ref = ""; } - const path = $('#filepath').val().trim(); + const path = $("#filepath").val().trim(); return { - 'providerPrefix': providerPrefix, 'repo': repo, - 'ref': ref, 'path': path, 'pathType': getPathType() + providerPrefix: providerPrefix, + repo: repo, + ref: ref, + path: path, + pathType: getPathType(), }; } diff --git a/binderhub/static/js/src/loading.js b/binderhub/static/js/src/loading.js index 956f87e7d..3569fd256 100644 --- a/binderhub/static/js/src/loading.js +++ b/binderhub/static/js/src/loading.js @@ -1,31 +1,32 @@ // Cycle through helpful messages on the loading page const help_messages = [ - 'New to Binder? Check out the Binder Documentation for more information.', - 'You can learn more about building your own Binder repositories in the Binder community documentation.', - 'We use the repo2docker tool to automatically build the environment in which to run your code.', - 'Take a look at the full list of configuration files supported by repo2docker.', - 'Need more than just a Jupyter notebook? You can customize the user interface.', - 'Take a look at our gallery of example repositories.', - 'If a repository takes a long time to launch, it is usually because Binder needs to create the environment for the first time.', - 'The tool that powers this page is called BinderHub. It is an open source tool that you can deploy yourself.', - 'The Binder team has a site reliability guide that talks about what it is like to run a BinderHub.', - 'You can connect with the Binder community in the Jupyter community forum.', - 'Empty log? Notebook not loading? Maybe your ad blocker is interfering. Consider adding this site to the list of trusted sources.', - 'Your launch may take longer the first few times a repository is used. This is because our machine needs to create your environment.', - 'Read our advice for speeding up your Binder launch.', -] + 'New to Binder? Check out the Binder Documentation for more information.', + 'You can learn more about building your own Binder repositories in the Binder community documentation.', + 'We use the repo2docker tool to automatically build the environment in which to run your code.', + 'Take a look at the full list of configuration files supported by repo2docker.', + 'Need more than just a Jupyter notebook? You can customize the user interface.', + 'Take a look at our gallery of example repositories.', + "If a repository takes a long time to launch, it is usually because Binder needs to create the environment for the first time.", + 'The tool that powers this page is called BinderHub. It is an open source tool that you can deploy yourself.', + 'The Binder team has a site reliability guide that talks about what it is like to run a BinderHub.', + 'You can connect with the Binder community in the Jupyter community forum.', + "Empty log? Notebook not loading? Maybe your ad blocker is interfering. Consider adding this site to the list of trusted sources.", + "Your launch may take longer the first few times a repository is used. This is because our machine needs to create your environment.", + 'Read our advice for speeding up your Binder launch.', +]; // Set a launch timeout beyond-which we'll stop cycling messages -export function nextHelpText () { - const text = $('div#loader-links p.text-center'); - let msg; - if (text !== null) { - if (!text.hasClass('longLaunch')) { - // Pick a random help message and update - msg = help_messages[Math.floor(Math.random() * help_messages.length)]; - } else { - msg = 'Your session is taking longer than usual to start!
Check the log messages below to see what is happening.'; - } - text.html(msg); +export function nextHelpText() { + const text = $("div#loader-links p.text-center"); + let msg; + if (text !== null) { + if (!text.hasClass("longLaunch")) { + // Pick a random help message and update + msg = help_messages[Math.floor(Math.random() * help_messages.length)]; + } else { + msg = + "Your session is taking longer than usual to start!
Check the log messages below to see what is happening."; } + text.html(msg); + } } diff --git a/binderhub/static/js/src/log.js b/binderhub/static/js/src/log.js index 5c687eb50..46664cb40 100644 --- a/binderhub/static/js/src/log.js +++ b/binderhub/static/js/src/log.js @@ -1,17 +1,17 @@ -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import { Terminal } from "xterm"; +import { FitAddon } from "xterm-addon-fit"; export function setUpLog() { const log = new Terminal({ convertEol: true, - disableStdin: true + disableStdin: true, }); const fitAddon = new FitAddon(); log.loadAddon(fitAddon); const logMessages = []; - log.open(document.getElementById('log'), false); + log.open(document.getElementById("log"), false); fitAddon.fit(); $(window).resize(function () { @@ -20,31 +20,31 @@ export function setUpLog() { const $panelBody = $("div.panel-body"); log.show = function () { - $('#toggle-logs button.toggle').text('hide'); - $panelBody.removeClass('hidden'); + $("#toggle-logs button.toggle").text("hide"); + $panelBody.removeClass("hidden"); }; log.hide = function () { - $('#toggle-logs button.toggle').text('show'); - $panelBody.addClass('hidden'); + $("#toggle-logs button.toggle").text("show"); + $panelBody.addClass("hidden"); }; log.toggle = function () { - if ($panelBody.hasClass('hidden')) { + if ($panelBody.hasClass("hidden")) { log.show(); } else { log.hide(); } }; - $('#view-raw-logs').on('click', function (ev) { - const blob = new Blob([logMessages.join('')], { type: 'text/plain' }); + $("#view-raw-logs").on("click", function (ev) { + const blob = new Blob([logMessages.join("")], { type: "text/plain" }); this.href = window.URL.createObjectURL(blob); // Prevent the toggle action from firing ev.stopPropagation(); }); - $('#toggle-logs').click(log.toggle); + $("#toggle-logs").click(log.toggle); log.writeAndStore = function (msg) { logMessages.push(msg); diff --git a/binderhub/static/js/src/repo.js b/binderhub/static/js/src/repo.js index c71c7672e..42dceb62b 100644 --- a/binderhub/static/js/src/repo.js +++ b/binderhub/static/js/src/repo.js @@ -10,21 +10,23 @@ function setLabels() { const label_prop_disabled = configDict[provider]["label_prop_disabled"]; const placeholder = "HEAD"; - $("#ref").attr('placeholder', placeholder).prop("disabled", ref_prop_disabled); + $("#ref") + .attr("placeholder", placeholder) + .prop("disabled", ref_prop_disabled); $("label[for=ref]").text(tag_text).prop("disabled", label_prop_disabled); - $("#repository").attr('placeholder', text); + $("#repository").attr("placeholder", text); $("label[for=repository]").text(text); } export function updateRepoText() { if (Object.keys(configDict).length === 0) { const configUrl = BASE_URL + "_config"; - fetch(configUrl).then(resp => { - resp.json().then(data => { - configDict = data + fetch(configUrl).then((resp) => { + resp.json().then((data) => { + configDict = data; setLabels(); }); - }) + }); } else { setLabels(); } diff --git a/binderhub/static/js/src/urls.js b/binderhub/static/js/src/urls.js index b869c10f6..5f58a9ecd 100644 --- a/binderhub/static/js/src/urls.js +++ b/binderhub/static/js/src/urls.js @@ -1,6 +1,6 @@ -import { makeBadgeMarkup } from './badge'; -import { getBuildFormValues } from './form'; -import { BADGE_BASE_URL, BASE_URL } from './constants'; +import { makeBadgeMarkup } from "./badge"; +import { getBuildFormValues } from "./form"; +import { BADGE_BASE_URL, BASE_URL } from "./constants"; function v2url(providerPrefix, repository, ref, path, pathType) { // return a v2 url from a providerPrefix, repository, ref, and (file|url)path @@ -10,14 +10,22 @@ function v2url(providerPrefix, repository, ref, path, pathType) { } let url; if (BADGE_BASE_URL) { - url = BADGE_BASE_URL + 'v2/' + providerPrefix + '/' + repository + '/' + ref; - } - else { - url = window.location.origin + BASE_URL + 'v2/' + providerPrefix + '/' + repository + '/' + ref; + url = + BADGE_BASE_URL + "v2/" + providerPrefix + "/" + repository + "/" + ref; + } else { + url = + window.location.origin + + BASE_URL + + "v2/" + + providerPrefix + + "/" + + repository + + "/" + + ref; } if (path && path.length > 0) { // encode the path, it will be decoded in loadingMain - url = url + '?' + pathType + 'path=' + encodeURIComponent(path); + url = url + "?" + pathType + "path=" + encodeURIComponent(path); } return url; } @@ -31,23 +39,25 @@ export function updateUrls(formValues) { formValues.repo, formValues.ref, formValues.path, - formValues.pathType + formValues.pathType, ); - if ((url || '').trim().length > 0) { + if ((url || "").trim().length > 0) { // update URLs and links (badges, etc.) - $("#badge-link").attr('href', url); - $('#basic-url-snippet').text(url); - $('#markdown-badge-snippet').text( - makeBadgeMarkup(BADGE_BASE_URL, BASE_URL, url, 'markdown') + $("#badge-link").attr("href", url); + $("#basic-url-snippet").text(url); + $("#markdown-badge-snippet").text( + makeBadgeMarkup(BADGE_BASE_URL, BASE_URL, url, "markdown"), ); - $('#rst-badge-snippet').text( - makeBadgeMarkup(BADGE_BASE_URL, BASE_URL, url, 'rst') + $("#rst-badge-snippet").text( + makeBadgeMarkup(BADGE_BASE_URL, BASE_URL, url, "rst"), ); } else { - ['#basic-url-snippet', '#markdown-badge-snippet', '#rst-badge-snippet'].map(function (item) { - const el = $(item); - el.text(el.attr('data-default')); - }); + ["#basic-url-snippet", "#markdown-badge-snippet", "#rst-badge-snippet"].map( + function (item) { + const el = $(item); + el.text(el.attr("data-default")); + }, + ); } } diff --git a/examples/appendix/static/custom.js b/examples/appendix/static/custom.js index ca98c1481..8b62e0f7a 100644 --- a/examples/appendix/static/custom.js +++ b/examples/appendix/static/custom.js @@ -1,35 +1,48 @@ function copy_link_into_clipboard(b) { - var $temp = $(""); - $(b).parent().append($temp); - $temp.val($(b).data('url')).select(); - document.execCommand("copy"); - $temp.remove(); + var $temp = $(""); + $(b).parent().append($temp); + $temp.val($(b).data("url")).select(); + document.execCommand("copy"); + $temp.remove(); } function add_binder_buttons() { - var copy_button = ''; + var copy_button = + '"; - var link_button = '' + - ' Go to {name}'; + var link_button = + '' + + " Go to {name}"; - var s = $(""); - s.append(link_button.replace(/{name}/g, 'repo').replace('{url}', '{repo_url}')); - s.append(copy_button.replace(/{name}/g, 'binder').replace('{url}', '{binder_url}')); - if ($("#ipython_notebook").length && $("#ipython_notebook>a").length) { - s.append(copy_button.replace(/{name}/g, 'session').replace('{url}', window.location.origin.concat($("#ipython_notebook>a").attr('href')))); - } - // add buttons at the end of header-container - $("#header-container").append(s); + var s = $(""); + s.append( + link_button.replace(/{name}/g, "repo").replace("{url}", "{repo_url}"), + ); + s.append( + copy_button.replace(/{name}/g, "binder").replace("{url}", "{binder_url}"), + ); + if ($("#ipython_notebook").length && $("#ipython_notebook>a").length) { + s.append( + copy_button + .replace(/{name}/g, "session") + .replace( + "{url}", + window.location.origin.concat($("#ipython_notebook>a").attr("href")), + ), + ); + } + // add buttons at the end of header-container + $("#header-container").append(s); } add_binder_buttons(); diff --git a/js/packages/binderhub-client/lib/index.js b/js/packages/binderhub-client/lib/index.js index fa9c66f56..46b09164f 100644 --- a/js/packages/binderhub-client/lib/index.js +++ b/js/packages/binderhub-client/lib/index.js @@ -1,4 +1,4 @@ -import { NativeEventSource, EventSourcePolyfill } from 'event-source-polyfill'; +import { NativeEventSource, EventSourcePolyfill } from "event-source-polyfill"; // Use native browser EventSource if available, and use the polyfill if not available const EventSource = NativeEventSource || EventSourcePolyfill; @@ -16,20 +16,22 @@ export class BinderRepository { constructor(providerSpec, buildEndpointUrl, buildToken) { this.providerSpec = providerSpec; // Make sure that buildEndpointUrl is a real URL - this ensures hostname is properly set - if(!(buildEndpointUrl instanceof URL)) { - throw new TypeError(`buildEndpointUrl must be a URL object, got ${buildEndpointUrl} instead`); + if (!(buildEndpointUrl instanceof URL)) { + throw new TypeError( + `buildEndpointUrl must be a URL object, got ${buildEndpointUrl} instead`, + ); } // We make a copy here so we don't modify the passed in URL object this.buildEndpointUrl = new URL(buildEndpointUrl); // The binderHub API is path based, so the buildEndpointUrl must have a trailing slash. We add // it if it is not passed in here to us. - if(!this.buildEndpointUrl.pathname.endsWith('/')) { + if (!this.buildEndpointUrl.pathname.endsWith("/")) { this.buildEndpointUrl.pathname += "/"; } // The actual URL we'll make a request to build this particular providerSpec this.buildUrl = new URL(this.providerSpec, this.buildEndpointUrl); - if(buildToken) { + if (buildToken) { this.buildUrl.searchParams.append("build_token", buildToken); } this.callbacks = {}; @@ -43,7 +45,7 @@ export class BinderRepository { this.eventSource.onerror = (err) => { console.error("Failed to construct event stream", err); this._changeState("failed", { - message: "Failed to connect to event stream\n" + message: "Failed to connect to event stream\n", }); }; this.eventSource.addEventListener("message", (event) => { @@ -108,11 +110,10 @@ export class BinderRepository { } } - url.searchParams.append('token', token); + url.searchParams.append("token", token); return url; } - /** * Add callback whenever state of the current build changes * @@ -128,7 +129,7 @@ export class BinderRepository { } _changeState(state, data) { - [state, "*"].map(key => { + [state, "*"].map((key) => { const callbacks = this.callbacks[key]; if (callbacks) { for (let i = 0; i < callbacks.length; i++) { diff --git a/js/packages/binderhub-client/lib/index.test.js b/js/packages/binderhub-client/lib/index.test.js index 7fd96c445..dd03daab3 100644 --- a/js/packages/binderhub-client/lib/index.test.js +++ b/js/packages/binderhub-client/lib/index.test.js @@ -4,7 +4,7 @@ test("Passed in URL object is not modified", () => { const buildEndpointUrl = new URL("https://test-binder.org/build"); const br = new BinderRepository("gh/test/test", buildEndpointUrl, "token"); expect(br.buildEndpointUrl.toString()).not.toEqual( - buildEndpointUrl.toString() + buildEndpointUrl.toString(), ); }); @@ -18,7 +18,7 @@ test("Trailing slash added if needed", () => { const buildEndpointUrl = new URL("https://test-binder.org/build"); const br = new BinderRepository("gh/test/test", buildEndpointUrl); expect(br.buildEndpointUrl.toString()).toEqual( - "https://test-binder.org/build/" + "https://test-binder.org/build/", ); }); @@ -26,7 +26,7 @@ test("Build URL correctly built from Build Endpoint", () => { const buildEndpointUrl = new URL("https://test-binder.org/build"); const br = new BinderRepository("gh/test/test", buildEndpointUrl); expect(br.buildUrl.toString()).toEqual( - "https://test-binder.org/build/gh/test/test" + "https://test-binder.org/build/gh/test/test", ); }); @@ -34,26 +34,26 @@ test("Build URL correctly built from Build Endpoint when used with token", () => const buildEndpointUrl = new URL("https://test-binder.org/build"); const br = new BinderRepository("gh/test/test", buildEndpointUrl, "token"); expect(br.buildUrl.toString()).toEqual( - "https://test-binder.org/build/gh/test/test?build_token=token" + "https://test-binder.org/build/gh/test/test?build_token=token", ); }); test("Get full redirect URL with correct token but no path", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br .getFullRedirectURL("https://hub.test-binder.org/user/something", "token") - .toString() + .toString(), ).toBe("https://hub.test-binder.org/user/something?token=token"); }); test("Get full redirect URL with urlpath", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -61,16 +61,16 @@ test("Get full redirect URL with urlpath", () => { "https://hub.test-binder.org/user/something", "token", "rstudio", - "url" + "url", ) - .toString() + .toString(), ).toBe("https://hub.test-binder.org/user/something/rstudio?token=token"); }); test("Get full redirect URL when opening a file with jupyterlab", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -78,16 +78,18 @@ test("Get full redirect URL when opening a file with jupyterlab", () => { "https://hub.test-binder.org/user/something", "token", "index.ipynb", - "lab" + "lab", ) - .toString() - ).toBe("https://hub.test-binder.org/user/something/doc/tree/index.ipynb?token=token"); + .toString(), + ).toBe( + "https://hub.test-binder.org/user/something/doc/tree/index.ipynb?token=token", + ); }); test("Get full redirect URL when opening a file with classic notebook (with file= path)", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -95,16 +97,18 @@ test("Get full redirect URL when opening a file with classic notebook (with file "https://hub.test-binder.org/user/something", "token", "index.ipynb", - "file" + "file", ) - .toString() - ).toBe("https://hub.test-binder.org/user/something/tree/index.ipynb?token=token"); + .toString(), + ).toBe( + "https://hub.test-binder.org/user/something/tree/index.ipynb?token=token", + ); }); test("Get full redirect URL and deal with excessive slashes (with pathType=url)", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -114,16 +118,16 @@ test("Get full redirect URL and deal with excessive slashes (with pathType=url)" "token", // Trailing slash should be preserved here, but leading slash should not be repeated "/rstudio/", - "url" + "url", ) - .toString() + .toString(), ).toBe("https://hub.test-binder.org/user/something/rstudio/?token=token"); }); test("Get full redirect URL and deal with excessive slashes (with pathType=lab)", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -132,16 +136,18 @@ test("Get full redirect URL and deal with excessive slashes (with pathType=lab)" "token", // Both leading and trailing slashes should be gone here. "/directory/index.ipynb/", - "lab" + "lab", ) - .toString() - ).toBe("https://hub.test-binder.org/user/something/doc/tree/directory/index.ipynb?token=token"); + .toString(), + ).toBe( + "https://hub.test-binder.org/user/something/doc/tree/directory/index.ipynb?token=token", + ); }); test("Get full redirect URL and deal with excessive slashes (with pathType=file)", () => { const br = new BinderRepository( "gh/test/test", - new URL("https://test-binder.org/build") + new URL("https://test-binder.org/build"), ); expect( br @@ -150,8 +156,10 @@ test("Get full redirect URL and deal with excessive slashes (with pathType=file) "token", // Both leading and trailing slashes should be gone here. "/directory/index.ipynb/", - "file" + "file", ) - .toString() - ).toBe("https://hub.test-binder.org/user/something/tree/directory/index.ipynb?token=token"); + .toString(), + ).toBe( + "https://hub.test-binder.org/user/something/tree/directory/index.ipynb?token=token", + ); }); diff --git a/webpack.config.js b/webpack.config.js index eedab27ce..ba73e76a6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,58 +1,58 @@ -const webpack = require('webpack'); -const path = require('path'); +const webpack = require("webpack"); +const path = require("path"); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { - mode: 'production', - context: path.resolve(__dirname, 'binderhub/static'), - entry: "./js/index.js", - output: { - path: path.resolve(__dirname, 'binderhub/static/dist/'), - filename: "bundle.js", - publicPath: '/static/dist/' - }, - plugins: [ - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - }), - new MiniCssExtractPlugin({ - filename: 'styles.css' - }), - ], - module: { - rules: [ - { - test: /\.js$/, - exclude: /(node_modules|bower_components)/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - } - } - }, - { - test: /\.css$/i, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { - // Set publicPath as relative path ("./"). - // By default it uses the `output.publicPath` ("/static/dist/"), when it rewrites the URLs in styles.css. - // And it causes these files unavailabe if BinderHub has a different base_url than "/". - publicPath: "./", - }, - }, - 'css-loader' - ], + mode: "production", + context: path.resolve(__dirname, "binderhub/static"), + entry: "./js/index.js", + output: { + path: path.resolve(__dirname, "binderhub/static/dist/"), + filename: "bundle.js", + publicPath: "/static/dist/", + }, + plugins: [ + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery", + }), + new MiniCssExtractPlugin({ + filename: "styles.css", + }), + ], + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: "babel-loader", + options: { + presets: ["@babel/preset-env"], + }, + }, + }, + { + test: /\.css$/i, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + // Set publicPath as relative path ("./"). + // By default it uses the `output.publicPath` ("/static/dist/"), when it rewrites the URLs in styles.css. + // And it causes these files unavailabe if BinderHub has a different base_url than "/". + publicPath: "./", }, - { - test: /\.(eot|woff|ttf|woff2|svg)$/, - type: 'asset/resource' - } - ] - }, - devtool: 'source-map', -} + }, + "css-loader", + ], + }, + { + test: /\.(eot|woff|ttf|woff2|svg)$/, + type: "asset/resource", + }, + ], + }, + devtool: "source-map", +};