use dioxus::prelude::*; use tavern::{Adventurer, Legend, Lore, Tale}; use crate::page::Page; use crate::server::*; const AUTHOR_CSS: Asset = asset!("/assets/css/blog_author.css"); const LIST_CSS: Asset = asset!("/assets/css/blog_list.css"); const NAV_CSS: Asset = asset!("/assets/css/blog_nav.css"); const POST_CSS: Asset = asset!("/assets/css/blog_post.css"); const TAG_CSS: Asset = asset!("/assets/css/blog_tag.css"); fn convert_tag(text: S) -> (String, String) where S: AsRef { if let Some((first, second)) = text.as_ref().split_once('-') { (first.to_owned(), second.to_owned()) } else { (text.as_ref().to_owned(), "none".to_owned()) } } fn strip_tag(text: &str) -> &str { if let Some((first, _)) = text.split_once('-') { first } else { text } } #[component] pub fn TagItem(tag: String, style: String) -> Element { rsx! { li { class: "tag_item", Link { class: "{style}", to: Page::Blog { tag: tag.clone() }, span { "{tag}" } } // a { class: "{style}", "{tag}" } } } } #[component] pub fn TagList(children: Element) -> Element { rsx! { document::Link { rel: "stylesheet", href: TAG_CSS } h5 { class: "blog_tag_style", ul { class: "tag_list", {children} } } } } #[component] pub fn BlogAuthor() -> Element { rsx! { document::Link { rel: "stylesheet", href: AUTHOR_CSS } section { class: "blog_author_style", } } } #[component] pub fn BlogItem(title: String, slug: String, author: String, summary: String, tags: Vec) -> Element { let tags: Vec<(String, String)> = tags.iter().map(|t| convert_tag(t)).collect(); rsx! { li { class: "blog_item", Link { to: Page::Post { slug: slug }, h1 { "{title}" } } TagList { for (tag, style) in tags { TagItem { tag: tag.clone(), style: style.clone() } } } h4 { "Author: {author}" } p { "{summary}" } } } } #[component] pub fn BlogList(tags: Vec, children: Element) -> Element { let summaries = use_server_future(move || { let categories = tags.clone(); async move { get_blog_list(categories).await } })?; rsx! { document::Link { rel: "stylesheet", href: LIST_CSS } section { class: "blog_list_style", ul { class: "blog_list", match &*summaries.read() { Some(Ok(lores)) => { rsx! { for lore in lores { BlogItem { title: lore.title.clone(), slug: lore.slug.clone(), author: lore.author.clone(), summary: lore.summary.clone(), tags: lore.tags.clone() } } } } Some(Err(e)) => { rsx! { p { "Unable to show post header." } p { "{e}" } } } None => { rsx! { p { "Loading..." } } } } {children} } } } } #[component] pub fn BlogNav() -> Element { let tags = use_server_future(move || async move { get_tags().await })?; rsx! { document::Link { rel: "stylesheet", href: NAV_CSS } section { class: "blog_nav_style", div { class: "tag_style", h3 { "Categories" } ul { class: "tag_list", match &*tags.read() { Some(Ok(categories)) => { rsx! { for tag in categories { li { class: "tag_item", a { href: "/blog/{strip_tag(tag)}", "{strip_tag(tag)}" } } } } } Some(Err(e)) => { rsx! { p { "Unable to show desired post." } p { "{e}" } } } None => { rsx! { p { "Loading..." } } } } } } } } } #[component] pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> Element { rsx! { h4 { "Author: ", a { href: "{profile_link}", "{name} @{handle}" } } } } #[component] pub fn PostHeader(title: String, author: String, tags: Vec) -> Element { let author = use_server_future(move || { let target_author = author.clone(); async move { get_author(target_author).await } })?; let converted_tags: Vec<(String, String)> = tags.iter().map(|t| convert_tag(t)).collect(); rsx! { h1 { "{title}" } TagList { for (tag, style) in converted_tags { TagItem { tag: tag.clone(), style: style.clone() } } } match &*author.read() { Some(Ok(author)) => { rsx! { PostHeaderAuthor { name: author.name.clone(), handle: author.handle.clone(), profile_link: author.legend.profile.clone() } } } Some(Err(e)) => { rsx! { p { "Unable to show post header." } p { "{e}" } } } None => { rsx! { p { "Loading..." } } } } } } #[component] pub fn BlogPost(slug: String, children: Element) -> Element { let post = use_server_future(move || { let url_slug = slug.clone(); async move { get_blog_post(url_slug).await } })?; rsx! { document::Link { rel: "stylesheet", href: POST_CSS } article { class: "blog_post_style", match &*post.read() { Some(Ok(post)) => { rsx! { PostHeader { title: post.lore.title.clone(), author: post.lore.author.clone(), tags: post.lore.tags.clone() } div { dangerous_inner_html: "{post.story}" } } } Some(Err(e)) => { rsx! { p { "Unable to show desired post." } p { "{e}" } } } None => { rsx! { p { "Loading..." } } } } {children} } } }