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`
Diff
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(-)
@@ -2171,6 +2171,7 @@ dependencies = [
"humantime",
"itertools",
"md5",
"mime_guess",
"moka",
"nom",
"once_cell",
@@ -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]
@@ -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
@@ -97,6 +97,17 @@ pub struct AppConfig {
pub css: Option<String>,
pub header: Option<HeaderStyle>,
}
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
}
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)]
pub enum HeaderStyle {
#[default]
Default,
Text,
Image,
}
impl<'de> Deserialize<'de> for HeaderStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let variant: &str = Deserialize::deserialize(deserializer)?;
match variant.to_lowercase().as_str() {
"default" => Ok(HeaderStyle::Default),
"text" => Ok(HeaderStyle::Text),
"image" => Ok(HeaderStyle::Image),
_ => Ok(HeaderStyle::Default),
}
}
}
@@ -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()
});
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<A: IntoResponse, B: IntoResponse> IntoResponse for ResponseEither<A, B> {
}
}
}
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)
}
@@ -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;
}
}
}
@@ -13,19 +13,31 @@
</head>
<body>
{% let header = config.header.clone().unwrap_or_default() %}
{% if header == crate::configuration::HeaderStyle::Default %}
<header>
<h1>
<a href="{{ config.logo_link }}" class="no-hover">
{% if config.logo.is_empty() %}
{{ config.logo_alt }}
{% else %}
<img src="{{ config.logo }}" alt="{{ config.logo_alt }}">
{% endif %}
</a>
{{ config.logo }}
</a>
{% block header -%}{{ config.root_title }}{%- endblock %}
</h1>
</header>
{% else %}
<header class="full">
{% if header == crate::configuration::HeaderStyle::Image %}
<a class="logo no-hover" href="{{ config.logo_link }}">
<img src="/{{ crate::get_logo_filename(config.logo.clone()) }}" alt="{{ config.logo_alt }}">
</a>
{% endif %}
<hgroup>
<h1>
{% block header -%}{{ config.root_title }}{%- endblock %}
</h1>
<p>{{ config.root_description }}</p>
</hgroup>
</header>
{% endif %}
{%- block nav -%}
<nav>
<div>