diff --git a/assets/links.js b/assets/links.js new file mode 100644 index 0000000..96b5cd5 --- /dev/null +++ b/assets/links.js @@ -0,0 +1,39 @@ +function buildRepoLink() { + let formRef = document.forms["repoSelect"]; + + let hoster = formRef.elements["hosterSelect"].value.toLowerCase(); + let owner = formRef.elements["owner"].value; + let repoName = formRef.elements["repoName"].value; + + if (hoster === "gitea") { + let baseUrl = formRef.elements["baseUrl"].value; + + // verify that the Base URL is not empty + if(baseUrl.length === 0) { + formRef.elements["baseUrl"].classList.add("is-danger"); + document.getElementById("baseUrlHelp").classList.add("is-danger"); + let hostName = formRef.elements["hosterSelect"].value; + document.getElementById("baseUrlHelp").textContent = `A Base URL is required for Hosting Provider ${hostName}.` + + return; + } + + window.location.href = `/repo/${hoster}/${baseUrl}/${owner}/${repoName}`; + } else { + window.location.href = `/repo/${hoster}/${owner}/${repoName}`; + } +} + +function buildCrateLink() { + let formRef = document.forms["crateSelect"]; + + let crate = formRef.elements["crateName"].value; + let crateVer = formRef.elements["crateVersion"].value; + + if (crateVer.length == 0) { + // default to latest version + window.location.href = `/crate/${crate}`; + } else { + window.location.href = `/crate/${crate}/${crateVer}`; + } +} diff --git a/assets/styles/main.sass b/assets/styles/main.sass index 6a35a1f..fb414d2 100644 --- a/assets/styles/main.sass +++ b/assets/styles/main.sass @@ -23,13 +23,17 @@ $family-monospace: "Source Serif Pro", monospace @import "bulma/base/generic" @import "bulma/elements/box" +@import "bulma/elements/button" @import "bulma/elements/container" @import "bulma/elements/content" +@import "bulma/elements/form" @import "bulma/elements/notification" @import "bulma/elements/table" @import "bulma/elements/tag" @import "bulma/elements/title" +@import "bulma/form/_all" + @import "bulma/components/level" @import "bulma/components/message" @import "bulma/components/navbar" diff --git a/build.rs b/build.rs index ee37440..add123a 100644 --- a/build.rs +++ b/build.rs @@ -18,6 +18,7 @@ fn build_style() -> String { fn main() { let out_dir = env::var("OUT_DIR").unwrap(); + // compile the sass files into a single CSS file to be served and cached let style = build_style(); let css_path = Path::new(&out_dir).join("style.css"); @@ -26,4 +27,13 @@ fn main() { let hash_path = Path::new(&out_dir).join("style.css.sha1"); let digest = Sha1::digest(style.as_bytes()); fs::write(hash_path, format!("{:x}", digest)).unwrap(); + + // hash and copy the JS file + let js_blob = fs::read("./assets/links.js").unwrap(); + let js_path = Path::new(&out_dir).join("links.js"); + fs::write(js_path, &js_blob).unwrap(); + + let js_hash_path = Path::new(&out_dir).join("links.js.sha1"); + let js_digest = Sha1::digest(&js_blob); + fs::write(js_hash_path, format!("{:x}", js_digest)).unwrap(); } diff --git a/src/server/assets.rs b/src/server/assets.rs index b7e6836..7172d7f 100644 --- a/src/server/assets.rs +++ b/src/server/assets.rs @@ -10,3 +10,15 @@ pub const STATIC_STYLE_CSS_ETAG: &str = concat!( "\"" ); pub static STATIC_FAVICON: &[u8] = include_bytes!("../../assets/logo.svg"); + +pub static STATIC_LINKS_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/links.js")); +pub const STATIC_LINKS_JS_PATH: &str = concat!( + "/static/links.", + include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")), + ".js" +); +pub const STATIC_LINKS_JS_ETAG: &str = concat!( + "\"", + include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")), + "\"" +); diff --git a/src/server/mod.rs b/src/server/mod.rs index 8134cc1..bccd5df 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,7 +15,9 @@ use slog::{error, info, o, Logger}; mod assets; mod views; -use self::assets::{STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH}; +use self::assets::{ + STATIC_LINKS_JS_ETAG, STATIC_LINKS_JS_PATH, STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH, +}; use crate::engine::{AnalyzeDependenciesOutcome, Engine}; use crate::models::crates::{CrateName, CratePath}; use crate::models::repo::RepoPath; @@ -31,6 +33,7 @@ enum StatusFormat { enum StaticFile { StyleCss, FaviconPng, + LinksJs, } enum Route { @@ -56,6 +59,7 @@ impl App { router.add(STATIC_STYLE_CSS_PATH, Route::Static(StaticFile::StyleCss)); router.add("/static/logo.svg", Route::Static(StaticFile::FaviconPng)); + router.add(STATIC_LINKS_JS_PATH, Route::Static(StaticFile::LinksJs)); router.add( "/repo/*site/:qual/:name", @@ -371,6 +375,12 @@ impl App { .header(CONTENT_TYPE, "image/svg+xml") .body(Body::from(assets::STATIC_FAVICON)) .unwrap(), + StaticFile::LinksJs => Response::builder() + .header(CONTENT_TYPE, "text/javascript; charset=utf-8") + .header(ETAG, STATIC_LINKS_JS_ETAG) + .header(CACHE_CONTROL, "public, max-age=365000000, immutable") + .body(Body::from(assets::STATIC_LINKS_JS)) + .unwrap(), } } } diff --git a/src/server/views/html/index.rs b/src/server/views/html/index.rs index e28c3e4..c63fea0 100644 --- a/src/server/views/html/index.rs +++ b/src/server/views/html/index.rs @@ -4,6 +4,94 @@ use maud::{html, Markup}; use crate::models::crates::CratePath; use crate::models::repo::Repository; +use crate::server::assets::STATIC_LINKS_JS_PATH; + +fn link_forms() -> Markup { + html! { + div class="columns" { + div class="column" { + div class="box" { + h2 class="title c is-3" { "Check a Repository" } + + form id="repoSelect" action="#" { + div class="field" { + label class="label" { "Hosting Provider" } + + div class="control" { + div class="select" { + select id="hosterSelect" { + option { "Github" } + option { "Gitlab" } + option { "Bitbucket" } + option { "Sourcehut" } + option { "Codeberg" } + option { "Gitea" } + } + } + } + } + + div class="field" { + label class="label" { "Owner" } + + div class="control" { + input class="input" type="text" id="owner" placeholder="rust-lang" required; + } + } + + div class="field" { + label class="label" { "Repository Name" } + + div class="control" { + input class="input" type="text" id="repoName" placeholder="cargo" required; + } + } + + div class="field" { + label class="label" { "Git instance URL" } + + div class="control" { + input class="input" type="text" id="baseUrl" placeholder="gitea.com"; + } + + p class="help" id="baseUrlHelp" { "Base URL of the Git instance the project is hosted on. Only relevant for Gitea Instances." } + } + + input type="submit" class="button is-primary" value="Check" onclick="buildRepoLink();"; + } + } + } + div class="column" { + div class="box" { + h2 class="title is-3" { "Check a Crate" } + + form id="crateSelect" action="#" { + div class="field" { + label class="label" { "Crate Name" } + + div class="control" { + input class="input" type="text" id="crateName" placeholder="serde-derive" required; + } + } + + div class="field" { + label class="label" { "Version (optional)" } + + div class="control" { + input class="input" type="text" id="crateVersion" placeholder="1.0.0"; + } + + p class="help" { "If left blank, defaults to the latest version." } + } + + input type="submit" class="button is-primary" value="Check" onclick="buildCrateLink();"; + } + } + } + } + } +} + fn popular_table(popular_repos: Vec, popular_crates: Vec) -> Markup { html! { div class="columns" { @@ -83,7 +171,11 @@ pub fn render(popular_repos: Vec, popular_crates: Vec) -> section class="section" { div class="container" { (popular_table(popular_repos, popular_crates)) } } + section class="section" { + div class="container" { (link_forms()) } + } (super::render_footer(None)) + script src=(STATIC_LINKS_JS_PATH) {} }, ) }