Compare commits

...

19 Commits

Author SHA1 Message Date
53e62fcb68 I just bumped the version so I could test.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 3m22s
I needed a fast test to make sure the Gitea server repository was
working.
2025-10-21 12:17:38 -04:00
04c2182705 The logos for the project.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 2m41s
2025-10-17 10:29:41 -04:00
f023f36558 Adjusted the responsive layout targets.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 3m11s
2025-10-16 18:47:20 -04:00
62a2c4f5bc Fixed a spelling mistake in the blog css.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 3m11s
2025-10-16 18:32:01 -04:00
87bb1459b8 Bumped the bard version for publishing.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 3m49s
2025-10-16 13:58:36 -04:00
8dd20cb0cb Fixed responsiveness of the blog.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 2m44s
Also added the publish date to a post.
2025-10-16 13:56:58 -04:00
55f1ac22b0 Changed the target dir tot he home directory bin.
All checks were successful
Build Tavernworks / Explore-Gitea-Actions (push) Successful in 2m37s
2025-10-11 20:43:47 -04:00
c3ae332b0b Changed the target directory to place executables.
Some checks failed
Build Tavernworks / Explore-Gitea-Actions (push) Failing after 2m38s
2025-10-11 20:34:23 -04:00
dede2e9072 Fixed the build script to use this repository.
Some checks failed
Build Tavernworks / Explore-Gitea-Actions (push) Failing after 2m42s
2025-10-11 20:27:26 -04:00
4c03aea947 Fixing the build script for gitea.
Some checks failed
Build Tavernworks / Explore-Gitea-Actions (push) Failing after 1m49s
2025-10-11 20:19:40 -04:00
ee82498161 Adding the build testing script.
Some checks failed
Build Tavernworks / Explore-Gitea-Actions (push) Failing after 7s
This will also install loreweaver for use by the blog.
2025-10-11 18:43:17 -04:00
d822ecdf84 Fixed up the OpenGraph meta. 2025-10-11 18:38:46 -04:00
e8a4c19fdb Post page now is completely done in SSR.
This was done to make loading time faster as it's all static information
and to make it so blog posts can be linked to and get OpenGraph data.
2025-10-10 11:19:14 -04:00
1a57f3d143 openGraph image now set properly to default image. 2025-10-10 01:24:41 -04:00
1a82e856d9 use_resource is the correct thing to use.
The top components can use_resource and be properly setup during
hydration. The sub components need to use_server_resource so that they
can be hydrated later.
2025-10-10 00:26:17 -04:00
e49ecbac4d The TagSelector needed to stay a use_resource. 2025-10-10 00:01:31 -04:00
bca3e9f939 Switching all server function calls.
Now server function calls use, use_server_future. It seems to be working
now, unlike in 0.6.3
2025-10-09 23:49:35 -04:00
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
20 changed files with 1557 additions and 600 deletions

View File

@ -0,0 +1,41 @@
name: Build Tavernworks
run-name: Building on Silverymoon by ${{ gitea.actor }}.
on:
push:
branches:
- main
jobs:
Explore-Gitea-Actions:
runs-on: FreeBSD-14.1
env:
SOURCE_DIR: tavernworks
TARGET_DIR: /home/gitea/bin
steps:
- name: Setup Workspace
run: |
rm -rf ${{ github.workspace }}/*
echo "Source Dir: $SOURCE_DIR"
- name: Clone Repo
uses: https://gitea.com/actions/checkout@v4
with:
repository: CyberMages/tavernworks
token: ${{ secrets.CM_GIT_TOKEN }}
path: ${{ env.SOURCE_DIR }}
- name: Build Repo
run: |
pwd
ls -la
cd "$SOURCE_DIR"
echo "Current Shell: $SHELL"
echo "PATH=$PATH"
. ~/.profile
echo "PATH=$PATH"
echo "Building release."
cargo build --release
echo "Installing loreweaver."
cp ./target/release/loreweaver "$TARGET_DIR"
- name: Clean Workspace
run: |
rm -rf ${{ github.workspace }}/*
ls -la

1854
Cargo.lock generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

View File

@ -1,6 +1,6 @@
[package]
name = "bard"
version = "0.3.5"
version = "0.3.17"
edition = "2024"
description = "Dioxus components that will display a Tavern blogging system Blog."
repository = "/CyberMages/tavern"
@ -14,12 +14,12 @@ name = "blog"
path = "examples/blog.rs"
[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}
tokio = { version = "1.0", features = ["rt", "macros"], optional = true }
[features]
default = ["web"]
default = ["tavern"]
web = ["tavern", "dioxus/web"]
server = ["tavern/database", "dioxus/server", "tokio"]

View File

@ -35,6 +35,9 @@
.blog_list
{
display: flex;
flex-direction: column;
width: 100%;
order: 5;
.blog_title
@ -43,6 +46,13 @@
color: var(--accent-color);
}
.blog_title_image
{
width: 23rem;
align-self: center;
margin-bottom: 25px;
}
.blog_item
{
margin-bottom: 30px;
@ -51,6 +61,16 @@
{
margin: 0px;
color: var(--accent-color);
a:link, a:visited
{
color: var(--accent-color);
}
a:hover
{
color: var(--text-color);
}
}
p
@ -97,7 +117,7 @@
padding-left: 20px;
}
.embeded_video
.embedded_video
{
margin-top: 50px;
display: flex;
@ -140,7 +160,7 @@
order: 1;
display: flex;
flex-direction: column;
margin-top: 70px;
margin-top: 185px;
margin-right: 70px;
position:sticky;
top: 150px;
@ -239,3 +259,28 @@
}
}
}
.video_link
{
display: none;
width: 100%;
justify-content: center;
}
@media screen and (max-width: 1000px)
{
.blog_post .blog_post_tale
{
.video_link
{
display: flex;
}
.embedded_video
{
display: none;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View File

@ -80,13 +80,13 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
// Retrieve the provided settings from context.
let settings = use_context::<BardSettings>();
let list = use_server_future(move ||
let list = use_resource(move ||
{
let t = tags();
let categories = t.iter().cloned().collect();
let tags = tags();
let categories: Vec<String> = tags.iter().cloned().collect();
async move { get_blog_list(categories).await }
})?;
});
rsx!
{
@ -94,20 +94,51 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
{
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 =>
{
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
{
key: "{lore.slug}",
title: lore.title.clone(),
slug: lore.slug.clone(),
author: lore.author.clone(),
@ -144,6 +176,7 @@ pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
#[component]
pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
{
let is_checked = toggled_tags.read().is_toggled(&tag);
rsx!
{
label
@ -153,7 +186,7 @@ pub fn ToggleTag(tag: String, toggled_tags: Signal<HashSet<String>>) -> Element
input
{
r#type: "checkbox",
checked: toggled_tags.read().is_toggled(&tag),
checked: is_checked,
onchange: move |_|
{
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
// of data together for clean state management.
#[component]
pub fn TagSelector(url_tag: ReadOnlySignal<String>,
pub fn TagSelector(url_tag: ReadSignal<String>,
toggled_tags: Signal<HashSet<String>>) -> Element
{
// Use use_resource to handle both fetching tags AND initializing selection

View File

@ -23,29 +23,65 @@ pub fn BlogAuthor() -> Element
#[component]
pub fn PostHeaderAuthor(adventurer: Adventurer) -> Element
pub fn PostHeaderAuthor(author: Option<Adventurer>, publish_date: String) -> Element
{
rsx!
match author
{
document::Meta
Some(adventurer) =>
{
name: "author",
content: "{adventurer.name}"
rsx!
{
document::Meta
{
name: "author",
content: "{adventurer.name}"
}
i
{
"Published: {publish_date}"
}
p
{
b
{
"Author: ",
a { href: "{adventurer.legend.profile}", "{adventurer.name}" }
}
}
}
}
p
None=>
{
b
rsx!
{
"Author: ",
a { href: "{adventurer.legend.profile}", "{adventurer.name}" }
document::Meta
{
name: "author",
content: "Unknown"
}
i
{
"Published: {publish_date}"
}
p
{
b
{
"Author: Unknown"
}
}
}
}
}
}
#[component]
pub fn PostHeader(tale: Tale) -> Element
pub fn PostHeader(tale: Tale, adventurer: Option<Adventurer>) -> Element
{
// Get the pages URL.
let url: Page = use_route();
@ -53,27 +89,20 @@ pub fn PostHeader(tale: Tale) -> Element
// Get the blog's image to use as a placeholder if there isn't one for the
// blog post.
// Retrieve the provided settings from context.
let _settings = use_context::<BardSettings>();
let author_future = use_server_future(move ||
{
let handle: String = tale.lore.author.clone();
async move { get_author(handle).await }
})?;
let settings = use_context::<BardSettings>();
rsx!
{
// Adding for SEO and OpenGraph post sharing.
document::Meta { name: "title", content: "{tale.lore.title}" }
document::Meta { name: "description", content: "{tale.lore.summary}" }
// Open Graph (used by LinkedIn, Bluesky, Discord, etc.)
document::Meta { property: "og:locale", content: "en_US" }
document::Meta { name: "title", property: "og:title", content: "{tale.lore.title}" }
document::Meta { name: "description", property: "og:description", content: "{tale.lore.summary}" }
document::Meta { property: "og:type", content: "article" }
document::Meta { property: "og:title", content: "{tale.lore.title}" }
document::Meta { property: "og:description", content: "{tale.lore.summary}" }
document::Meta { property: "og:url", content: "{url}" }
document::Meta { property: "og:image", content: "" }
if let Some(image) = settings.default_post_image
{
document::Meta { name: "image", property: "og:image", content: "{image}" }
}
h1 { {tale.lore.title} }
@ -88,13 +117,10 @@ pub fn PostHeader(tale: Tale) -> Element
}
}
if let Some(Ok(Some(adventurer))) = (author_future.value())()
PostHeaderAuthor
{
PostHeaderAuthor { adventurer: adventurer }
}
else
{
p { "Loading author..." }
author: adventurer,
publish_date: tale.lore.publish_date.format("%m/%d/%Y").to_string()
}
}
}
@ -122,7 +148,12 @@ pub fn BlogPost(slug: Signal<String>, children: Element) -> Element
// Make this reactive so that as the page changes it should rerun this.
let url_slug = slug();
async move { get_blog_post(url_slug).await }
async move
{
let post = get_blog_post(url_slug).await?;
let author = get_author(post.lore.author.clone()).await?;
Ok::<_, ServerFnError>((post, author))
}
})?;
rsx!
@ -131,11 +162,12 @@ pub fn BlogPost(slug: Signal<String>, children: Element) -> Element
{
class: "blog_post",
if let Some(Ok(tale)) = (post_future.value())()
if let Some(Ok((tale, adventurer))) = (post_future.value())()
{
PostHeader
{
tale: tale.clone()
tale: tale.clone(),
adventurer: adventurer.clone()
}
Story { text: tale.story }

View File

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

View File

@ -6,7 +6,7 @@ use crate::components::{BlogPost, TagNav};
/// Blog page
#[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.
let url_slug = use_signal(|| slug());

View File

@ -1,6 +1,7 @@
use dioxus::prelude::*;
#[derive(Copy, Clone)]
pub enum StylesheetBehavior
{
@ -23,6 +24,13 @@ pub struct BardSettings
/// it is still available for screen readers.
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.
pub stylesheet: StylesheetBehavior
}

View File

@ -1,23 +1,22 @@
[package]
name = "blog_test"
version = "0.3.1"
version = "0.3.2"
authors = ["Myrddin Dundragon <myrddin@cybermages.tech>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = { version = "0.7.0", optional = true }
axum-server = { version = "0.7.1", optional = true }
dioxus = { version = "*", features = ["router", "fullstack"] }
dioxus-cli-config = { version = "*", optional = true }
axum = { version = "0.8.4", optional = true }
axum-server = { version = "0.7.2", optional = true }
dioxus = { version = "=0.7.0-rc.1", features = ["router", "fullstack"] }
bard = { version = "*", path="../bard", optional = true }
tokio = { version = "1.0", optional = true }
[features]
default = ["web"]
web = ["dioxus/web", "bard"]
server = ["dioxus/server", "axum", "axum-server", "tokio/rt-multi-thread", "tokio/macros", "dioxus-cli-config", "bard/server"]
default = ["bard"]
web = ["dioxus/web", "bard/web"]
server = ["dioxus/server", "axum", "axum-server", "tokio/rt-multi-thread", "tokio/macros", "bard/server"]
[profile.wasm-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 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")]
{
let _ = tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move { bard::init_database("/home/myrddin/cybermages/website/tavern.db").await });
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async
{
let db_path = "/home/myrddin/cybermages/website/tavern.db";
let _ = bard::init_database(db_path).await;
});
}
#[cfg(feature = "web")]
dioxus::launch(App);
LaunchBuilder::new().launch(App);
}
#[component]
@ -28,6 +35,8 @@ fn App() -> Element
let custom_settings = BardSettings
{
blog_name: Some(String::from("Blog Test")),
blog_image: Some(BLOG_IMAGE),
default_post_image: Some(POST_IMAGE),
stylesheet: StylesheetBehavior::Extend(BLOG),
};
provide_context(custom_settings);

View File

@ -1,6 +1,6 @@
[package]
name = "loreweaver"
version = "0.3.1"
version = "0.3.2"
edition = "2024"
description = "Converts a blog repository into an SQLite database using the Tavern blog system."
repository = "/CyberMages/tavern"

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

2
rust-toolchain.toml Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB