656 lines
17 KiB
Rust
656 lines
17 KiB
Rust
use maud::{PreEscaped, html};
|
|
|
|
use crate::ui::prelude::Optional;
|
|
|
|
// TODO : documentation
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn Meta(key: &str, value: &str) -> PreEscaped<String> {
|
|
html! {
|
|
meta property=(key) content=(value) {};
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Metadata {
|
|
pub title: String,
|
|
pub kind: PreEscaped<String>,
|
|
pub image: MediaItem,
|
|
pub url: String,
|
|
|
|
pub audio: Option<MediaItem>,
|
|
pub description: Option<String>,
|
|
pub determiner: Option<Determiner>,
|
|
pub locale: Option<String>,
|
|
pub locale_alternate: Vec<String>,
|
|
pub site_name: Option<String>,
|
|
pub video: Option<MediaItem>,
|
|
}
|
|
|
|
impl Metadata {
|
|
pub fn new<T: ObjectType>(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<String> {
|
|
let r = v.into_iter().map(|x| Meta(tag_name, x)).collect::<Vec<_>>();
|
|
html! {
|
|
@for e in r {
|
|
(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Determiner {
|
|
A,
|
|
An,
|
|
The,
|
|
Blank,
|
|
Auto,
|
|
}
|
|
|
|
impl Determiner {
|
|
pub fn render(&self) -> PreEscaped<String> {
|
|
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<String> {
|
|
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<String>,
|
|
pub mime: String,
|
|
pub width: Option<u32>,
|
|
pub height: Option<u32>,
|
|
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<String> {
|
|
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<String>,
|
|
}
|
|
|
|
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<String> {
|
|
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<String> {
|
|
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<String> {
|
|
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<String> {
|
|
html! {
|
|
(Meta("music:creator", &self.creator))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Video {
|
|
pub actor: Vec<(String, String)>,
|
|
pub director: Vec<String>,
|
|
pub writer: Vec<String>,
|
|
pub duration: u32,
|
|
pub release_date: chrono::NaiveDate,
|
|
pub tag: Vec<String>,
|
|
pub series: Option<String>,
|
|
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<String> {
|
|
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<String>,
|
|
pub section: String,
|
|
pub tag: Vec<String>,
|
|
}
|
|
|
|
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<String> {
|
|
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<String>,
|
|
pub isbn: String,
|
|
pub release_date: chrono::NaiveDate,
|
|
pub tag: Vec<String>,
|
|
}
|
|
|
|
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<String> {
|
|
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<String> {
|
|
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<String> {
|
|
html! {
|
|
(Meta("og:type", "website"))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait ObjectType {
|
|
fn render(&self) -> PreEscaped<String>;
|
|
}
|