Commit
Message
Changed Files (8)
-
modified src/cli.rs
diff --git a/src/cli.rs b/src/cli.rs index 5a1e845..882dc03 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -22,4 +22,6 @@ pub enum CliCommand { }, /// Build the site Build, + /// Dumps the default theme to `.abbaye/theme` so you can inspect and modify it. It will be used afterwards when building the site. + DumpTheme, } -
modified src/config.rs
diff --git a/src/config.rs b/src/config.rs index ba7d4fc..900accb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,10 @@ use std::path::PathBuf; +use figment::{ + Figment, + providers::{Format, Toml}, +}; +use miette::{IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use crate::{ @@ -21,6 +26,9 @@ pub struct SiteConfig { /// URL of the project's repository (e.g. `"https://git.sr.ht/~ololduck/abbaye"`). /// When set, we will show the repository link in the generated website. pub repo_url: Option<String>, + /// An optional language code for the website (e.g. `"en"` or `"fr"`). + /// When set, the generated HTML will include a `lang` attribute on the `<html>` tag. Defaults to `"en"`. + pub lang: Option<String>, } /// A full configuration for the Abbaye site generator. @@ -56,10 +64,24 @@ pub struct AbbayeConfig { pub version_extractor: AnyVersionExtractor, pub changelog: ChangelogConfig, pub builders: Vec<AnyBuilder>, - #[serde(default = "abbaye2_output_dir")] + #[serde(default = "abbaye_output_dir")] pub output_dir: Option<PathBuf>, } -fn abbaye2_output_dir() -> Option<PathBuf> { +fn abbaye_output_dir() -> Option<PathBuf> { Some(PathBuf::from("public")) } + +/// Load the Abbaye2 configuration from the current working directory. +/// +/// Looks for `.abbaye.toml` first, then `abbaye.toml`; when both are present +/// `abbaye.toml` takes precedence (last merge wins). +pub fn load_config() -> Result<AbbayeConfig> { + let cwd = std::env::current_dir().into_diagnostic()?; + Figment::new() + .merge(Toml::file(cwd.join(".abbaye.toml"))) + .merge(Toml::file(cwd.join("abbaye.toml"))) + .merge(Toml::file(cwd.join(".abbaye").join("abbaye.toml"))) + .extract() + .into_diagnostic() +} -
modified src/main.rs
diff --git a/src/main.rs b/src/main.rs index e90a06c..fc1d2b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,9 @@ //! //! ## Why ? //! -//! This piece of software is for people that can't or won't use a full-featured forge such as GitHub, GitLab, ForgeJo & others. These forges provide "release pages" that allow you to upload and distribute your software, as well as get a changelog. +//! This piece of software is for people that can't or won't use a full-featured forge such as GitHub, GitLab, ForgeJo & others. +//! These forges provide "release pages" that allow you to upload and distribute your software, as well as get a changelog. +//! //! Abbaye is made to be a simple, lightweight alternative to these forges, for the release/documentation parts. //! //! ### Why "Abbaye" ? @@ -88,10 +90,19 @@ //! Then run `abbaye build` to build the site. The site will be generated in the `public/` directory by default. //! Now you can copy the contents of `public/` to your web server to deploy the site. For instance, with rsync: //! `rsync --progress -avz --links --perms --update public/ ololduck@vit.am:public_html/abbaye/` +//! +//! ## Contributing +//! +//! Contributions are welcome! As i am mainly a rust developer, i am open to any contributions that improve the project, especially to support more artifacts builders/types. +//! +//! Just clone the repository and {send me an email,contact me on {IRC (ololduck@irc.libera.chat),the Fediverse (@ololduck@fosstodon.org)}} with {a link to your fork,a git patch,compliments and adoration}. + +use std::path::PathBuf; use clap::Parser; use human_panic::setup_panic; use miette::{IntoDiagnostic, Result}; +use tokio::fs::create_dir_all; use crate::{ builders::{AnyBuilder, archive::ArchiveBuilderConfig}, @@ -140,6 +151,7 @@ async fn main() -> Result<()> { readme: None, base_url: None, repo_url: None, + lang: None, }, version_extractor: AnyVersionExtractor::Git(GitVersionConfig { tag_prefix: Some("v".to_string()), @@ -158,12 +170,30 @@ async fn main() -> Result<()> { }; let config_path = base_path.join("abbaye.toml"); let toml = toml::to_string_pretty(&abbaye_config).into_diagnostic()?; - std::fs::write(&config_path, toml).into_diagnostic()?; + tokio::fs::write(&config_path, toml) + .await + .into_diagnostic()?; } cli::CliCommand::Build => { - let config = site::load_config()?; + let config = config::load_config()?; site::build_site(config).await?; } + cli::CliCommand::DumpTheme => { + let theme_path = PathBuf::from(".abbaye").join("theme"); + create_dir_all(&theme_path).await.into_diagnostic()?; + tokio::fs::write( + theme_path.join("root_index.html.j2"), + site::TEMPLATE_ROOT_INDEX, + ) + .await + .into_diagnostic()?; + tokio::fs::write( + theme_path.join("version_index.html.j2"), + site::TEMPLATE_VERSION_INDEX, + ) + .await + .into_diagnostic()?; + } } Ok(()) } -
modified src/site.rs
diff --git a/src/site.rs b/src/site.rs index 3f52a83..6dc1421 100644 --- a/src/site.rs +++ b/src/site.rs @@ -7,10 +7,6 @@ use std::{ use chrono::{DateTime, SecondsFormat, Utc}; use sha2::{Digest, Sha256}; -use figment::{ - Figment, - providers::{Format, Toml}, -}; use flate2::{Compression, write::GzEncoder}; use miette::{IntoDiagnostic, Result}; use pulldown_cmark::{Options, Parser, html}; @@ -19,8 +15,8 @@ use tracing::warn; use crate::{changelog::ChangelogExtractor, config::AbbayeConfig, version_extractors::VersionInfo}; -const TEMPLATE_ROOT_INDEX: &str = include_str!("templates/root_index.html"); -const TEMPLATE_VERSION_INDEX: &str = include_str!("templates/version_index.html"); +pub const TEMPLATE_ROOT_INDEX: &str = include_str!("templates/root_index.html.j2"); +pub const TEMPLATE_VERSION_INDEX: &str = include_str!("templates/version_index.html.j2"); const ATOM_FEED_FILENAME: &str = "releases.atom"; // ── Types ─────────────────────────────────────────────────────────────────── @@ -56,21 +52,6 @@ struct DistFileInfo { sha256: String, } -// ── Public API ──────────────────────────────────────────────────────────────── - -/// Load the Abbaye2 configuration from the current working directory. -/// -/// Looks for `.abbaye.toml` first, then `abbaye.toml`; when both are present -/// `abbaye.toml` takes precedence (last merge wins). -pub fn load_config() -> Result<AbbayeConfig> { - let cwd = std::env::current_dir().into_diagnostic()?; - Figment::new() - .merge(Toml::file(cwd.join(".abbaye.toml"))) - .merge(Toml::file(cwd.join("abbaye.toml"))) - .extract() - .into_diagnostic() -} - /// Build the full website into `config.output_dir` (defaults to `public/`). /// /// # Steps @@ -102,10 +83,27 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { // ── 2. Tera setup ───────────────────────────────────────────────────────── let mut tera = Tera::default(); - tera.add_raw_template("root_index.html", TEMPLATE_ROOT_INDEX) + let theme_path = PathBuf::from(".abbaye").join("theme"); + if theme_path.join("root_index.html.j2").is_file() { + tera.add_template_file( + theme_path.join("root_index.html.j2"), + Some("root_index.html"), + ) .into_diagnostic()?; - tera.add_raw_template("version_index.html", TEMPLATE_VERSION_INDEX) + } else { + tera.add_raw_template("root_index.html", TEMPLATE_ROOT_INDEX) + .into_diagnostic()?; + } + if theme_path.join("version_index.html.j2").is_file() { + tera.add_template_file( + theme_path.join("version_index.html.j2"), + Some("version_index.html"), + ) .into_diagnostic()?; + } else { + tera.add_raw_template("version_index.html", TEMPLATE_VERSION_INDEX) + .into_diagnostic()?; + } // ── 3. Builders ─────────────────────────────────────────────────────────── let mut dist_artifacts = Vec::new(); @@ -213,6 +211,7 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { // ── 8. Version index.html ───────────────────────────────────────────────── let mut version_ctx = Context::new(); version_ctx.insert("project_name", &config.site.name); + version_ctx.insert("lang", &config.site.lang); version_ctx.insert("repo_url", &config.site.repo_url); version_ctx.insert("version", &version); version_ctx.insert("readme_html", &readme_html); @@ -250,6 +249,7 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { let mut root_ctx = Context::new(); root_ctx.insert("project_name", &config.site.name); + root_ctx.insert("lang", &config.site.lang); root_ctx.insert("repo_url", &config.site.repo_url); root_ctx.insert("versions", &version_entries); root_ctx.insert("atom_feed", ATOM_FEED_FILENAME); -
deleted src/templates/root_index.html
diff --git a/src/templates/root_index.html b/src/templates/root_index.html deleted file mode 100644 index fcedc06..0000000 --- a/src/templates/root_index.html +++ /dev/null @@ -1,157 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>{{ project_name }}</title> - <link - rel="alternate" - type="application/atom+xml" - title="{{ project_name }} Releases" - href="{{ atom_feed }}" - /> - <style> - *, - *::before, - *::after { - box-sizing: border-box; - margin: 0; - padding: 0; - } - body { - font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - background: #f5efe4; - color: #2e2416; - line-height: 1.6; - } - header { - background: #3d5732; - color: #f0e8d8; - padding: 1.5rem 2rem; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - } - header h1 { - font-size: 1.75rem; - font-weight: 700; - letter-spacing: 0.01em; - } - .feed-link { - display: flex; - align-items: center; - gap: 0.4em; - color: #f0e8d8; - text-decoration: none; - font-size: 0.85rem; - opacity: 0.8; - } - .feed-link:hover { - opacity: 1; - text-decoration: underline; - } - .feed-icon { - /* Classic orange RSS/Atom square */ - display: inline-block; - width: 1em; - height: 1em; - flex-shrink: 0; - background: #f96b15; - border-radius: 2px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='3' cy='13' r='2' fill='white'/%3E%3Cpath d='M3 6.5A6.5 6.5 0 0 1 9.5 13' stroke='white' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3Cpath d='M3 2A11 11 0 0 1 14 13' stroke='white' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: center; - background-size: 80%; - } - - main { - max-width: 760px; - margin: 2.5rem auto; - padding: 0 1.5rem; - } - h2 { - font-size: 0.75rem; - font-weight: 700; - color: #7a6855; - text-transform: uppercase; - letter-spacing: 0.12em; - margin-bottom: 1rem; - } - ul { - list-style: none; - } - li { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 1rem; - background: #fdfaf5; - border: 1px solid #c9baa8; - border-radius: 5px; - margin-bottom: 0.5rem; - } - li:hover { - border-color: #a06020; - } - a.version-link { - font-size: 1.05rem; - font-weight: 500; - color: #7a4429; - text-decoration: none; - } - a.version-link:hover { - text-decoration: underline; - color: #a05a3a; - } - .badge-latest { - font-size: 0.65rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.07em; - background: #a06020; - color: #fdf6ec; - padding: 0.2em 0.6em; - border-radius: 999px; - } - .version-date { - margin-left: auto; - font-size: 0.8rem; - color: #8a7060; - font-variant-numeric: tabular-nums; - } - </style> - </head> - <body> - <header> - <h1>{{ project_name }}</h1> - <a class="feed-link" href="{{ atom_feed }}"> - <span class="feed-icon" aria-hidden="true"></span> - Atom feed - </a> - </header> - <main> - <h2>Versions</h2> - <ul> - {% for v in versions %} - <li> - <a class="version-link" href="{{ v.version }}/" - >{{ v.version }}</a - > - {% if loop.first %}<span class="badge-latest">latest</span - >{% endif %} {% if v.date %}<time - class="version-date" - datetime="{{ v.date }}" - >{{ v.date }}</time - >{% endif %} - </li> - {% endfor %} - </ul> - </main> - </body> -</html> -
added src/templates/root_index.html.j2
diff --git a/src/templates/root_index.html.j2 b/src/templates/root_index.html.j2 new file mode 100644 index 0000000..b209fb1 --- /dev/null +++ b/src/templates/root_index.html.j2 @@ -0,0 +1,157 @@ +<!doctype html> +<html lang="{{lang|default(value='en')}}"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>{{ project_name }}</title> + <link + rel="alternate" + type="application/atom+xml" + title="{{ project_name }} Releases" + href="{{ atom_feed }}" + /> + <style> + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + body { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + background: #f5efe4; + color: #2e2416; + line-height: 1.6; + } + header { + background: #3d5732; + color: #f0e8d8; + padding: 1.5rem 2rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + } + header h1 { + font-size: 1.75rem; + font-weight: 700; + letter-spacing: 0.01em; + } + .feed-link { + display: flex; + align-items: center; + gap: 0.4em; + color: #f0e8d8; + text-decoration: none; + font-size: 0.85rem; + opacity: 0.8; + } + .feed-link:hover { + opacity: 1; + text-decoration: underline; + } + .feed-icon { + /* Classic orange RSS/Atom square */ + display: inline-block; + width: 1em; + height: 1em; + flex-shrink: 0; + background: #f96b15; + border-radius: 2px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='3' cy='13' r='2' fill='white'/%3E%3Cpath d='M3 6.5A6.5 6.5 0 0 1 9.5 13' stroke='white' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3Cpath d='M3 2A11 11 0 0 1 14 13' stroke='white' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 80%; + } + + main { + max-width: 760px; + margin: 2.5rem auto; + padding: 0 1.5rem; + } + h2 { + font-size: 0.75rem; + font-weight: 700; + color: #7a6855; + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 1rem; + } + ul { + list-style: none; + } + li { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #fdfaf5; + border: 1px solid #c9baa8; + border-radius: 5px; + margin-bottom: 0.5rem; + } + li:hover { + border-color: #a06020; + } + a.version-link { + font-size: 1.05rem; + font-weight: 500; + color: #7a4429; + text-decoration: none; + } + a.version-link:hover { + text-decoration: underline; + color: #a05a3a; + } + .badge-latest { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + background: #a06020; + color: #fdf6ec; + padding: 0.2em 0.6em; + border-radius: 999px; + } + .version-date { + margin-left: auto; + font-size: 0.8rem; + color: #8a7060; + font-variant-numeric: tabular-nums; + } + </style> + </head> + <body> + <header> + <h1>{{ project_name }}</h1> + <a class="feed-link" href="{{ atom_feed }}"> + <span class="feed-icon" aria-hidden="true"></span> + Atom feed + </a> + </header> + <main> + <h2>Versions</h2> + <ul> + {% for v in versions %} + <li> + <a class="version-link" href="{{ v.version }}/" + >{{ v.version }}</a + > + {% if loop.first %}<span class="badge-latest">latest</span + >{% endif %} {% if v.date %}<time + class="version-date" + datetime="{{ v.date }}" + >{{ v.date }}</time + >{% endif %} + </li> + {% endfor %} + </ul> + </main> + </body> +</html> -
deleted src/templates/version_index.html
diff --git a/src/templates/version_index.html b/src/templates/version_index.html deleted file mode 100644 index 3939b12..0000000 --- a/src/templates/version_index.html +++ /dev/null @@ -1,268 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>{{ project_name }} — {{ version }}</title> - <style> - *, - *::before, - *::after { - box-sizing: border-box; - margin: 0; - padding: 0; - } - body { - font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - color: #2e2416; - line-height: 1.6; - } - /* ── Header ── */ - header { - background: #3d5732; - color: #f0e8d8; - padding: 0.75rem 1.5rem; - display: flex; - align-items: center; - gap: 1.5rem; - } - header a { - color: #c8bbaa; - text-decoration: none; - font-size: 0.9rem; - } - header a:hover { - text-decoration: underline; - color: #f0e8d8; - } - header .title { - font-weight: 600; - font-size: 1rem; - } - /* ── Layout ── */ - .layout { - display: flex; - min-height: calc(100vh - 44px); - } - /* ── Sidebar ── */ - aside { - width: 230px; - flex-shrink: 0; - background: #ede5d8; - border-right: 1px solid #c9baa8; - padding: 1.5rem 1rem; - font-size: 0.875rem; - } - aside h3 { - font-size: 0.65rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.12em; - color: #7a6855; - margin-top: 1.5rem; - margin-bottom: 0.5rem; - } - aside h3:first-child { - margin-top: 0; - } - aside ul { - list-style: none; - } - aside li { - padding: 0.25rem 0; - } - aside a { - color: #7a4429; - text-decoration: none; - } - aside a:hover { - text-decoration: underline; - color: #a05a3a; - } - /* ── Main content ── */ - main { - flex: 1; - padding: 2rem 2.5rem; - max-width: 860px; - overflow-x: hidden; - background: #fdfaf5; - } - /* ── Markdown typography ── */ - main h1, - main h2, - main h3, - main h4, - main h5, - main h6 { - margin-top: 1.5em; - margin-bottom: 0.5em; - line-height: 1.3; - color: #2e2416; - } - main h1 { - font-size: 1.9rem; - border-bottom: 2px solid #c9baa8; - padding-bottom: 0.4rem; - } - main h2 { - font-size: 1.4rem; - border-bottom: 1px solid #ddd4c4; - padding-bottom: 0.3rem; - } - main h3 { - font-size: 1.15rem; - } - main p { - margin-bottom: 1em; - } - main a { - color: #7a4429; - } - main a:hover { - color: #a05a3a; - } - main img { - max-width: 100%; - height: auto; - } - main pre { - background: #eae2d4; - border: 1px solid #c9baa8; - border-radius: 5px; - padding: 1em 1.25em; - overflow-x: auto; - margin-bottom: 1em; - font-size: 0.875em; - } - main code { - font-family: - "SFMono-Regular", Consolas, "Liberation Mono", Menlo, - monospace; - background: #eae2d4; - padding: 0.15em 0.4em; - border-radius: 3px; - font-size: 0.875em; - } - main pre code { - background: none; - padding: 0; - font-size: inherit; - } - main ul, - main ol { - padding-left: 1.5em; - margin-bottom: 1em; - } - main li { - margin-bottom: 0.2em; - } - main table { - border-collapse: collapse; - margin-bottom: 1em; - width: 100%; - } - main th, - main td { - border: 1px solid #c9baa8; - padding: 0.4em 0.75em; - text-align: left; - } - main th { - background: #ede5d8; - font-weight: 600; - } - main blockquote { - border-left: 4px solid #c9baa8; - padding: 0.5em 1em; - margin: 0 0 1em; - color: #7a6855; - font-style: italic; - } - /* ── Dist file metadata ── */ - .dist-meta { - font-size: 0.75rem; - color: #7a6855; - margin-top: 0.15rem; - word-break: break-all; - } - .dist-meta code { - font-family: - "SFMono-Regular", Consolas, "Liberation Mono", Menlo, - monospace; - font-size: 0.7rem; - } - /* ── Changelog section ── */ - .changelog-divider { - border: none; - border-top: 2px solid #c9baa8; - margin: 2.5rem 0; - } - .changelog-heading { - font-size: 1.3rem; - font-weight: 600; - color: #5a4030; - margin-bottom: 1rem; - } - /* ── Responsive ── */ - @media (max-width: 640px) { - .layout { - flex-direction: column; - } - aside { - width: 100%; - border-right: none; - border-bottom: 1px solid #c9baa8; - } - } - </style> - </head> - <body> - <header> - <a href="../">← All versions</a> - <span class="title">{{ project_name }} — {{ version }}</span> - </header> - <div class="layout"> - <aside> - {% if repo_url %} - <dl> - <dt>Repository</dt> - <dd><a href="{{ repo_url }}">{{ repo_url }}</a></dd> - </dl> - {% endif %} {% if has_docs %} - <h3>Documentation</h3> - <ul> - <li><a href="docs/">📖 API docs</a></li> - {% if has_docs_tarball %} - <li><a href="docs.tar.gz">⬇ docs.tar.gz</a></li> - {% endif %} - </ul> - {% endif %} {% if has_dist %} - <h3>Downloads</h3> - <ul> - {% for file in dist_files %} - <li> - <a href="dist/{{ file.name }}">⬇ {{ file.name }}</a> - <div class="dist-meta"> - {{ file.size_human }}<br /><code title="SHA-256" - >sha256: {{ file.sha256 }}</code - > - </div> - </li> - {% endfor %} - </ul> - {% endif %} - </aside> - <main> - {{ readme_html | safe }} - <hr class="changelog-divider" /> - <h2 class="changelog-heading">Changes in {{ version }}</h2> - {{ changelog_html | safe }} - </main> - </div> - </body> -</html> -
added src/templates/version_index.html.j2
diff --git a/src/templates/version_index.html.j2 b/src/templates/version_index.html.j2 new file mode 100644 index 0000000..ab578d4 --- /dev/null +++ b/src/templates/version_index.html.j2 @@ -0,0 +1,267 @@ +<!doctype html> +<html lang="{{lang|default(value='en')}}"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>{{ project_name }} — {{ version }}</title> + <style> + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + body { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + color: #2e2416; + background: #f5efe4; + line-height: 1.6; + } + /* ── Header ── */ + header { + background: #3d5732; + color: #f0e8d8; + padding: 0.75rem 1.5rem; + display: flex; + align-items: center; + gap: 1.5rem; + } + header a { + color: #c8bbaa; + text-decoration: none; + font-size: 0.9rem; + } + header a:hover { + text-decoration: underline; + color: #f0e8d8; + } + header .title { + font-weight: 600; + font-size: 1rem; + } + /* ── Layout ── */ + .layout { + display: flex; + min-height: calc(100vh - 44px); + } + /* ── Sidebar ── */ + aside { + width: 230px; + flex-shrink: 0; + background: #ede5d8; + border-right: 1px solid #c9baa8; + padding: 1.5rem 1rem; + font-size: 0.875rem; + } + aside h3 { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.12em; + color: #7a6855; + margin-top: 1.5rem; + margin-bottom: 0.5rem; + } + aside h3:first-child { + margin-top: 0; + } + aside ul { + list-style: none; + } + aside li { + padding: 0.25rem 0; + } + aside a { + color: #7a4429; + text-decoration: none; + } + aside a:hover { + text-decoration: underline; + color: #a05a3a; + } + /* ── Main content ── */ + main { + flex: 1; + padding: 2rem 2.5rem; + max-width: 860px; + overflow-x: hidden; + background: #fdfaf5; + } + /* ── Markdown typography ── */ + main h1, + main h2, + main h3, + main h4, + main h5, + main h6 { + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.3; + color: #2e2416; + } + main h1 { + font-size: 1.9rem; + border-bottom: 2px solid #c9baa8; + padding-bottom: 0.4rem; + } + main h2 { + font-size: 1.4rem; + border-bottom: 1px solid #ddd4c4; + padding-bottom: 0.3rem; + } + main h3 { + font-size: 1.15rem; + } + main p { + margin-bottom: 1em; + } + main a { + color: #7a4429; + } + main a:hover { + color: #a05a3a; + } + main img { + max-width: 100%; + height: auto; + } + main pre { + background: #eae2d4; + border: 1px solid #c9baa8; + border-radius: 5px; + padding: 1em 1.25em; + overflow-x: auto; + margin-bottom: 1em; + font-size: 0.875em; + } + main code { + font-family: + "SFMono-Regular", Consolas, "Liberation Mono", Menlo, + monospace; + background: #eae2d4; + padding: 0.15em 0.4em; + border-radius: 3px; + font-size: 0.875em; + } + main pre code { + background: none; + padding: 0; + font-size: inherit; + } + main ul, + main ol { + padding-left: 1.5em; + margin-bottom: 1em; + } + main li { + margin-bottom: 0.2em; + } + main table { + border-collapse: collapse; + margin-bottom: 1em; + width: 100%; + } + main th, + main td { + border: 1px solid #c9baa8; + padding: 0.4em 0.75em; + text-align: left; + } + main th { + background: #ede5d8; + font-weight: 600; + } + main blockquote { + border-left: 4px solid #c9baa8; + padding: 0.5em 1em; + margin: 0 0 1em; + color: #7a6855; + font-style: italic; + } + /* ── Dist file metadata ── */ + .dist-meta { + font-size: 0.75rem; + color: #7a6855; + margin-top: 0.15rem; + word-break: break-all; + } + .dist-meta code { + font-family: + "SFMono-Regular", Consolas, "Liberation Mono", Menlo, + monospace; + font-size: 0.7rem; + } + /* ── Changelog section ── */ + .changelog-divider { + border: none; + border-top: 2px solid #c9baa8; + margin: 2.5rem 0; + } + .changelog-heading { + font-size: 1.3rem; + font-weight: 600; + color: #5a4030; + margin-bottom: 1rem; + } + /* ── Responsive ── */ + @media (max-width: 640px) { + .layout { + flex-direction: column; + } + aside { + width: 100%; + border-right: none; + border-bottom: 1px solid #c9baa8; + } + } + </style> + </head> + <body> + <header> + <a href="../">← All versions</a> + <span class="title">{{ project_name }} — {{ version }}</span> + </header> + <div class="layout"> + <aside> + {% if repo_url %} + <h3>Repository</h3> + <p><a href="{{ repo_url }}">{{ repo_url }}</a></p> + {% endif %} {% if has_docs %} + <h3>Documentation</h3> + <ul> + <li><a href="docs/">📖 API docs</a></li> + {% if has_docs_tarball %} + <li><a href="docs.tar.gz">⬇ docs.tar.gz</a></li> + {% endif %} + </ul> + {% endif %} {% if has_dist %} + <h3>Downloads</h3> + <ul> + {% for file in dist_files %} + <li> + <a href="dist/{{ file.name }}">⬇ {{ file.name }}</a> + <div class="dist-meta"> + {{ file.size_human }}<br /><code title="SHA-256" + >sha256: {{ file.sha256 }}</code + > + </div> + </li> + {% endfor %} + </ul> + {% endif %} + </aside> + <main> + {{ readme_html | safe }} {% if changelog_html %} + <hr class="changelog-divider" /> + <h2 class="changelog-heading">Changes in {{ version }}</h2> + {{ changelog_html | safe }} {% endif %} + </main> + </div> + </body> +</html>