Files
webpage/crates/service/service-webpage/src/pages/mod.rs
rm-dr a29db858d1
Some checks failed
CI / Check typos (push) Successful in 15s
CI / Check links (push) Failing after 1m37s
CI / Clippy (push) Successful in 3m44s
CI / Build and test (push) Successful in 12m33s
CI / Build container (push) Successful in 10m24s
CI / Deploy on waypoint (push) Successful in 49s
Migrate to servable
2025-11-27 21:07:52 -08:00

213 lines
4.4 KiB
Rust

use chrono::TimeDelta;
use maud::{Markup, PreEscaped, html};
use reqwest::StatusCode;
use servable::{HtmlPage, PageMetadata, RenderContext};
use std::sync::LazyLock;
use crate::{
components::{
fa::FAIcon,
md::{Markdown, meta_from_markdown},
misc::FarLink,
},
routes::{IMG_ICON, MAIN_CSS},
};
mod handouts;
mod index;
pub use handouts::HANDOUTS;
pub use index::INDEX;
//
// MARK: md
//
fn page_from_markdown(md: impl Into<String>, default_image: Option<String>) -> HtmlPage {
let md: String = md.into();
let md = Markdown::parse(&md);
let mut meta = meta_from_markdown(&md)
.unwrap_or(Some(PageMetadata {
title: "Invalid frontmatter!".into(),
..Default::default()
}))
.unwrap_or_default();
if meta.image.is_none() {
meta.image = default_image
}
let html = PreEscaped(md.render());
HtmlPage::default()
.with_script_inline(LAZY_IMAGE_JS)
.with_style_linked(MAIN_CSS.route())
.with_meta(meta)
.with_render(move |_page, ctx| {
let html = html.clone();
Box::pin(async move {
html! {
div class="wrapper" style="margin-top:3ex;" {
@if let Some(backlinks) = backlinks(ctx) {
(backlinks)
}
(html)
(footer())
}
}
})
})
.with_ttl(Some(TimeDelta::days(1)))
}
//
// MARK: components
//
const LAZY_IMAGE_JS: &str = "
window.onload = function() {
var imgs = document.querySelectorAll('.img-placeholder');
imgs.forEach(img => {
img.style.border = 'none';
img.style.filter = 'blur(10px)';
img.style.transition = 'filter 0.3s';
var lg = new Image();
lg.src = img.dataset.large;
lg.onload = function () {
img.src = img.dataset.large;
img.style.filter = 'blur(0px)';
};
})
}
";
/*
const MAIN_TEMPLATE: PageTemplate = PageTemplate {
// Order matters, base htmx goes first
scripts: &[
ScriptSource::Linked(&"/assets/htmx-2.0.8.js"),
ScriptSource::Linked(&"/assets/htmx-json-1.19.12.js"),
],
extra_meta: &[(
"viewport",
"width=device-width,initial-scale=1,user-scalable=no",
)],
};
*/
pub fn backlinks(ctx: &RenderContext) -> Option<Markup> {
let mut backlinks = vec![("/", "home")];
let mut segments = ctx.route.split("/").skip(1).collect::<Vec<_>>();
let last = segments.pop();
let mut end = 0;
for s in segments {
end += s.len();
backlinks.push((&ctx.route[0..=end], s));
end += 1; // trailing slash
}
last.map(|last| {
html! {
div {
@for (url, text) in backlinks {
a href=(url) style="padding-left:5pt;padding-right:5pt;" { (text) }
"/"
}
span style="color:var(--metaColor);padding-left:5pt;padding-right:5pt;" { (last) }
}
}
})
}
pub fn footer() -> Markup {
html!(
footer style="margin-top:10rem;" {
hr class = "footline";
div class = "footContainer" {
p {
"This site was built by hand with "
(FarLink("https://rust-lang.org", "Rust"))
", "
(FarLink("https://maud.lambda.xyz", "Maud"))
", and "
(FarLink("https://docs.rs/axum/latest/axum", "Axum"))
". "
(
FarLink(
"https://git.betalupi.com/Mark/webpage",
html!(
(FAIcon::Git)
"Source here!"
)
)
)
}
}
}
)
}
//
// MARK: pages
//
pub const LINKS: LazyLock<HtmlPage> = LazyLock::new(|| {
/*
Dead links:
https://www.commitstrip.com/en/
http://www.3dprintmath.com/
*/
page_from_markdown(include_str!("links.md"), Some(IMG_ICON.route().into()))
});
pub const BETALUPI: LazyLock<HtmlPage> = LazyLock::new(|| {
page_from_markdown(include_str!("betalupi.md"), Some(IMG_ICON.route().into()))
});
pub const HTWAH_TYPESETTING: LazyLock<HtmlPage> = LazyLock::new(|| {
page_from_markdown(
include_str!("htwah-typesetting.md"),
Some(IMG_ICON.route().into()),
)
});
pub static NOT_FOUND: LazyLock<HtmlPage> = LazyLock::new(|| {
HtmlPage::default()
.with_style_linked(MAIN_CSS.route())
.with_meta(PageMetadata {
title: "Page not found".into(),
author: None,
description: None,
image: Some(IMG_ICON.route().into()),
})
.with_render(
move |_page, _ctx| {
Box::pin(async {
html! {
div class="wrapper" {
div style="display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh" {
p style="font-weight:bold;font-size:50pt;margin:0;" { "404" }
p style="font-size:13pt;margin:0;color:var(--grey);" { "(page not found)" }
a style="font-size:12pt;margin:10pt;padding:5px;" href="/" {"<- Back to site"}
}
}
}
})
}
)
.with_code(StatusCode::NOT_FOUND)
});