use chrono::{Datelike, Utc};
use maud::{html, Markup, PreEscaped, Render};
use std::fs;
pub struct LogoLink<'a> {
pub url: &'a str,
pub logo: &'a str,
pub alt_text: &'a str,
pub text: &'a str,
}
impl<'a> Render for LogoLink<'a> {
fn render(&self) -> Markup {
html! {
a.link-anchor href=(self.url) {
img.link-logo src=(self.logo) alt=(self.alt_text); (self.text)
}
}
}
}
pub struct PageMetaData<'a> {
pub lang: &'a str,
pub charset: &'a str,
pub scripts: &'a [Script<'a>],
pub css: &'a [Css<'a>],
pub title: &'a str,
pub description: &'a str,
pub copyright: Copyright<'a>,
pub favicons: &'a [Favicon<'a>],
pub menu: Option<&'a Menu<'a>>,
pub contact: Option<&'a Contact<'a>>,
pub logo: Option<&'a Logo<'a>>,
pub theme_color: &'a str,
}
impl<'a> Default for PageMetaData<'a> {
fn default() -> Self {
Self {
lang: "en-US",
charset: "UTF-8",
scripts: &[],
css: &[],
title: "Benjamin Xu",
description: "Benjamin Xu's personal site.",
copyright: Copyright {
name: &Name {
first: "Benjamin",
middle: Some("Peiyan"),
last: "Xu",
nicknames: &[],
},
icon: "©",
rights_clause: "All rights reserved",
},
favicons: &[],
menu: None,
contact: None,
logo: None,
theme_color: "#00003f",
}
}
}
pub struct Favicon<'a> {
pub link: &'a str,
pub media_type: Option<&'a str>,
pub sizes: Option<&'a str>
}
impl<'a> Render for Favicon<'a> {
fn render(&self) -> Markup {
match (self.media_type, self.sizes) {
(Some(mt), Some(sz)) => {
html! { link rel="icon" href={(self.link)} type={(mt)} sizes={(sz)} {} }
},
(None, Some(sz)) => {
html! { link rel="icon" href={(self.link)} sizes={(sz)} {} }
},
(Some(mt), None) => {
html! { link rel="icon" href={(self.link)} type={(mt)} {} }
},
(None, None) => {
html! { link rel="icon" href={(self.link)} {} }
},
}
}
}
pub struct Logo<'a> {
pub src: &'a str,
pub href: Option<&'a str>,
}
impl<'a> Render for Logo<'a> {
fn render(&self) -> Markup {
html! {
div.logo {
@match self.href {
Some(link) => a.logo-wrapper href=(link) {
img.logo-img src=(self.src);
},
None => img.logo-img src=(self.src);,
}
}
}
}
}
pub enum Script<'a> {
External(&'a str),
Embedded(&'a str),
}
impl<'a> Render for Script<'a> {
fn render(&self) -> Markup {
match self {
Script::External(src) => html! { script defer?[true] src={ (src) } {} },
Script::Embedded(src) => html! { script { (PreEscaped(src)) } },
}
}
}
impl<'a> Script<'a> {
pub fn wasm_bindgen_loader(js_path: &str, wasm_path: &str, name: &str) -> (String, String) {
let glue = format!("/{js_path}/{name}.js");
let load = format!(
"\
document.addEventListener(\
\"DOMContentLoaded\",\
function(){{\
var mod = wasm_bindgen(\"/{wasm_path}/{name}_bg.wasm\")\
.catch(function(e) {{\
console.log(\"Promise received from wasm load.\");\
console.log(e);\
e.catch(function(e) {{\
console.log(e);\
}});
}});\
if (mod.load_listeners) {{\
var listeners = mod.load_listeners();\
}}\
}}\
);\
",
);
(glue, load)
}
}
pub enum Css<'a> {
Critical { src: &'a str },
NonCritical { src: &'a str },
}
impl<'a> Render for Css<'a> {
fn render(&self) -> Markup {
match self {
Css::NonCritical { src } => html! { link rel="stylesheet" href={
(src)
}{} },
Css::Critical { src } => {
let style = fs::read_to_string(src)
.unwrap_or_else(|e| panic!("{:?} is missing ({:?})", src, e));
html! { style { (PreEscaped(style)) } }
}
}
}
}
pub struct Email<'a> {
pub user: &'a str,
pub domain: &'a str,
}
impl<'a> Render for Email<'a> {
fn render(&self) -> Markup {
html! {
(self.user)"@"(self.domain)
}
}
}
pub enum PhoneNumber<'a> {
US {
area_code: u16,
prefix: u16,
line_number: u16,
icon: &'a str,
},
}
impl<'a> Render for PhoneNumber<'a> {
fn render(&self) -> Markup {
match self {
PhoneNumber::US {
icon,
area_code,
prefix,
line_number,
} => html! {
(icon)": ("(area_code)") "(prefix)"-"(line_number)
},
}
}
}
pub struct Contact<'a> {
pub email: &'a [Email<'a>],
pub phone: &'a [PhoneNumber<'a>],
}
impl<'a> Render for Contact<'a> {
fn render(&self) -> Markup {
html! {
@for email in self.email {
p.contact-email { "Email: " (email) }
}
@for phone in self.phone {
p.contact-phone-number { "Phone: " (phone) }
}
}
}
}
pub struct Name<'a> {
pub first: &'a str,
pub middle: Option<&'a str>,
pub last: &'a str,
pub nicknames: &'a [&'a str],
}
impl<'a> Render for Name<'a> {
fn render(&self) -> Markup {
html! {
(self.first) " " @if let Some(middle) = self.middle {
@if let Some(initial) = middle.chars().next() {
(initial) ". "
}
} (self.last)
}
}
}
pub struct Copyright<'a> {
pub name: &'a Name<'a>,
pub icon: &'a str,
pub rights_clause: &'a str,
}
impl<'a> Render for Copyright<'a> {
fn render(&self) -> Markup {
let year = Utc::now().year();
let start_year = year - 1;
let end_year = year + 1;
html! {
p.copyright { (self.icon) " " (start_year) "-" (end_year) " " (self.name) ". " (self.rights_clause) "." }
}
}
}
pub struct MenuItem<'a> {
pub text: &'a str,
pub link: Option<&'a str>,
pub children: Option<&'a Menu<'a>>,
}
impl<'a> MenuItem<'a> {
fn render_possible_link(link: Option<&str>, text: &str) -> Markup {
html! {
@if let Some(link) = link {
a href=(link) { (text) }
} @else {
(text)
}
}
}
}
impl<'a> Render for MenuItem<'a> {
fn render(&self) -> Markup {
html! {
li {
(MenuItem::render_possible_link(self.link, self.text))
@if let Some(children) = self.children {
(children)
}
}
}
}
}
pub struct Menu<'a>(pub &'a [MenuItem<'a>]);
impl<'a> Render for Menu<'a> {
fn render(&self) -> Markup {
html! {
nav.menu {
ul {
@for item in self.0.iter() {
(item)
}
}
}
}
}
}
impl<'a> Menu<'a> {
pub fn into_string(self) -> String {
self.render().into_string()
}
}