cache-bust css via content-hash ?v; keycap button style
This commit is contained in:
parent
c3c9e08a9e
commit
55c6685df1
4 changed files with 47 additions and 14 deletions
43
src/main.rs
43
src/main.rs
|
|
@ -15,8 +15,27 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::OnceLock;
|
||||
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
||||
|
||||
/// Short content hash of the bundled CSS, used as a `?v=` cache-buster: a changed
|
||||
/// stylesheet gets a new URL (fetched fresh), an unchanged one stays cached.
|
||||
fn asset_ver() -> &'static str {
|
||||
static V: OnceLock<String> = OnceLock::new();
|
||||
V.get_or_init(|| {
|
||||
let mut h: u64 = 0xcbf29ce484222325;
|
||||
for b in include_str!("../static/style.css")
|
||||
.bytes()
|
||||
.chain(include_str!("../static/docs.css").bytes())
|
||||
{
|
||||
h ^= b as u64;
|
||||
h = h.wrapping_mul(0x100000001b3);
|
||||
}
|
||||
format!("{h:x}")
|
||||
})
|
||||
.as_str()
|
||||
}
|
||||
|
||||
/// Display-ready snapshot of the live network state for templates.
|
||||
struct Status {
|
||||
online: bool,
|
||||
|
|
@ -48,6 +67,7 @@ struct AppState {
|
|||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {
|
||||
active: &'static str,
|
||||
css_v: &'static str,
|
||||
status: Status,
|
||||
commits: Vec<commits::Commit>,
|
||||
}
|
||||
|
|
@ -56,18 +76,21 @@ struct IndexTemplate {
|
|||
#[template(path = "features.html")]
|
||||
struct FeaturesTemplate {
|
||||
active: &'static str,
|
||||
css_v: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "connect.html")]
|
||||
struct ConnectTemplate {
|
||||
active: &'static str,
|
||||
css_v: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "services.html")]
|
||||
struct ServicesTemplate {
|
||||
active: &'static str,
|
||||
css_v: &'static str,
|
||||
}
|
||||
|
||||
struct SidePage {
|
||||
|
|
@ -84,6 +107,7 @@ struct SideSection {
|
|||
#[template(path = "docs.html")]
|
||||
struct DocsTemplate {
|
||||
active: &'static str,
|
||||
css_v: &'static str,
|
||||
page_title: &'static str,
|
||||
body: &'static str,
|
||||
toc: &'static [docs::Heading],
|
||||
|
|
@ -104,16 +128,16 @@ fn page<T: Template>(t: T) -> Response {
|
|||
|
||||
async fn index(State(st): State<AppState>) -> Response {
|
||||
let commits = st.commits.read().ok().map(|g| g.clone()).unwrap_or_default();
|
||||
page(IndexTemplate { active: "home", status: view(&st.stats), commits })
|
||||
page(IndexTemplate { active: "home", css_v: asset_ver(), status: view(&st.stats), commits })
|
||||
}
|
||||
async fn features() -> Response {
|
||||
page(FeaturesTemplate { active: "features" })
|
||||
page(FeaturesTemplate { active: "features", css_v: asset_ver() })
|
||||
}
|
||||
async fn connect() -> Response {
|
||||
page(ConnectTemplate { active: "connect" })
|
||||
page(ConnectTemplate { active: "connect", css_v: asset_ver() })
|
||||
}
|
||||
async fn services() -> Response {
|
||||
page(ServicesTemplate { active: "services" })
|
||||
page(ServicesTemplate { active: "services", css_v: asset_ver() })
|
||||
}
|
||||
|
||||
async fn docs_index() -> Response {
|
||||
|
|
@ -137,6 +161,7 @@ async fn docs_page(Path(slug): Path<String>) -> Response {
|
|||
let (prev, next) = docs::neighbours(&slug);
|
||||
page(DocsTemplate {
|
||||
active: "docs",
|
||||
css_v: asset_ver(),
|
||||
page_title: p.title,
|
||||
body: &p.html,
|
||||
toc: &p.toc,
|
||||
|
|
@ -154,13 +179,19 @@ async fn docs_search() -> impl IntoResponse {
|
|||
|
||||
async fn style() -> impl IntoResponse {
|
||||
(
|
||||
[(header::CONTENT_TYPE, "text/css; charset=utf-8")],
|
||||
[
|
||||
(header::CONTENT_TYPE, "text/css; charset=utf-8"),
|
||||
(header::CACHE_CONTROL, "public, max-age=31536000, immutable"),
|
||||
],
|
||||
include_str!("../static/style.css"),
|
||||
)
|
||||
}
|
||||
async fn docs_css() -> impl IntoResponse {
|
||||
(
|
||||
[(header::CONTENT_TYPE, "text/css; charset=utf-8")],
|
||||
[
|
||||
(header::CONTENT_TYPE, "text/css; charset=utf-8"),
|
||||
(header::CACHE_CONTROL, "public, max-age=31536000, immutable"),
|
||||
],
|
||||
include_str!("../static/docs.css"),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* echoIRCd — teal is echo's own colour. Light by default; dark via [data-theme]. */
|
||||
:root{
|
||||
--teal:#12a594; --teal-d:#0e8577; --teal-dd:#0a6a5f; --teal-t:#e7f6f3;
|
||||
--teal:#12a594; --teal-d:#0e8577; --teal-dd:#0a6a5f; --teal-t:#e7f6f3; --teal-edge:#0a6a5f;
|
||||
--ink:#1b2733; --muted:#5c6773; --faint:#8b95a0;
|
||||
--line:#e4e8eb; --line2:#d8dee2; --bg:#f5f6f7; --card:#ffffff; --panel:#fbfcfd;
|
||||
--code-bg:#132430; --code-fg:#e7edf0; --cmd:#6fe0cf;
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
--wrap:1080px;
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--teal:#15b6a3; --teal-d:#48d8c4; --teal-dd:#7de9db; --teal-t:#0f2d29;
|
||||
--teal:#15b6a3; --teal-d:#48d8c4; --teal-dd:#7de9db; --teal-t:#0f2d29; --teal-edge:#0b8474;
|
||||
--ink:#e3ece8; --muted:#93a8a1; --faint:#66807a;
|
||||
--line:#1b2e29; --line2:#26403a; --bg:#0b1614; --card:#132622; --panel:#0f1e1b;
|
||||
--purple-line:#4f5a7a; --purple-fg:#bfc7ee; --purple-bg:#182034; --bq-fg:#8fe6d8;
|
||||
|
|
@ -36,12 +36,14 @@ code{font-family:var(--mono);font-size:.9em;background:rgba(18,165,148,.09);colo
|
|||
.mono-line{font-family:var(--mono);color:var(--muted)}
|
||||
|
||||
/* buttons */
|
||||
.btn{display:inline-block;font-weight:600;font-size:.95rem;padding:.6rem 1.25rem;border-radius:6px;border:1.5px solid transparent;transition:background .14s ease,border-color .14s ease,color .14s ease}
|
||||
.btn{display:inline-flex;align-items:center;gap:.45rem;font-weight:600;font-size:.95rem;letter-spacing:.005em;padding:.58rem 1.25rem;border-radius:5px;border:1.5px solid transparent;cursor:pointer;transition:background .14s ease,border-color .14s ease,color .14s ease,box-shadow .09s ease,transform .09s ease}
|
||||
.btn:hover{text-decoration:none}
|
||||
.btn-primary{background:var(--teal);border-color:var(--teal);color:#fff}
|
||||
.btn-primary{background:var(--teal);border-color:var(--teal);color:#fff;box-shadow:0 3px 0 var(--teal-edge),inset 0 1px 0 rgba(255,255,255,.2)}
|
||||
.btn-primary:hover{background:var(--teal-d);border-color:var(--teal-d)}
|
||||
.btn-ghost{border-color:var(--line2);color:var(--ink);background:transparent}
|
||||
.btn-ghost:hover{border-color:var(--teal);color:var(--teal-d);background:var(--teal-t)}
|
||||
.btn-primary:active{transform:translateY(2px);box-shadow:0 1px 0 var(--teal-edge),inset 0 1px 0 rgba(255,255,255,.15)}
|
||||
.btn-ghost{border-color:var(--teal);color:var(--teal-d);background:transparent}
|
||||
.btn-ghost:hover{background:var(--teal-t);color:var(--teal-dd)}
|
||||
.btn-ghost:active{transform:translateY(1px)}
|
||||
|
||||
/* top bar (teal in both themes) */
|
||||
.topbar{background:var(--teal);color:#fff;position:sticky;top:0;z-index:20;box-shadow:0 2px 12px -6px rgba(0,0,0,.35)}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v={{ css_v }}">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}{{ page_title }} — echoIRCd Docs{% endblock %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/docs.css">
|
||||
<link rel="stylesheet" href="/static/docs.css?v={{ css_v }}">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue