From 1f502a73868d1564213b0367a549ac1310b2e579 Mon Sep 17 00:00:00 2001 From: Myrddin Dundragon Date: Mon, 8 Sep 2025 18:39:30 -0400 Subject: [PATCH] Updating components to use the database. --- bard/Cargo.toml | 2 +- bard/src/components.rs | 211 +++++++++++++++++++++++------------------ bard/src/lib.rs | 4 +- bard/src/pages/blog.rs | 6 +- bard/src/pages/post.rs | 5 +- bard/src/server.rs | 50 +++++----- 6 files changed, 150 insertions(+), 128 deletions(-) diff --git a/bard/Cargo.toml b/bard/Cargo.toml index 7ebe558..0fdca86 100644 --- a/bard/Cargo.toml +++ b/bard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bard" -version = "0.0.13" +version = "0.0.14" edition = "2024" description = "Dioxus components that will display a Tavern blogging system Blog." repository = "/CyberMages/tavern" diff --git a/bard/src/components.rs b/bard/src/components.rs index 4eb7508..6d294b8 100644 --- a/bard/src/components.rs +++ b/bard/src/components.rs @@ -1,6 +1,4 @@ - use dioxus::prelude::*; - use tavern::{Adventurer, Legend, Lore, Tale}; use crate::page::Page; @@ -15,51 +13,42 @@ const POST_CSS: Asset = asset!("/assets/css/blog_post.css"); const TAG_CSS: Asset = asset!("/assets/css/blog_tag.css"); -#[derive(Clone, Copy, PartialEq)] -pub enum TagStyle + +fn convert_tag(text: S) -> (String, String) + where S: AsRef { - Regular, - Easy, - Medium, - Hard + 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()) + } } -#[derive(Clone, Copy, PartialEq)] -pub enum Tag +fn strip_tag(text: &str) -> &str { - LeetCode, - Embedded, - Simulation, - Web + if let Some((first, _)) = text.split_once('-') + { + first + } + else + { + text + } } #[component] -pub fn TagItem(tag: Tag, style: TagStyle) -> Element +pub fn TagItem(tag: String, style: String) -> Element { - let text = match tag - { - Tag::LeetCode => { "LeetCode" } - Tag::Embedded => { "Embedded" } - Tag::Simulation => { "Simulation" } - Tag::Web => { "Web" } - }; - - let tag_style = match style - { - TagStyle::Regular => { "none" } - TagStyle::Easy => { "easy" } - TagStyle::Medium => { "medium" } - TagStyle::Hard => { "hard" } - }; - - rsx! - { + rsx! { li { class: "tag_item", - a { class: "{tag_style}", "{text}" } + a { class: "{style}", "{tag}" } } } } @@ -67,8 +56,7 @@ pub fn TagItem(tag: Tag, style: TagStyle) -> Element #[component] pub fn TagList(children: Element) -> Element { - rsx! - { + rsx! { document::Link { rel: "stylesheet", href: TAG_CSS } h5 @@ -87,8 +75,7 @@ pub fn TagList(children: Element) -> Element #[component] pub fn BlogAuthor() -> Element { - rsx! - { + rsx! { document::Link { rel: "stylesheet", href: AUTHOR_CSS } section @@ -101,11 +88,14 @@ pub fn BlogAuthor() -> Element #[component] -pub fn BlogItem(title: String, slug: String, author: String, - summary: String, tags: Vec<(Tag, TagStyle)>) -> Element +pub fn BlogItem(title: String, slug: String, author: String, summary: String, + tags: Vec) + -> Element { - rsx! - { + let tags: Vec<(String, String)> = + tags.iter().map(|t| convert_tag(t)).collect(); + + rsx! { li { class: "blog_item", @@ -120,8 +110,8 @@ pub fn BlogItem(title: String, slug: String, author: String, { TagItem { - tag: tag, - style: style + tag: tag.clone(), + style: style.clone() } } } @@ -135,17 +125,12 @@ pub fn BlogItem(title: String, slug: String, author: String, #[component] pub fn BlogList(tags: Vec, children: Element) -> Element { - let summaries = use_server_future(move || - { + let summaries = use_server_future(move || { let categories = tags.clone(); - async move - { - get_blog_list(categories).await - } + async move { get_blog_list(categories).await } })?; - rsx! - { + rsx! { document::Link { rel: "stylesheet", href: LIST_CSS } section @@ -155,6 +140,44 @@ pub fn BlogList(tags: Vec, children: Element) -> Element { 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} } } @@ -164,16 +187,9 @@ pub fn BlogList(tags: Vec, children: Element) -> Element #[component] pub fn BlogNav() -> Element { - let tags = use_server_future(move || - { - async move - { - get_tags().await - } - })?; + let tags = use_server_future(move || async move { get_tags().await })?; - rsx! - { + rsx! { document::Link { rel: "stylesheet", href: NAV_CSS } section @@ -186,38 +202,52 @@ pub fn BlogNav() -> Element ul { class: "tag_list", + match &*tags.read() + { + Some(Ok(categories)) => + { + rsx! + { + for tag in categories + { li { class: "tag_item", - a { href: "/blog/LeetCode", "LeetCode" } + a { href: "/blog/{strip_tag(tag)}", "{strip_tag(tag)}" } } - li - { - class: "tag_item", - a { href: "/blog/Embedded", "Embedded" } + } } - li + } + + Some(Err(e)) => + { + rsx! { - class: "tag_item", - a { href: "/blog/Simulation", "Simulation" } + p { "Unable to show desired post." } + p { "{e}" } } - li + } + + None => + { + rsx! { - class: "tag_item", - a { href: "/blog/Web", "Web" } + p { "Loading..." } } } } + } + } } } } #[component] -pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> Element +pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) + -> Element { - rsx! - { + rsx! { h4 { "Author: ", a { href: "{profile_link}", "{name} @{handle}" } } } } @@ -225,27 +255,25 @@ pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> E #[component] pub fn PostHeader(title: String, author: String, tags: Vec) -> Element { - let author = use_server_future(move || - { + let author = use_server_future(move || { let target_author = author.clone(); - async move - { - get_author(target_author).await - } + async move { get_author(target_author).await } })?; - rsx! - { + let converted_tags: Vec<(String, String)> = + tags.iter().map(|t| convert_tag(t)).collect(); + + rsx! { h1 { "{title}" } TagList { - for category in tags + for (tag, style) in converted_tags { TagItem { - tag: Tag::LeetCode, - style: TagStyle::Regular + tag: tag.clone(), + style: style.clone() } } } @@ -288,17 +316,12 @@ pub fn PostHeader(title: String, author: String, tags: Vec) -> Element #[component] pub fn BlogPost(slug: String, children: Element) -> Element { - let post = use_server_future(move || - { + let post = use_server_future(move || { let url_slug = slug.clone(); - async move - { - get_blog_post(url_slug).await - } + async move { get_blog_post(url_slug).await } })?; - rsx! - { + rsx! { document::Link { rel: "stylesheet", href: POST_CSS } article diff --git a/bard/src/lib.rs b/bard/src/lib.rs index 63fd9b6..0e5c501 100644 --- a/bard/src/lib.rs +++ b/bard/src/lib.rs @@ -9,11 +9,9 @@ mod server; -pub use crate::info::{get_name, get_version}; - pub use crate::components::*; +pub use crate::info::{get_name, get_version}; pub use crate::page::Page; pub use crate::pages::*; - #[cfg(feature = "server")] pub use crate::server::*; diff --git a/bard/src/pages/blog.rs b/bard/src/pages/blog.rs index 8f40ce3..1597af9 100644 --- a/bard/src/pages/blog.rs +++ b/bard/src/pages/blog.rs @@ -1,8 +1,7 @@ use dioxus::prelude::*; -use crate::page::Page; - use crate::components::BlogList; +use crate::page::Page; @@ -21,8 +20,7 @@ pub fn Blog(tag: String) -> Element categories.push(tag); } - rsx! - { + rsx! { document::Stylesheet { href: BLOG_CSS } main diff --git a/bard/src/pages/post.rs b/bard/src/pages/post.rs index 6ebb1c8..fc6b7f8 100644 --- a/bard/src/pages/post.rs +++ b/bard/src/pages/post.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; -use crate::page::Page; use crate::components::BlogPost; +use crate::page::Page; @@ -13,8 +13,7 @@ const BLOG_CSS: Asset = asset!("/assets/css/blog.css"); #[component] pub fn Post(slug: String) -> Element { - rsx! - { + rsx! { document::Stylesheet { href: BLOG_CSS } main diff --git a/bard/src/server.rs b/bard/src/server.rs index 832a17e..8ea7407 100644 --- a/bard/src/server.rs +++ b/bard/src/server.rs @@ -3,21 +3,19 @@ // // Server functions must be: // * Be an async function -// * Have arguments and a return type that both implement serialize and deserialize (with serde). +// * Have arguments and a return type that both implement serialize and +// deserialize (with serde). // * Return a Result with an error type of ServerFnError #[cfg(feature = "server")] use std::sync::Arc; use dioxus::prelude::*; - -#[cfg(feature = "server")] -use tokio::sync::OnceCell; - -use tavern::{Adventurer, Lore, Tale}; - #[cfg(feature = "server")] use tavern::Database; +use tavern::{Adventurer, Lore, Tale}; +#[cfg(feature = "server")] +use tokio::sync::OnceCell; @@ -27,7 +25,9 @@ static BLOG_DATABASE: OnceCell> = OnceCell::const_new(); #[cfg(feature = "server")] -async fn get_database_instance

(path: P) -> Result<&'static Arc, ServerFnError> +async fn get_database_instance

( + path: P) + -> Result<&'static Arc, ServerFnError> where P: AsRef { BLOG_DATABASE.get_or_try_init(|| async @@ -45,8 +45,8 @@ pub async fn init_database

(path: P) -> Result<(), ServerFnError> { match get_database_instance(path).await { - Ok(_) => { Ok(()) } - Err(e) => { Err(e) } + Ok(_) => Ok(()), + Err(e) => Err(e) } } @@ -69,36 +69,40 @@ pub async fn get_tags() -> Result, ServerFnError> } #[server] -pub async fn get_blog_list(categories: Vec) -> Result, ServerFnError> +pub async fn get_blog_list(categories: Vec) + -> Result, ServerFnError> { - let db = get_database().await?; + let db = get_database().await?; - let summaries = db.get_tales_summary(&categories).await - .map_err(|e| ServerFnError::new(e))?; + let summaries = db.get_tales_summary(&categories) + .await + .map_err(|e| ServerFnError::new(e))?; - Ok(summaries) + Ok(summaries) } #[server] pub async fn get_blog_post(slug: String) -> Result { - let db = get_database().await?; + let db = get_database().await?; - let tale = db.get_tale_by_slug(&slug).await - .map_err(|e| ServerFnError::new(e))?; + let tale = db.get_tale_by_slug(&slug) + .await + .map_err(|e| ServerFnError::new(e))?; - tale.ok_or(ServerFnError::new(format!("Post {} not found", slug))) + tale.ok_or(ServerFnError::new(format!("Post {} not found", slug))) } #[server] pub async fn get_author(handle: String) -> Result { - let db = get_database().await?; + let db = get_database().await?; - let author = db.get_adventurer(&handle).await - .map_err(|e| ServerFnError::new(e))?; + let author = db.get_adventurer(&handle) + .await + .map_err(|e| ServerFnError::new(e))?; - author.ok_or(ServerFnError::new(format!("Author {} not found.", handle))) + author.ok_or(ServerFnError::new(format!("Author {} not found.", handle))) }