use maud::{PreEscaped, html}; use crate::ui::prelude::Optional; // TODO : documentation #[allow(non_snake_case)] pub fn Meta(key: &str, value: &str) -> PreEscaped { html! { meta property=(key) content=(value) {}; } } #[derive(Debug, Clone)] pub struct Metadata { pub title: String, pub kind: PreEscaped, pub image: MediaItem, pub url: String, pub audio: Option, pub description: Option, pub determiner: Option, pub locale: Option, pub locale_alternate: Vec, pub site_name: Option, pub video: Option, } impl Metadata { pub fn new(url: &str, title: &str, image: MediaItem, kind: T) -> Self { Self { title: title.to_string(), kind: kind.render(), image, url: url.to_string(), audio: None, description: None, determiner: None, locale: None, locale_alternate: Vec::new(), site_name: None, video: None, } } pub fn audio(mut self, audio: MediaItem) -> Self { self.audio = Some(audio); self } pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_string()); self } pub fn determiner(mut self, determiner: Determiner) -> Self { self.determiner = Some(determiner); self } pub fn locale(mut self, locale: &str) -> Self { self.locale = Some(locale.to_string()); self } pub fn locale_alternate(mut self, locale: &str) -> Self { self.locale_alternate.push(locale.to_string()); self } pub fn site_name(mut self, site_name: &str) -> Self { self.site_name = Some(site_name.to_string()); self } pub fn video(mut self, video: MediaItem) -> Self { self.video = Some(video); self } } fn array_meta(v: &[String], tag_name: &str) -> PreEscaped { let r = v.into_iter().map(|x| Meta(tag_name, x)).collect::>(); html! { @for e in r { (e) } } } #[derive(Debug, Clone)] pub enum Determiner { A, An, The, Blank, Auto, } impl Determiner { pub fn render(&self) -> PreEscaped { match self { Determiner::A => Meta("og:determiner", "a"), Determiner::An => Meta("og:determiner", "an"), Determiner::The => Meta("og:determiner", "the"), Determiner::Blank => Meta("og:determiner", ""), Determiner::Auto => Meta("og:determiner", "auto"), } } } impl Metadata { pub fn render(&self) -> PreEscaped { html! { (Meta("og:title", &self.title)) (Optional(self.description.as_ref(), |desc| Meta("og:description", desc))) (Optional(self.site_name.as_ref(), |site_name| Meta("og:site_name", site_name))) (Optional(self.determiner.as_ref(), |determiner| determiner.render())) (Optional(self.locale.as_ref(), |locale| Meta("og:locale", locale))) (array_meta(&self.locale_alternate, "og:locale:alternate")) (Meta("og:url", &self.url)) (self.image.render()) (Optional(self.video.as_ref(), |video| video.render())) (Optional(self.audio.as_ref(), |audio| audio.render())) (self.kind) } } } #[derive(Debug, Clone)] pub enum MediaItemKind { Image, Video, Audio, } #[derive(Debug, Clone)] pub struct MediaItem { pub url: String, pub secure_url: Option, pub mime: String, pub width: Option, pub height: Option, pub alt: String, pub kind: MediaItemKind, } impl MediaItem { pub fn secure_url(mut self, url: &str) -> Self { self.secure_url = Some(url.to_string()); self } pub fn width(mut self, width: u32) -> Self { self.width = Some(width); self } pub fn height(mut self, height: u32) -> Self { self.height = Some(height); self } } impl MediaItem { #[allow(non_snake_case)] pub fn Image(url: &str, mime: &str, alt: &str) -> Self { let secure_url = if url.starts_with("https") { Some(url.to_string()) } else { None }; MediaItem { url: url.to_string(), secure_url: secure_url, mime: mime.to_string(), width: None, height: None, alt: alt.to_string(), kind: MediaItemKind::Image, } } #[allow(non_snake_case)] pub fn Video(url: &str, mime: &str, alt: &str) -> Self { let secure_url = if url.starts_with("https") { Some(url.to_string()) } else { None }; MediaItem { url: url.to_string(), secure_url: secure_url, mime: mime.to_string(), width: None, height: None, alt: alt.to_string(), kind: MediaItemKind::Video, } } #[allow(non_snake_case)] pub fn Audio(url: &str, mime: &str, alt: &str) -> Self { let secure_url = if url.starts_with("https") { Some(url.to_string()) } else { None }; MediaItem { url: url.to_string(), secure_url: secure_url, mime: mime.to_string(), width: None, height: None, alt: alt.to_string(), kind: MediaItemKind::Audio, } } } impl MediaItem { pub fn render(&self) -> PreEscaped { match self.kind { MediaItemKind::Image => { html! { (Meta("og:image", &self.url)) (Optional(self.secure_url.as_ref(), |secure_url| Meta("og:image:secure_url", secure_url))) (Meta("og:image:type", &self.mime)) (Optional(self.width.as_ref(), |width| Meta("og:image:width", &width.to_string()))) (Optional(self.height.as_ref(), |height| Meta("og:image:height", &height.to_string()))) (Meta("og:image:alt", &self.alt)) } } MediaItemKind::Video => { html! { (Meta("og:video", &self.url)) (Optional(self.secure_url.as_ref(), |secure_url| Meta("og:video:secure_url", secure_url))) (Meta("og:video:type", &self.mime)) (Optional(self.width.as_ref(), |width| Meta("og:video:width", &width.to_string()))) (Optional(self.height.as_ref(), |height| Meta("og:video:height", &height.to_string()))) } } MediaItemKind::Audio => { html! { (Meta("og:audio", &self.url)) (Optional(self.secure_url.as_ref(), |secure_url| Meta("og:audio:secure_url", secure_url))) (Meta("og:audio:type", &self.mime)) } } } } } pub struct Song { pub duration: u32, pub album: String, pub album_disc: u32, pub album_track: u32, pub musician: Vec, } impl Song { pub fn new(duration: u32, album: &str, album_disc: u32, album_track: u32) -> Self { Self { duration, album: album.to_string(), album_disc, album_track, musician: Vec::new(), } } pub fn musician(mut self, musician: &str) -> Self { self.musician.push(musician.to_string()); self } } impl ObjectType for Song { fn render(&self) -> PreEscaped { html! { (Meta("og:type", "music.song")) (Meta("music:duration", &self.duration.to_string())) (Meta("music:album:disc", &self.album_disc.to_string())) (Meta("music:album:track", &self.album_track.to_string())) (array_meta(&self.musician, "music:musician")) } } } pub struct Album { pub song: Vec<(String, u32, u32)>, pub musician: String, pub release_date: chrono::NaiveDate, } impl Album { pub fn new(musician: &str, release_date: chrono::NaiveDate) -> Self { Self { song: Vec::new(), musician: musician.to_string(), release_date, } } pub fn song(mut self, song: &str, disc: u32, track: u32) -> Self { self.song.push((song.to_string(), disc, track)); self } } impl ObjectType for Album { fn render(&self) -> PreEscaped { html! { @for song in &self.song { (Meta("music:song", &song.0)) (Meta("music:song:disc", &song.1.to_string())) (Meta("music:song:track", &song.2.to_string())) } (Meta("music:musician", &self.musician)) (Meta("music:release_date", &self.release_date.format("%Y-%m-%d").to_string())) } } } pub struct Playlist { pub song: Vec<(String, u32, u32)>, pub creator: String, } impl Playlist { pub fn new(creator: &str) -> Self { Self { song: Vec::new(), creator: creator.to_string(), } } pub fn song(mut self, song: &str, disc: u32, track: u32) -> Self { self.song.push((song.to_string(), disc, track)); self } } impl ObjectType for Playlist { fn render(&self) -> PreEscaped { html! { @for song in &self.song { (Meta("music:song", &song.0)) (Meta("music:song:disc", &song.1.to_string())) (Meta("music:song:track", &song.2.to_string())) } (Meta("music:creator", &self.creator)) } } } pub struct RadioStation { pub creator: String, } impl RadioStation { pub fn new(creator: &str) -> Self { Self { creator: creator.to_string(), } } } impl ObjectType for RadioStation { fn render(&self) -> PreEscaped { html! { (Meta("music:creator", &self.creator)) } } } pub struct Video { pub actor: Vec<(String, String)>, pub director: Vec, pub writer: Vec, pub duration: u32, pub release_date: chrono::NaiveDate, pub tag: Vec, pub series: Option, pub kind: VideoType, } impl Video { pub fn actor(mut self, actor: &str, role: &str) -> Self { self.actor.push((actor.to_string(), role.to_string())); self } pub fn director(mut self, director: &str) -> Self { self.director.push(director.to_string()); self } pub fn writer(mut self, writer: &str) -> Self { self.writer.push(writer.to_string()); self } pub fn tag(mut self, tag: &str) -> Self { self.tag.push(tag.to_string()); self } } impl Video { #[allow(non_snake_case)] pub fn Movie(duration: u32, release_date: chrono::NaiveDate) -> Self { Self { actor: Vec::new(), director: Vec::new(), writer: Vec::new(), duration, release_date, tag: Vec::new(), series: None, kind: VideoType::Movie, } } #[allow(non_snake_case)] pub fn Episode(duration: u32, release_date: chrono::NaiveDate) -> Self { Self { actor: Vec::new(), director: Vec::new(), writer: Vec::new(), duration, release_date, tag: Vec::new(), series: None, kind: VideoType::Episode, } } #[allow(non_snake_case)] pub fn TV_Show(duration: u32, release_date: chrono::NaiveDate, series: &str) -> Self { Self { actor: Vec::new(), director: Vec::new(), writer: Vec::new(), duration, release_date, tag: Vec::new(), series: Some(series.to_string()), kind: VideoType::TV_Show, } } #[allow(non_snake_case)] pub fn Other(duration: u32, release_date: chrono::NaiveDate) -> Self { Self { actor: Vec::new(), director: Vec::new(), writer: Vec::new(), duration, release_date, tag: Vec::new(), series: None, kind: VideoType::Other, } } } impl ObjectType for Video { fn render(&self) -> PreEscaped { html! { (Meta("og:type", self.kind.kind())) @for actor in &self.actor { (Meta("video:actor", &actor.0)) (Meta("video:actor:role", &actor.1)) } (array_meta(&self.director, "video:director")) (array_meta(&self.writer, "video:writer")) (Meta("video:duration", &self.duration.to_string())) (Meta("video:release_date", &self.release_date.format("%Y-%m-%d").to_string())) (array_meta(&self.tag, "video:tag")) (Optional(self.series.as_ref(), |series| Meta("video.series", series))) } } } pub enum VideoType { Movie, Episode, #[allow(non_camel_case_types)] TV_Show, Other, } impl VideoType { pub fn kind(&self) -> &str { match self { VideoType::Movie => "video.movie", VideoType::Episode => "video.episode", VideoType::TV_Show => "video.tv_show", VideoType::Other => "video.other", } } } pub struct Article { pub published_time: chrono::NaiveDate, pub modified_time: chrono::NaiveDate, pub expiration_time: chrono::NaiveDate, pub author: Vec, pub section: String, pub tag: Vec, } impl Article { pub fn new( published_time: chrono::NaiveDate, modified_time: chrono::NaiveDate, expiration_time: chrono::NaiveDate, section: &str, ) -> Self { Self { published_time, modified_time, expiration_time, author: Vec::new(), section: section.to_string(), tag: Vec::new(), } } pub fn author(mut self, author: &str) -> Self { self.author.push(author.to_string()); self } pub fn tag(mut self, tag: &str) -> Self { self.tag.push(tag.to_string()); self } } impl ObjectType for Article { fn render(&self) -> PreEscaped { html! { (Meta("article:published_time", &self.published_time.format("%Y-%m-%d").to_string())) (Meta("article:modified_time", &self.modified_time.format("%Y-%m-%d").to_string())) (Meta("article:expiration_time", &self.expiration_time.format("%Y-%m-%d").to_string())) (array_meta(&self.author, "article:author")) (Meta("article:section", &self.section)) (array_meta(&self.tag, "article:tag")) } } } pub struct Book { pub author: Vec, pub isbn: String, pub release_date: chrono::NaiveDate, pub tag: Vec, } impl Book { pub fn new(isbn: &str, release_date: chrono::NaiveDate) -> Self { Self { author: Vec::new(), isbn: isbn.to_string(), release_date, tag: Vec::new(), } } pub fn author(mut self, author: &str) -> Self { self.author.push(author.to_string()); self } pub fn tag(mut self, tag: &str) -> Self { self.tag.push(tag.to_string()); self } } impl ObjectType for Book { fn render(&self) -> PreEscaped { html! { (array_meta(&self.author, "book:author")) (Meta("book:isbn", &self.isbn)) (Meta("book:release_date", &self.release_date.format("%Y-%m-%d").to_string())) (array_meta(&self.tag, "book:tag")) } } } pub struct Profile { first_name: String, last_name: String, username: String, gender: Gender, } impl Profile { pub fn new(first_name: &str, last_name: &str, username: &str, gender: Gender) -> Self { Self { first_name: first_name.to_string(), last_name: last_name.to_string(), username: username.to_string(), gender, } } } pub enum Gender { Male, Female, None, } impl ObjectType for Profile { fn render(&self) -> PreEscaped { html! { (Meta("profile:first_name", &self.first_name)) (Meta("profile:last_name", &self.last_name)) (Meta("profile:username", &self.username)) (Meta("profile:gender", match self.gender { Gender::Male => "male", Gender::Female => "female", Gender::None => "none", })) } } } pub struct Website; impl Website { pub fn new() -> Self { Self } } impl ObjectType for Website { fn render(&self) -> PreEscaped { html! { (Meta("og:type", "website")) } } } pub trait ObjectType { fn render(&self) -> PreEscaped; }