Compare commits

...

2 Commits

Author SHA1 Message Date
3fe3d874ca Added the ability to specify images.
The blog title can now be set to be an image. If not it will default to
the text. If no text it will just be "Blog".

A Post has now been given a default post image that will be used for all
posts for their openGraph sharing. This can later be expanded to allow
a blog post to have a desired image.
2025-10-09 17:00:56 -04:00
725824003b Updated to use the new Dioxus v0.7.0-rc.1
The system is using the nightly toolchain now due to Dioxus needing it.
2025-10-08 15:17:48 -04:00
13 changed files with 1430 additions and 568 deletions

1854
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bard" name = "bard"
version = "0.3.5" version = "0.3.8"
edition = "2024" edition = "2024"
description = "Dioxus components that will display a Tavern blogging system Blog." description = "Dioxus components that will display a Tavern blogging system Blog."
repository = "/CyberMages/tavern" repository = "/CyberMages/tavern"
@ -14,12 +14,12 @@ name = "blog"
path = "examples/blog.rs" path = "examples/blog.rs"
[dependencies] [dependencies]
dioxus = { version = "*", features = ["router", "fullstack"] } dioxus = { version = "=0.7.0-rc.1", features = ["router", "fullstack"] }
tavern = { version = "0.3.0", path = "../tavern", registry="cybermages", optional = true} tavern = { version = "0.3.0", path = "../tavern", registry="cybermages", optional = true}
tokio = { version = "1.0", features = ["rt", "macros"], optional = true } tokio = { version = "1.0", features = ["rt", "macros"], optional = true }
[features] [features]
default = ["web"] default = ["tavern"]
web = ["tavern", "dioxus/web"] web = ["tavern", "dioxus/web"]
server = ["tavern/database", "dioxus/server", "tokio"] server = ["tavern/database", "dioxus/server", "tokio"]

View File

@ -35,6 +35,9 @@
.blog_list .blog_list
{ {
display: flex;
flex-direction: column;
width: 100%;
order: 5; order: 5;
.blog_title .blog_title
@ -43,6 +46,13 @@
color: var(--accent-color); color: var(--accent-color);
} }
.blog_title_image
{
width: 23rem;
align-self: center;
margin-bottom: 25px;
}
.blog_item .blog_item
{ {
margin-bottom: 30px; margin-bottom: 30px;
@ -51,6 +61,16 @@
{ {
margin: 0px; margin: 0px;
color: var(--accent-color); color: var(--accent-color);
a:link, a:visited
{
color: var(--accent-color);
}
a:hover
{
color: var(--text-color);
}
} }
p p
@ -140,7 +160,7 @@
order: 1; order: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 70px; margin-top: 185px;
margin-right: 70px; margin-right: 70px;
position:sticky; position:sticky;
top: 150px; top: 150px;

View File

@ -15,12 +15,12 @@ pub fn BlogItem(title: String, slug: String, author: String, summary: String,
tags: Vec<String>) tags: Vec<String>)
-> Element -> Element
{ {
let author_future = use_server_future(move || let author_future = use_resource(move ||
{ {
let handle: String = author.clone(); let handle: String = author.clone();
async move { get_author(handle).await } async move { get_author(handle).await }
})?; });
rsx! rsx!
{ {
@ -80,13 +80,13 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
// Retrieve the provided settings from context. // Retrieve the provided settings from context.
let settings = use_context::<BardSettings>(); let settings = use_context::<BardSettings>();
let list = use_server_future(move || let list = use_resource(move ||
{ {
let t = tags(); let tags = tags();
let categories = t.iter().cloned().collect(); let categories: Vec<String> = tags.iter().cloned().collect();
async move { get_blog_list(categories).await } async move { get_blog_list(categories).await }
})?; });
rsx! rsx!
{ {
@ -94,20 +94,51 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
{ {
class: "blog_list", class: "blog_list",
match settings.blog_name match settings.blog_image
{ {
Some(title) => Some(image) =>
{ {
rsx! match settings.blog_name
{ {
h1 { class: "blog_title", "{title}" } Some(title) =>
{
rsx!
{
h1 { class: "blog_title visually_hidden", "{title}" }
img { class: "blog_title_image", alt: "{title} blog logo", src: image }
}
}
None =>
{
rsx!
{
h1 { class: "blog_title visually_hidden", "Blog" }
img { class: "blog_title_image", alt: "Blog logo", src: image }
}
}
} }
} }
None => None =>
{ {
rsx! match settings.blog_name
{ {
h1 { class: "blog_title visually_hidden", "Blog" } Some(title) =>
{
rsx!
{
h1 { class: "blog_title", "{title}" }
}
}
None =>
{
rsx!
{
h1 { class: "blog_title visually_hidden", "Blog" }
}
}
} }
} }
} }
@ -118,6 +149,7 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
{ {
BlogItem BlogItem
{ {
key: "{lore.slug}",
title: lore.title.clone(), title: lore.title.clone(),
slug: lore.slug.clone(), slug: lore.slug.clone(),
author: lore.author.clone(), author: lore.author.clone(),
@ -144,6 +176,7 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
#[component] #[component]
pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
{ {
let is_checked = toggled_tags.read().is_toggled(&tag);
rsx! rsx!
{ {
label label
@ -153,7 +186,7 @@ pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
input input
{ {
r#type: "checkbox", r#type: "checkbox",
checked: toggled_tags.read().is_toggled(&tag), checked: is_checked,
onchange: move |_| onchange: move |_|
{ {
toggled_tags.write().toggle(&tag.clone()); toggled_tags.write().toggle(&tag.clone());
@ -182,7 +215,7 @@ pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
// which tags should be selected based on the current URL, returning both pieces // which tags should be selected based on the current URL, returning both pieces
// of data together for clean state management. // of data together for clean state management.
#[component] #[component]
pub fn TagSelector(url_tag: ReadOnlySignal<String>, pub fn TagSelector(url_tag: ReadSignal<String>,
toggled_tags: Signal<HashSet<String>>) -> Element toggled_tags: Signal<HashSet<String>>) -> Element
{ {
// Use use_resource to handle both fetching tags AND initializing selection // Use use_resource to handle both fetching tags AND initializing selection

View File

@ -53,7 +53,7 @@ pub fn PostHeader(tale: Tale) -> Element
// Get the blog's image to use as a placeholder if there isn't one for the // Get the blog's image to use as a placeholder if there isn't one for the
// blog post. // blog post.
// Retrieve the provided settings from context. // Retrieve the provided settings from context.
let _settings = use_context::<BardSettings>(); let settings = use_context::<BardSettings>();
let author_future = use_server_future(move || let author_future = use_server_future(move ||
{ {
@ -73,7 +73,10 @@ pub fn PostHeader(tale: Tale) -> Element
document::Meta { property: "og:title", content: "{tale.lore.title}" } document::Meta { property: "og:title", content: "{tale.lore.title}" }
document::Meta { property: "og:description", content: "{tale.lore.summary}" } document::Meta { property: "og:description", content: "{tale.lore.summary}" }
document::Meta { property: "og:url", content: "{url}" } document::Meta { property: "og:url", content: "{url}" }
document::Meta { property: "og:image", content: "" } if let Some(image) = settings.default_post_image
{
document::Meta { property: "og:image", content: image }
}
h1 { {tale.lore.title} } h1 { {tale.lore.title} }

View File

@ -8,7 +8,7 @@ use crate::components::{BlogList, TagSelector};
/// Blog page /// Blog page
#[component] #[component]
pub fn Blog(tag: ReadOnlySignal<String>) -> Element pub fn Blog(tag: ReadSignal<String>) -> Element
{ {
let categories: Signal<HashSet<String>> = let categories: Signal<HashSet<String>> =
use_signal(|| HashSet::new()); use_signal(|| HashSet::new());

View File

@ -6,7 +6,7 @@ use crate::components::{BlogPost, TagNav};
/// Blog page /// Blog page
#[component] #[component]
pub fn Post(slug: ReadOnlySignal<String>) -> Element pub fn Post(slug: ReadSignal<String>) -> Element
{ {
// Create a copy of the current slug to detect changes. // Create a copy of the current slug to detect changes.
let url_slug = use_signal(|| slug()); let url_slug = use_signal(|| slug());

View File

@ -1,6 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum StylesheetBehavior pub enum StylesheetBehavior
{ {
@ -23,6 +24,13 @@ pub struct BardSettings
/// it is still available for screen readers. /// it is still available for screen readers.
pub blog_name: Option<String>, pub blog_name: Option<String>,
/// The image to use for the blog title instead of just the name.
pub blog_image: Option<Asset>,
/// The image to use for the default post image in open graph. This is what
/// is shown when sharing the article.
pub default_post_image: Option<Asset>,
/// A user defined stylesheet and how the library should include it. /// A user defined stylesheet and how the library should include it.
pub stylesheet: StylesheetBehavior pub stylesheet: StylesheetBehavior
} }

View File

@ -1,23 +1,22 @@
[package] [package]
name = "blog_test" name = "blog_test"
version = "0.3.1" version = "0.3.2"
authors = ["Myrddin Dundragon <myrddin@cybermages.tech>"] authors = ["Myrddin Dundragon <myrddin@cybermages.tech>"]
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = { version = "0.7.0", optional = true } axum = { version = "0.8.4", optional = true }
axum-server = { version = "0.7.1", optional = true } axum-server = { version = "0.7.2", optional = true }
dioxus = { version = "*", features = ["router", "fullstack"] } dioxus = { version = "=0.7.0-rc.1", features = ["router", "fullstack"] }
dioxus-cli-config = { version = "*", optional = true }
bard = { version = "*", path="../bard", optional = true } bard = { version = "*", path="../bard", optional = true }
tokio = { version = "1.0", optional = true } tokio = { version = "1.0", optional = true }
[features] [features]
default = ["web"] default = ["bard"]
web = ["dioxus/web", "bard"] web = ["dioxus/web", "bard/web"]
server = ["dioxus/server", "axum", "axum-server", "tokio/rt-multi-thread", "tokio/macros", "dioxus-cli-config", "bard/server"] server = ["dioxus/server", "axum", "axum-server", "tokio/rt-multi-thread", "tokio/macros", "bard/server"]
[profile.wasm-dev] [profile.wasm-dev]
inherits = "dev" inherits = "dev"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

View File

@ -6,6 +6,10 @@ use bard::*;
const FAVICON: Asset = asset!("/assets/favicon.ico"); const FAVICON: Asset = asset!("/assets/favicon.ico");
const BLOG: Asset = asset!("/assets/blog.css"); const BLOG: Asset = asset!("/assets/blog.css");
const BLOG_IMAGE: Asset = asset!("/assets/runes_and_ramblings_text.png",
AssetOptions::builder().with_hash_suffix(false));
const POST_IMAGE: Asset = asset!("/assets/runes_and_ramblings_logo.png",
AssetOptions::builder().with_hash_suffix(false));
@ -13,13 +17,16 @@ fn main()
{ {
#[cfg(feature = "server")] #[cfg(feature = "server")]
{ {
let _ = tokio::runtime::Runtime::new() let rt = tokio::runtime::Runtime::new().unwrap();
.unwrap() rt.block_on(async
.block_on(async move { bard::init_database("/home/myrddin/cybermages/website/tavern.db").await }); {
let db_path = "/home/myrddin/cybermages/website/tavern.db";
let _ = bard::init_database(db_path).await;
});
} }
#[cfg(feature = "web")] LaunchBuilder::new().launch(App);
dioxus::launch(App);
} }
#[component] #[component]
@ -28,6 +35,8 @@ fn App() -> Element
let custom_settings = BardSettings let custom_settings = BardSettings
{ {
blog_name: Some(String::from("Blog Test")), blog_name: Some(String::from("Blog Test")),
blog_image: Some(BLOG_IMAGE),
default_post_image: Some(POST_IMAGE),
stylesheet: StylesheetBehavior::Extend(BLOG), stylesheet: StylesheetBehavior::Extend(BLOG),
}; };
provide_context(custom_settings); provide_context(custom_settings);

2
rust-toolchain.toml Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"