2025-09-12 13:52:35 -04:00
|
|
|
use std::borrow::Borrow;
|
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
use std::hash::Hash;
|
|
|
|
|
|
2025-09-02 14:00:19 -04:00
|
|
|
use dioxus::prelude::*;
|
2025-09-06 15:05:58 -04:00
|
|
|
use tavern::{Adventurer, Legend, Lore, Tale};
|
2025-09-02 14:00:19 -04:00
|
|
|
|
2025-09-08 10:01:02 -04:00
|
|
|
use crate::page::Page;
|
2025-09-06 15:05:58 -04:00
|
|
|
use crate::server::*;
|
2025-09-02 14:00:19 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
/// Trait to toggle the presence of a value (like a tag) in a set.
|
|
|
|
|
pub trait Togglable
|
|
|
|
|
{
|
|
|
|
|
type Item: ?Sized;
|
|
|
|
|
|
|
|
|
|
fn toggle(&mut self, item: &Self::Item);
|
|
|
|
|
fn is_toggled(&self, item: &Self::Item) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Togglable for HashSet<String>
|
|
|
|
|
{
|
|
|
|
|
type Item = str;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn toggle(&mut self, item: &str)
|
|
|
|
|
{
|
|
|
|
|
if self.contains(item)
|
|
|
|
|
{
|
|
|
|
|
self.remove(item);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
self.insert(item.to_owned());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_toggled(&self, item: &str) -> bool
|
|
|
|
|
{
|
|
|
|
|
self.contains(item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
fn convert_tag<S>(text: S) -> (String, String)
|
|
|
|
|
where S: AsRef<str>
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
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())
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
fn strip_tag(text: &str) -> &str
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
if let Some((first, _)) = text.split_once('-')
|
|
|
|
|
{
|
|
|
|
|
first
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
text
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[component]
|
2025-09-08 18:39:30 -04:00
|
|
|
pub fn TagItem(tag: String, style: String) -> Element
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
rsx! {
|
2025-09-12 13:52:35 -04:00
|
|
|
li
|
|
|
|
|
{
|
|
|
|
|
class: "tag_item",
|
|
|
|
|
Link
|
|
|
|
|
{
|
|
|
|
|
class: "{style}",
|
|
|
|
|
to: Page::Blog { tag: tag.clone() },
|
|
|
|
|
"{tag}"
|
|
|
|
|
}
|
|
|
|
|
// a { class: "{style}", "{tag}" }
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
pub fn TagList(children: Element) -> Element
|
|
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
rsx! {
|
2025-09-02 14:00:19 -04:00
|
|
|
document::Link { rel: "stylesheet", href: TAG_CSS }
|
|
|
|
|
|
|
|
|
|
h5
|
|
|
|
|
{
|
|
|
|
|
class: "blog_tag_style",
|
|
|
|
|
ul
|
|
|
|
|
{
|
|
|
|
|
class: "tag_list",
|
|
|
|
|
{children}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
pub fn BlogAuthor() -> Element
|
|
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
rsx! {
|
2025-09-02 14:00:19 -04:00
|
|
|
document::Link { rel: "stylesheet", href: AUTHOR_CSS }
|
|
|
|
|
|
|
|
|
|
section
|
|
|
|
|
{
|
|
|
|
|
class: "blog_author_style",
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[component]
|
2025-09-08 18:39:30 -04:00
|
|
|
pub fn BlogItem(title: String, slug: String, author: String, summary: String,
|
|
|
|
|
tags: Vec<String>)
|
|
|
|
|
-> Element
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
let tags: Vec<(String, String)> =
|
|
|
|
|
tags.iter().map(|t| convert_tag(t)).collect();
|
|
|
|
|
|
|
|
|
|
rsx! {
|
2025-09-02 14:00:19 -04:00
|
|
|
li
|
|
|
|
|
{
|
|
|
|
|
class: "blog_item",
|
|
|
|
|
Link
|
|
|
|
|
{
|
|
|
|
|
to: Page::Post { slug: slug },
|
|
|
|
|
h1 { "{title}" }
|
|
|
|
|
}
|
|
|
|
|
TagList
|
|
|
|
|
{
|
|
|
|
|
for (tag, style) in tags
|
|
|
|
|
{
|
|
|
|
|
TagItem
|
|
|
|
|
{
|
2025-09-08 18:39:30 -04:00
|
|
|
tag: tag.clone(),
|
|
|
|
|
style: style.clone()
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
h4 { "Author: {author}" }
|
|
|
|
|
p { "{summary}" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[component]
|
2025-09-12 13:52:35 -04:00
|
|
|
pub fn BlogList(tags: Signal<HashSet<String>>, children: Element) -> Element
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
let list = use_server_future(move || {
|
|
|
|
|
let categories = tags().iter().cloned().collect();
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
async move { get_blog_list(categories).await }
|
2025-09-08 10:01:02 -04:00
|
|
|
})?;
|
|
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
// The tale depends on the post future resolving.
|
|
|
|
|
let mut summaries: Signal<Option<Vec<Lore>>> = use_signal(|| None);
|
|
|
|
|
use_context_provider(|| summaries);
|
|
|
|
|
|
|
|
|
|
use_effect(move || {
|
|
|
|
|
if let Some(Ok(data)) = &*list.read()
|
|
|
|
|
{
|
|
|
|
|
summaries.set(Some(data.clone()));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
rsx! {
|
2025-09-02 14:00:19 -04:00
|
|
|
document::Link { rel: "stylesheet", href: LIST_CSS }
|
|
|
|
|
|
|
|
|
|
section
|
|
|
|
|
{
|
|
|
|
|
class: "blog_list_style",
|
|
|
|
|
ul
|
|
|
|
|
{
|
|
|
|
|
class: "blog_list",
|
2025-09-08 10:01:02 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
match &*list.read()
|
2025-09-08 18:39:30 -04:00
|
|
|
{
|
|
|
|
|
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..." }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 14:00:19 -04:00
|
|
|
{children}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
2025-09-12 13:52:35 -04:00
|
|
|
pub fn TagSelector(toggled_tags: Signal<HashSet<String>>) -> Element
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
let mut categories = use_signal(Vec::<String>::new);
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
let tags = use_server_future(move || async move { get_tags().await })?;
|
2025-09-08 10:01:02 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
use_effect(move ||
|
|
|
|
|
{
|
|
|
|
|
if let Some(Ok(ref tags_vec)) = *tags.read()
|
|
|
|
|
{
|
|
|
|
|
categories.set(tags_vec.clone());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rsx!
|
|
|
|
|
{
|
2025-09-02 14:00:19 -04:00
|
|
|
document::Link { rel: "stylesheet", href: NAV_CSS }
|
|
|
|
|
|
|
|
|
|
section
|
|
|
|
|
{
|
|
|
|
|
class: "blog_nav_style",
|
|
|
|
|
div
|
|
|
|
|
{
|
|
|
|
|
class: "tag_style",
|
2025-09-12 13:52:35 -04:00
|
|
|
h2 { "Categories" }
|
2025-09-02 14:00:19 -04:00
|
|
|
ul
|
|
|
|
|
{
|
|
|
|
|
class: "tag_list",
|
2025-09-08 18:39:30 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
if categories.read().is_empty()
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
p { "Loading..." }
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
2025-09-12 13:52:35 -04:00
|
|
|
else
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
for tag in categories.read().iter().cloned()
|
|
|
|
|
{
|
|
|
|
|
li
|
|
|
|
|
{
|
|
|
|
|
class: "tag_item",
|
|
|
|
|
span
|
|
|
|
|
{
|
|
|
|
|
class: if toggled_tags.read().is_toggled(&tag)
|
|
|
|
|
{ "tag selected" }
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
{ "tag" },
|
|
|
|
|
|
|
|
|
|
onclick: move |_|
|
|
|
|
|
{ toggled_tags.write().toggle(&tag); },
|
|
|
|
|
|
|
|
|
|
"{strip_tag(&tag)}"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-08 18:39:30 -04:00
|
|
|
}
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
|
2025-09-06 15:05:58 -04:00
|
|
|
#[component]
|
2025-09-12 13:52:35 -04:00
|
|
|
pub fn PostHeaderAuthor() -> Element
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
let author: Signal<Option<Adventurer>> =
|
|
|
|
|
use_context::<Signal<Option<Adventurer>>>();
|
2025-09-08 18:39:30 -04:00
|
|
|
|
|
|
|
|
rsx! {
|
2025-09-12 13:52:35 -04:00
|
|
|
match author()
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
Some(author) =>
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
rsx!
|
2025-09-02 14:00:19 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
h4
|
|
|
|
|
{
|
|
|
|
|
"Author: ",
|
|
|
|
|
a { href: "{author.legend.profile}", "{author.name} @{author.handle}" }
|
|
|
|
|
}
|
2025-09-02 14:00:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-12 13:52:35 -04:00
|
|
|
|
|
|
|
|
None =>
|
|
|
|
|
{
|
|
|
|
|
rsx! { h4 { "Author: Unknown" } }
|
|
|
|
|
}
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
2025-09-12 13:52:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-06 15:05:58 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
#[component]
|
|
|
|
|
pub fn PostHeader() -> Element
|
|
|
|
|
{
|
|
|
|
|
let tale: Signal<Option<Tale>> = use_context::<Signal<Option<Tale>>>();
|
|
|
|
|
let adventurer = use_context::<Signal<Option<Adventurer>>>();
|
|
|
|
|
|
|
|
|
|
let converted_tags = use_memo(move || {
|
|
|
|
|
tale().as_ref()
|
|
|
|
|
.map(|t| {
|
|
|
|
|
t.lore
|
|
|
|
|
.tags
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|tag| convert_tag(tag))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rsx! {
|
|
|
|
|
match tale()
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
Some(tale) =>
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
|
|
|
|
rsx!
|
|
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
h1 { {tale.lore.title} }
|
|
|
|
|
TagList
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
for (tag, style) in converted_tags().iter()
|
|
|
|
|
{
|
|
|
|
|
TagItem
|
|
|
|
|
{
|
|
|
|
|
tag: tag.clone(),
|
|
|
|
|
style: style.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
None =>
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
rsx! { p { "Loading post header..." } }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match adventurer()
|
|
|
|
|
{
|
|
|
|
|
Some(author) =>
|
|
|
|
|
{
|
|
|
|
|
rsx! { PostHeaderAuthor {} }
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None =>
|
|
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
rsx! { p { "Loading author..." } }
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
2025-09-12 13:52:35 -04:00
|
|
|
pub fn BlogPost(slug: Signal<String>, children: Element) -> Element
|
2025-09-06 15:05:58 -04:00
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
// Run a once off server fetch of the unchanging blog data.
|
2025-09-08 18:39:30 -04:00
|
|
|
let post = use_server_future(move || {
|
2025-09-12 13:52:35 -04:00
|
|
|
// Make this reactive so that as the page changes it should rerun this.
|
|
|
|
|
let url_slug = slug();
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
async move { get_blog_post(url_slug).await }
|
2025-09-06 22:48:58 -04:00
|
|
|
})?;
|
2025-09-06 15:05:58 -04:00
|
|
|
|
2025-09-12 13:52:35 -04:00
|
|
|
// The tale depends on the post future resolving.
|
|
|
|
|
let mut tale: Signal<Option<Tale>> = use_signal(|| None);
|
|
|
|
|
use_context_provider(|| tale);
|
|
|
|
|
|
|
|
|
|
let author = use_server_future(move || {
|
|
|
|
|
let handle = match tale()
|
|
|
|
|
{
|
|
|
|
|
Some(data) => data.lore.author,
|
|
|
|
|
|
|
|
|
|
None => "unknown_author_handle".to_string()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async move { get_author(handle).await }
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let mut adventurer = use_signal(|| None);
|
|
|
|
|
use_context_provider(|| adventurer);
|
|
|
|
|
|
|
|
|
|
use_effect(move || {
|
|
|
|
|
if let Some(Ok(data)) = &*post.read()
|
|
|
|
|
{
|
|
|
|
|
tale.set(Some(data.clone()));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
use_effect(move || {
|
|
|
|
|
if let Some(Ok(data)) = &*author.read()
|
|
|
|
|
{
|
|
|
|
|
adventurer.set(Some(data.clone()));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2025-09-08 18:39:30 -04:00
|
|
|
rsx! {
|
2025-09-06 15:05:58 -04:00
|
|
|
document::Link { rel: "stylesheet", href: POST_CSS }
|
|
|
|
|
|
|
|
|
|
article
|
|
|
|
|
{
|
|
|
|
|
class: "blog_post_style",
|
|
|
|
|
|
|
|
|
|
match &*post.read()
|
|
|
|
|
{
|
|
|
|
|
Some(Ok(post)) =>
|
|
|
|
|
{
|
|
|
|
|
rsx!
|
|
|
|
|
{
|
2025-09-12 14:14:23 -04:00
|
|
|
PostHeader {}
|
2025-09-12 13:52:35 -04:00
|
|
|
|
|
|
|
|
//Story {}
|
2025-09-06 15:05:58 -04:00
|
|
|
|
|
|
|
|
div { dangerous_inner_html: "{post.story}" }
|
2025-09-12 13:52:35 -04:00
|
|
|
|
|
|
|
|
//Author {}
|
2025-09-06 15:05:58 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(Err(e)) =>
|
|
|
|
|
{
|
|
|
|
|
rsx!
|
|
|
|
|
{
|
2025-09-12 13:52:35 -04:00
|
|
|
p { "Unable to load desired post: {slug.read()}" }
|
2025-09-06 15:05:58 -04:00
|
|
|
p { "{e}" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None =>
|
|
|
|
|
{
|
|
|
|
|
rsx!
|
|
|
|
|
{
|
|
|
|
|
p { "Loading..." }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 14:00:19 -04:00
|
|
|
{children}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-12 13:52:35 -04:00
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
pub fn TagNav() -> 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",
|
|
|
|
|
h2 { "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..." }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|