From 523ae871d8f66c88952eb457b7a275b26dca5950 Mon Sep 17 00:00:00 2001 From: holly sparkles Date: Tue, 2 Jan 2024 20:56:09 +0100 Subject: [PATCH] feat!: add new header styles - Default: same as previous versions of rgit - Text: text and subtitle only, no image or emoji - Image: image, text, and subtitle also utilises `root_description` in the configuration BREAKING CHANGE: this breaks previous use of `logo` and `logo_alt` --- Cargo.lock | 1 + Cargo.toml | 1 + rgit.conf.example | 8 +++++--- src/configuration/mod.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ statics/sass/style.scss | 29 +++++++++++++++++++++++++++++ templates/base.html | 26 +++++++++++++++++++------- 7 files changed, 139 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 881531e..7001daf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2171,6 +2171,7 @@ dependencies = [ "humantime", "itertools", "md5", + "mime_guess", "moka", "nom", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index d77931f..fe8c4a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ yoke = { version = "0.7.1", features = ["derive"] } rand = "0.8.5" confy = "0.5.1" rsass = "0.28.0" +mime_guess = "2.0.4" [build-dependencies] diff --git a/rgit.conf.example b/rgit.conf.example index 3479f0b..3ea3274 100644 --- a/rgit.conf.example +++ b/rgit.conf.example @@ -12,9 +12,9 @@ snapshots = [ ] # Add a favicon favicon = '/config/favicon.ico' -# Use a custom logo image +# Specify a path to a logo image, or an emoji (eg. '🏔️') logo = '/config/logo.png' -# Alt text for the custom logo image +# Alt text for the logo image logo_alt = '🏔️' # Url loaded when clicking on the logo logo_link = '/' @@ -33,4 +33,6 @@ max_commit_message_length = 80 # Limit length of repository descriptions; use 0 for no limit max_repo_desc_length = 80 # Use custom css on all pages -css = "/custom.scss" \ No newline at end of file +css = "/custom.scss" +# Change the header style +header = "Image" \ No newline at end of file diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs index 68be7c1..7305fb2 100644 --- a/src/configuration/mod.rs +++ b/src/configuration/mod.rs @@ -97,6 +97,17 @@ pub struct AppConfig { /// /// Default value: `None` pub css: Option, + + /// Specifies the style of header to use. + /// + /// `Default` is the default style of an emoji and title text + /// + /// `Text` is only the title text and a subtitle (if applicable) + /// + /// `Image` is an image, title text, and a subtitle (if applicable) + /// + /// Default value: `Default` + pub header: Option, } impl ::std::default::Default for AppConfig { @@ -111,8 +122,8 @@ impl ::std::default::Default for AppConfig { String::from("zip"), ], favicon: String::new(), - logo: String::new(), - logo_alt: String::from("🏡"), + logo: String::from("🏡"), + logo_alt: String::new(), logo_link: String::from("/"), enable_http_clone: true, enable_index_links: false, @@ -122,6 +133,7 @@ impl ::std::default::Default for AppConfig { max_commit_message_length: 0, max_repo_desc_length: 0, css: None, + header: Some(HeaderStyle::default()), } } } @@ -183,3 +195,31 @@ impl ReadmeConfig { options } } + +/// Represents available header styles for rendering the frontend. +#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)] +pub enum HeaderStyle { + /// Emoji logo, text title + #[default] + Default, + /// Text title, text subtitle + Text, + /// Image logo, text title, text subtitle + Image, +} + +impl<'de> Deserialize<'de> for HeaderStyle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let variant: &str = Deserialize::deserialize(deserializer)?; + // Support case insensitivity + match variant.to_lowercase().as_str() { + "default" => Ok(HeaderStyle::Default), + "text" => Ok(HeaderStyle::Text), + "image" => Ok(HeaderStyle::Image), + _ => Ok(HeaderStyle::Default), + } + } +} diff --git a/src/main.rs b/src/main.rs index 71eb480..59da25a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,6 +201,35 @@ async fn main() -> Result<(), anyhow::Error> { } }; + let static_file = |file: String| { + move || async move { + let mime_type = mime_guess::from_path(&file) + .first() + .map_or("text/plain".to_string(), |mime| { + mime.essence_str().to_string() + }); + + let content = std::fs::read(&PathBuf::from(&file)).unwrap_or_else(|_| { + if file.is_empty() { + warn!("Requested filename is blank."); + } + error!("Unable to load file: {}", file); + Vec::new() + }); + + //TODO: create a 410 (gone) response here when content is empty or file is invalid. + let mut resp = Response::new(Body::from(content)); + resp.headers_mut().insert( + http::header::CONTENT_TYPE, + HeaderValue::from_str(&mime_type).unwrap_or_else(|_| { + warn!("Invalid MIME type: {} for file: {}", mime_type, file); + HeaderValue::from_static("text/plain") + }), + ); + resp + } + }; + let config = configuration::AppConfig::load(args.config); let app = Router::new() @@ -226,6 +255,10 @@ async fn main() -> Result<(), anyhow::Error> { ) .route("/favicon.ico", get(static_favicon(config.favicon.clone()))) .route("/about", get(methods::about::handle)) + .route( + &format!("/{}", get_logo_filename(config.logo.clone())), + get(static_file(config.logo.clone())), + ) .fallback(methods::repo::service) .layer(TimeoutLayer::new(args.request_timeout.into())) .layer(layer_fn(LoggingMiddleware)) @@ -392,3 +425,12 @@ impl IntoResponse for ResponseEither { } } } + +pub fn get_logo_filename(filename: String) -> String { + let extension = PathBuf::from(filename) + .extension() + .unwrap_or_else(|| std::ffi::OsStr::new("png")) + .to_string_lossy() + .to_string(); + format!("logo.{}", extension) +} diff --git a/statics/sass/style.scss b/statics/sass/style.scss index 0be2ab2..9702da1 100644 --- a/statics/sass/style.scss +++ b/statics/sass/style.scss @@ -104,3 +104,32 @@ a { font-size: 80%; padding: 0em 0.5em; } + +header.full { + border: none; + display: flex; + + hgroup { + width: 100%; + vertical-align: middle; + h1 { + border-bottom: solid 1px #ccc; + margin-bottom: 0rem; + padding-bottom: 0.2rem; + } + p { + margin-top: 0rem; + padding-top: 0.2rem; + } + } + + a.logo { + margin: auto 0.9rem auto 0; + font-size: 4rem; + + img { + max-height: 5rem; + vertical-align: middle; + } + } +} diff --git a/templates/base.html b/templates/base.html index 6387c8d..2b911a8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -13,19 +13,31 @@ +{% let header = config.header.clone().unwrap_or_default() %} +{% if header == crate::configuration::HeaderStyle::Default %}

- {% if config.logo.is_empty() %} - {{ config.logo_alt }} - {% else %} - {{ config.logo_alt }} - {% endif %} - + {{ config.logo }} + {% block header -%}{{ config.root_title }}{%- endblock %}

- +{% else %} +
+ {% if header == crate::configuration::HeaderStyle::Image %} + + {% endif %} +
+

+ {% block header -%}{{ config.root_title }}{%- endblock %} +

+

{{ config.root_description }}

+
+
+{% endif %} {%- block nav -%}