Skip to content

Commit

Permalink
Merge pull request #1778 from yuvipanda/badgey
Browse files Browse the repository at this point in the history
JS: Refactor link & badge generation, use URLs (not string) for base URLs
  • Loading branch information
consideRatio authored Oct 18, 2023
2 parents 3dfe348 + cd9c9e6 commit 9f6ea44
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 106 deletions.
30 changes: 12 additions & 18 deletions binderhub/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ 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 { BASE_URL, BADGE_BASE_URL } from "./src/constants";
import { getBuildFormValues } from "./src/form";
import { updateRepoText } from "./src/repo";

async function build(providerSpec, log, fitAddon, path, pathType) {
updateFavicon(BASE_URL + "favicon_building.ico");
updateFavicon(new URL("favicon_building.ico", BASE_URL));
// split provider prefix off of providerSpec
const spec = providerSpec.slice(providerSpec.indexOf("/") + 1);
// Update the text of the loading page if it exists
Expand All @@ -39,13 +39,7 @@ async function build(providerSpec, log, fitAddon, path, pathType) {
$(".on-build").removeClass("hidden");

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 buildEndpointUrl = new URL("build", BASE_URL);
const image = new BinderRepository(
providerSpec,
buildEndpointUrl,
Expand Down Expand Up @@ -82,7 +76,7 @@ async function build(providerSpec, log, fitAddon, path, pathType) {
$("#loader").addClass("paused");

// If we fail for any reason, show an error message and logs
updateFavicon(BASE_URL + "favicon_fail.ico");
updateFavicon(new URL("favicon_fail.ico", BASE_URL));
log.show();
if ($("div#loader-text").length > 0) {
$("#loader").addClass("error");
Expand All @@ -96,7 +90,7 @@ async function build(providerSpec, log, fitAddon, path, pathType) {
case "built": {
$("#phase-already-built").removeClass("hidden");
$("#phase-launching").removeClass("hidden");
updateFavicon(BASE_URL + "favicon_success.ico");
updateFavicon(new URL("favicon_success.ico", BASE_URL));
break;
}
case "ready": {
Expand Down Expand Up @@ -127,15 +121,15 @@ function indexMain() {
const [log, fitAddon] = setUpLog();

// setup badge dropdown and default values.
updateUrls();
updateUrls(BADGE_BASE_URL);

$("#provider_prefix_sel li").click(function (event) {
event.preventDefault();

$("#provider_prefix-selected").text($(this).text());
$("#provider_prefix").val($(this).attr("value"));
updateRepoText();
updateUrls();
updateUrls(BADGE_BASE_URL);
});

$("#url-or-file-btn")
Expand All @@ -145,21 +139,21 @@ function indexMain() {

$("#url-or-file-selected").text($(this).text());
updatePathText();
updateUrls();
updateUrls(BADGE_BASE_URL);
});
updatePathText();
updateRepoText();

$("#repository").on("keyup paste change", function () {
updateUrls();
updateUrls(BADGE_BASE_URL);
});

$("#ref").on("keyup paste change", function () {
updateUrls();
updateUrls(BADGE_BASE_URL);
});

$("#filepath").on("keyup paste change", function () {
updateUrls();
updateUrls(BADGE_BASE_URL);
});

$("#toggle-badge-snippet").on("click", function () {
Expand All @@ -180,7 +174,7 @@ function indexMain() {
$("#build-form").submit(async function (e) {
e.preventDefault();
const formValues = getBuildFormValues();
updateUrls(formValues);
updateUrls(BADGE_BASE_URL, formValues);
await build(
formValues.providerPrefix + "/" + formValues.repo + "/" + formValues.ref,
log,
Expand Down
24 changes: 0 additions & 24 deletions binderhub/static/js/src/badge.js

This file was deleted.

23 changes: 16 additions & 7 deletions binderhub/static/js/src/constants.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
/**
* @type {string}
* Base URL of this binderhub installation
* @type {URL}
* Base URL of this binderhub installation.
*
* Guaranteed to have a leading & trailing slash by the binderhub python configuration.
*/
export const BASE_URL = $("#base-url").data().url;
export const BASE_URL = new URL(
document.getElementById("base-url").dataset.url,
document.location.origin,
);

const badge_base_url = document.getElementById("badge-base-url").dataset.url;
/**
* @type {string}
* Optional base URL to use for both badge images as well as launch links.
* @type {URL}
* Base URL to use for both badge images as well as launch links.
*
* Is different from BASE_URL primarily when used as part of a federation.
* If not explicitly set, will default to BASE_URL. Primarily set up different than BASE_URL
* when used as part of a federation
*/
export const BADGE_BASE_URL = $("#badge-base-url").data().url;
export const BADGE_BASE_URL = badge_base_url
? new URL(badge_base_url, document.location.origin)
: BASE_URL;
2 changes: 1 addition & 1 deletion binderhub/static/js/src/favicon.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Dynamically set current page's favicon.
*
* @param {String} href Path to Favicon to use
* @param {URL} href Path to Favicon to use
*/
function updateFavicon(href) {
let link = document.querySelector("link[rel*='icon']");
Expand Down
2 changes: 1 addition & 1 deletion binderhub/static/js/src/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function setLabels() {
*/
export function updateRepoText() {
if (Object.keys(configDict).length === 0) {
const configUrl = BASE_URL + "_config";
const configUrl = new URL("_config", BASE_URL);
fetch(configUrl).then((resp) => {
resp.json().then((data) => {
configDict = data;
Expand Down
70 changes: 16 additions & 54 deletions binderhub/static/js/src/urls.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,33 @@
import { makeBadgeMarkup } from "./badge";
import { getBuildFormValues } from "./form";
import { BADGE_BASE_URL, BASE_URL } from "./constants";

/**
* Generate a shareable binder URL for given repository
* @param {string} providerPrefix prefix denoting what provider was selected
* @param {string} repo repo to build
* @param {[string]} ref optional ref in this repo to build
* @param {string} path Path to launch after this repo has been built
* @param {string} pathType Type of thing to open path with (raw url, notebook file, lab, etc)
*
* @returns {string|null} A URL that can be shared with others, and clicking which will launch the repo
*/
function v2url(providerPrefix, repository, ref, path, pathType) {
// return a v2 url from a providerPrefix, repository, ref, and (file|url)path
if (repository.length === 0) {
// no repo, no url
return null;
}
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;
}
if (path && path.length > 0) {
// encode the path, it will be decoded in loadingMain
url = url + "?" + pathType + "path=" + encodeURIComponent(path);
}
return url;
}
import {
makeShareableBinderURL,
makeBadgeMarkup,
} from "@jupyterhub/binderhub-client";

/**
* Update the shareable URL and badge snippets in the UI based on values user has entered in the form
*/
export function updateUrls(formValues) {
export function updateUrls(publicBaseUrl, formValues) {
if (typeof formValues === "undefined") {
formValues = getBuildFormValues();
}
const url = v2url(
formValues.providerPrefix,
formValues.repo,
formValues.ref,
formValues.path,
formValues.pathType,
);
if (formValues.repo) {
const url = makeShareableBinderURL(
publicBaseUrl,
formValues.providerPrefix,
formValues.repo,
formValues.ref,
formValues.path,
formValues.pathType,
);

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"),
);
$("#rst-badge-snippet").text(
makeBadgeMarkup(BADGE_BASE_URL, BASE_URL, url, "rst"),
makeBadgeMarkup(publicBaseUrl, url, "markdown"),
);
$("#rst-badge-snippet").text(makeBadgeMarkup(publicBaseUrl, url, "rst"));
} else {
["#basic-url-snippet", "#markdown-badge-snippet", "#rst-badge-snippet"].map(
function (item) {
Expand Down
62 changes: 62 additions & 0 deletions js/packages/binderhub-client/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,65 @@ export class BinderRepository {
return url;
}
}

/**
* Generate a shareable binder URL for given repository
*
* @param {URL} publicBaseUrl Base URL to use for making public URLs. Must end with a trailing slash.
* @param {string} providerPrefix prefix denoting what provider was selected
* @param {string} repo repo to build
* @param {string} ref optional ref in this repo to build
* @param {[string]} path Path to launch after this repo has been built
* @param {[string]} pathType Type of thing to open path with (raw url, notebook file, lab, etc)
*
* @returns {URL} A URL that can be shared with others, and clicking which will launch the repo
*/
export function makeShareableBinderURL(
publicBaseUrl,
providerPrefix,
repository,
ref,
path,
pathType,
) {
if (!publicBaseUrl.pathname.endsWith("/")) {
throw new Error(
`publicBaseUrl must end with a trailing slash, got ${publicBaseUrl}`,
);
}
const url = new URL(
`v2/${providerPrefix}/${repository}/${ref}`,
publicBaseUrl,
);
if (path && path.length > 0) {
url.searchParams.append(`${pathType}path`, path);
}
return url;
}

/**
* Generate markup that people can put on their README or documentation to link to a specific binder
*
* @param {URL} publicBaseUrl Base URL to use for making public URLs
* @param {URL} url Link target URL that represents this binder installation
* @param {string} syntax Kind of markup to generate. Supports 'markdown' and 'rst'
* @returns {string}
*/
export function makeBadgeMarkup(publicBaseUrl, url, syntax) {
if (!publicBaseUrl.pathname.endsWith("/")) {
throw new Error(
`publicBaseUrl must end with a trailing slash, got ${publicBaseUrl}`,
);
}
const badgeImageUrl = new URL("badge_logo.svg", publicBaseUrl);

if (syntax === "markdown") {
return `[![Binder](${badgeImageUrl})](${url})`;
} else if (syntax === "rst") {
return `.. image:: ${badgeImageUrl}\n :target: ${url}`;
} else {
throw new Error(
`Only markdown or rst badges are supported, got ${syntax} instead`,
);
}
}
Loading

0 comments on commit 9f6ea44

Please sign in to comment.