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, default_image: Option) -> 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 { let mut backlinks = vec![("/", "home")]; let mut segments = ctx.route.split("/").skip(1).collect::>(); 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 = 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 = LazyLock::new(|| { page_from_markdown(include_str!("betalupi.md"), Some(IMG_ICON.route().into())) }); pub const HTWAH_TYPESETTING: LazyLock = LazyLock::new(|| { page_from_markdown( include_str!("htwah-typesetting.md"), Some(IMG_ICON.route().into()), ) }); pub static NOT_FOUND: LazyLock = 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) });