Major refactoring on the front end. Dark mode is set to automatic again, in preparation for user preference management.
This commit is contained in:
		
							parent
							
								
									961bb61553
								
							
						
					
					
						commit
						8b04898c25
					
				
					 15 changed files with 442 additions and 412 deletions
				
			
		
							
								
								
									
										2
									
								
								public/styles/tailwind.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/styles/tailwind.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -13,7 +13,8 @@ pub mod web_app_backend {
 | 
			
		|||
    use rocket::response::Redirect;
 | 
			
		||||
    use rocket::{Build, Rocket};
 | 
			
		||||
    use rocket_dyn_templates::{context, Template};
 | 
			
		||||
    use void_fe::void_app::{self, DarkModeProps, PoemRequest, VirtualDom};
 | 
			
		||||
    use void_fe::void_app::{self, VirtualDom};
 | 
			
		||||
    use void_fe::utils::prop_structs::{DarkModeProps, PoemRequest};
 | 
			
		||||
 | 
			
		||||
    #[get("/")]
 | 
			
		||||
    async fn index(cookies: &CookieJar<'_>) -> Template {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								void-fe/src/components.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								void-fe/src/components.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
pub mod void_poem;
 | 
			
		||||
pub mod void_footer;
 | 
			
		||||
pub mod void_title;
 | 
			
		||||
pub mod void_buttons;
 | 
			
		||||
							
								
								
									
										49
									
								
								void-fe/src/components/void_buttons.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								void-fe/src/components/void_buttons.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
use dioxus::prelude::*;
 | 
			
		||||
use crate::utils::prop_structs::ButtonProps;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_family = "wasm")]
 | 
			
		||||
use dioxus_router::Link;
 | 
			
		||||
 | 
			
		||||
pub fn BackToHomePage(cx: Scope) -> Element {
 | 
			
		||||
    #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
    return cx.render(rsx!{
 | 
			
		||||
        a { class: "flex justify-center p-4 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
            href: "/",
 | 
			
		||||
            p {
 | 
			
		||||
                "Back to the homepage"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    return cx.render(rsx!{
 | 
			
		||||
        Link { class: "flex justify-center p-4 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
            to: "/",
 | 
			
		||||
            p {
 | 
			
		||||
                "Back to the homepage"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn NavigationButton(cx: Scope<ButtonProps>) -> Element {
 | 
			
		||||
    let title = cx.props.title.clone();
 | 
			
		||||
    let title_ref = title.as_str();
 | 
			
		||||
    let slug = cx.props.slug.clone();
 | 
			
		||||
    let slug_ref = slug.as_str();
 | 
			
		||||
    #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
    return cx.render(rsx!{
 | 
			
		||||
        a { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
            href: "{slug_ref}",
 | 
			
		||||
            "{title_ref}"
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    return cx.render(rsx!{
 | 
			
		||||
        Link { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
            to: "{slug_ref}",
 | 
			
		||||
            div {
 | 
			
		||||
                dangerous_inner_html: "{title_ref}",
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								void-fe/src/components/void_footer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								void-fe/src/components/void_footer.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
use dioxus::prelude::*;
 | 
			
		||||
 | 
			
		||||
pub fn Footer(cx: Scope) -> Element {
 | 
			
		||||
    cx.render(rsx!{
 | 
			
		||||
        MutantStandardFooter {}
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn MutantStandardFooter(cx: Scope) -> Element {
 | 
			
		||||
    cx.render(rsx!{
 | 
			
		||||
        div { class: "flex p-4 mx-auto max-w-full justify-center text-md text-center ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
            "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								void-fe/src/components/void_poem.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								void-fe/src/components/void_poem.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
use dioxus::prelude::*;
 | 
			
		||||
use crate::utils::helpers;
 | 
			
		||||
use crate::utils::prop_structs::{PoemChildren, PoemData};
 | 
			
		||||
use crate::components::void_buttons::*;
 | 
			
		||||
use crate::components::void_title::*;
 | 
			
		||||
 | 
			
		||||
pub fn PoemList(cx: Scope) -> Element {
 | 
			
		||||
    let poem_list = helpers::get_poem_list();
 | 
			
		||||
    cx.render(rsx! {
 | 
			
		||||
        ul { class: "flex flex-col space-y-4",
 | 
			
		||||
            poem_list.into_iter().map(|p| {
 | 
			
		||||
                let slug = format!("/poems/{}", p.1);
 | 
			
		||||
                rsx!{
 | 
			
		||||
                    NavigationButton { title: p.0, slug: slug }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn MakePoem<'a>(cx: Scope<'a, PoemChildren<'a>>) -> Element {
 | 
			
		||||
    cx.render(rsx! {
 | 
			
		||||
        div { class: "flex-col space-y-4",
 | 
			
		||||
            &cx.props.children
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn GetPoem(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
    let slug = String::from(cx.props.slug.clone().expect("No slug specified."));
 | 
			
		||||
    let (title, content, creation_date) = helpers::get_poem(slug.clone());
 | 
			
		||||
    cx.render(rsx! {
 | 
			
		||||
        Title { title: title, is_html: true }
 | 
			
		||||
        MakePoem{
 | 
			
		||||
            PoemContent { content: content, creation_date: creation_date }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn PoemContent(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
    let content = cx.props.content.clone().expect("No content specified.");
 | 
			
		||||
    let creation_date = cx
 | 
			
		||||
        .props
 | 
			
		||||
        .creation_date
 | 
			
		||||
        .clone()
 | 
			
		||||
        .expect("No creation date specified.");
 | 
			
		||||
    #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    return cx.render(rsx! {
 | 
			
		||||
        div { class: "flex p-2 mx-auto max-w-full justify-center",
 | 
			
		||||
            details { class: "group p-4 max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-4 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center p-2 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                }
 | 
			
		||||
                div { class: "font-nerd flex flex-col space-y-4 py-4", "{content}{creation_date}"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    return cx.render(rsx! {
 | 
			
		||||
        div { class: "flex p-2 mx-auto max-w-full justify-center",
 | 
			
		||||
            details { class: "group p-4 max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-4 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center p-2 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                }
 | 
			
		||||
                div { class: "font-nerd flex flex-col space-y-4 py-4", 
 | 
			
		||||
                    dangerous_inner_html: "{content}{creation_date}",
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								void-fe/src/components/void_title.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								void-fe/src/components/void_title.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
use dioxus::prelude::*;
 | 
			
		||||
use crate::utils::prop_structs::TitleProps;
 | 
			
		||||
 | 
			
		||||
pub fn Title(cx: Scope<TitleProps>) -> Element {
 | 
			
		||||
    let title = cx.props.title.clone();
 | 
			
		||||
    let is_html = cx.props.is_html;
 | 
			
		||||
    cx.render(rsx!{
 | 
			
		||||
        div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
            p { class: "flex flex-row mx-auto max-w-full justify-center text-xl text-center",
 | 
			
		||||
                TitleHtml { title: title, is_html: is_html }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn TitleHtml(cx: Scope<TitleProps>) -> Element {
 | 
			
		||||
    let title = cx.props.title.clone();
 | 
			
		||||
    if cx.props.is_html {
 | 
			
		||||
        #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
 | 
			
		||||
                "{title} "
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
 | 
			
		||||
                div { dangerous_inner_html: "{title}", }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            span {
 | 
			
		||||
                "{title}"
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,102 +4,56 @@
 | 
			
		|||
 | 
			
		||||
#![allow(non_snake_case)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
mod components;
 | 
			
		||||
pub mod utils;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// A module that handles the functions needed
 | 
			
		||||
/// to render the site.
 | 
			
		||||
pub mod void_app {
 | 
			
		||||
    // import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
 | 
			
		||||
    pub use dioxus::prelude::*;
 | 
			
		||||
    use markdown::{self, Options};
 | 
			
		||||
    use rust_embed::RustEmbed;
 | 
			
		||||
    use std::collections::VecDeque;
 | 
			
		||||
    use dioxus_router::{Route, Router, Redirect};
 | 
			
		||||
 | 
			
		||||
    use crate::components::void_buttons::*;
 | 
			
		||||
    use crate::components::void_footer::*;
 | 
			
		||||
    use crate::components::void_poem::*;
 | 
			
		||||
    use crate::components::void_title::*;
 | 
			
		||||
    use crate::utils::helpers;
 | 
			
		||||
    use crate::utils::prop_structs::*;
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "wasm"))]
 | 
			
		||||
    use dioxus_helmet::Helmet;
 | 
			
		||||
    #[cfg(any(target_family = "wasm"))]
 | 
			
		||||
    use dioxus_router::{Link};
 | 
			
		||||
    use dioxus_router::{Link, Route, Router, Redirect};
 | 
			
		||||
    #[cfg(any(target_family = "wasm"))]
 | 
			
		||||
    use dioxus_use_storage::use_local_storage;
 | 
			
		||||
    #[cfg(any(target_family = "wasm"))]
 | 
			
		||||
    use serde::Deserialize;
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    #[derive(Deserialize)]
 | 
			
		||||
    struct CurrentPoem {
 | 
			
		||||
        current: String,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    pub struct PoemRequest {
 | 
			
		||||
        pub slug: String,
 | 
			
		||||
        pub dark_mode: Option<bool>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    pub struct DarkModeProps {
 | 
			
		||||
        pub dark_mode: bool,
 | 
			
		||||
        pub slug: Option<String>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    struct ButtonProps {
 | 
			
		||||
        title: String,
 | 
			
		||||
        slug: String,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    struct PoemData {
 | 
			
		||||
        title: Option<String>,
 | 
			
		||||
        content: Option<String>,
 | 
			
		||||
        creation_date: Option<String>,
 | 
			
		||||
        slug: Option<String>,
 | 
			
		||||
        dark_mode: Option<bool>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Props)]
 | 
			
		||||
    struct PoemChildren<'a> {
 | 
			
		||||
        children: Element<'a>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(RustEmbed)]
 | 
			
		||||
    #[folder = "data/poems"]
 | 
			
		||||
    pub struct Poems;
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    pub fn DioxusApp(cx: Scope) -> Element {
 | 
			
		||||
        // use dioxus_router::Redirect;
 | 
			
		||||
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            Router {
 | 
			
		||||
                Route { to: "/", HomePage { dark_mode: true, } }
 | 
			
		||||
                Route { to: "/poems/", PoemListPage { slug: "".to_string(), dark_mode: true, } }
 | 
			
		||||
                Route { to: "/poems/:slug", PoemPage { slug: "".to_string(), dark_mode: true, } }
 | 
			
		||||
                Route { to: "/", self::HomePage { dark_mode: true, } }
 | 
			
		||||
                Route { to: "/poems", 
 | 
			
		||||
                    PoemListPage { slug: "".to_string(), dark_mode: true, }
 | 
			
		||||
                }
 | 
			
		||||
                Route { to: "/poems/:slug", 
 | 
			
		||||
                    PoemPage { slug: "".to_string(), dark_mode: true, } 
 | 
			
		||||
                }
 | 
			
		||||
                Route { to: "", PageNotFound {} }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    fn RoutePreviousPage(cx: Scope<PoemRequest>) -> Element {
 | 
			
		||||
        let current = String::from(dioxus_router::use_route(cx)
 | 
			
		||||
                .segment("slug")
 | 
			
		||||
                .expect("Slug to know which poem we are at."));
 | 
			
		||||
        log::info!("{}", current.clone());
 | 
			
		||||
        let previous = current.clone();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Redirect { to: "{previous}" }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    fn RouteNextPage(cx: Scope<PoemRequest>) -> Element {
 | 
			
		||||
        let current = String::from(dioxus_router::use_route(cx)
 | 
			
		||||
                .segment("slug")
 | 
			
		||||
                .expect("Slug to know which poem we are at."));
 | 
			
		||||
        let next = current.clone();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Redirect { to: "{next}" }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn PageNotFound(cx: Scope) -> Element {
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            p { "That page doesn't exist, sorry!" }
 | 
			
		||||
| 
						 | 
				
			
			@ -107,132 +61,6 @@ pub mod void_app {
 | 
			
		|||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn MutantStandardFooter(cx: Scope) -> Element {
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            div { class: "flex p-4 mx-auto max-w-full justify-center text-md text-center ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn BackToHomePage(cx: Scope) -> Element {
 | 
			
		||||
        #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            a { class: "flex justify-center p-4 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                href: "/",
 | 
			
		||||
                p {
 | 
			
		||||
                    "Back to the homepage"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            Link { class: "flex justify-center p-4 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                to: "/",
 | 
			
		||||
                p {
 | 
			
		||||
                    "Back to the homepage"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn NavigationButton(cx: Scope<ButtonProps>) -> Element {
 | 
			
		||||
        let title = cx.props.title.clone();
 | 
			
		||||
        let title_ref = title.as_str();
 | 
			
		||||
        let slug = cx.props.slug.clone();
 | 
			
		||||
        let slug_ref = slug.as_str();
 | 
			
		||||
        #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            a { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                href: "{slug_ref}",
 | 
			
		||||
                "{title_ref}"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            Link { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                to: "{slug_ref}",
 | 
			
		||||
                div {
 | 
			
		||||
                    dangerous_inner_html: "{title_ref}",
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn DarkModeButton(cx: Scope<DarkModeProps>) -> Element {
 | 
			
		||||
        let slug = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .slug
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Slug found to know where to redirect after dark mode setting.");
 | 
			
		||||
        let slug_ref = slug.as_str();
 | 
			
		||||
        let dark_mode = cx.props.dark_mode;
 | 
			
		||||
        #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
        return cx.render(rsx! {
 | 
			
		||||
            a { href: "/?dark_mode&callback={slug_ref}",
 | 
			
		||||
                match dark_mode {
 | 
			
		||||
                    true => {
 | 
			
		||||
                        rsx! {
 | 
			
		||||
                            img { src: "/images/white_square_button.png",
 | 
			
		||||
                                alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    false => {
 | 
			
		||||
                        rsx! {
 | 
			
		||||
                            img { src: "/images/black_square_button.png",
 | 
			
		||||
                                alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx! {
 | 
			
		||||
            Link { to: "{slug_ref}/toggle-dark-mode",
 | 
			
		||||
                match dark_mode {
 | 
			
		||||
                    true => {
 | 
			
		||||
                        rsx! {
 | 
			
		||||
                            img { src: "/images/white_square_button.png",
 | 
			
		||||
                                alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    false => {
 | 
			
		||||
                        rsx! {
 | 
			
		||||
                            img { src: "/images/black_square_button.png",
 | 
			
		||||
                                alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn PoemButton(cx: Scope<ButtonProps>) -> Element {
 | 
			
		||||
        let title = cx.props.title.clone();
 | 
			
		||||
        let title_ref = title.as_str();
 | 
			
		||||
        let slug = cx.props.slug.clone();
 | 
			
		||||
        let slug_ref = slug.as_str();
 | 
			
		||||
        #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            a { href: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                    "{title_ref}"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            Link { to: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-2 mr-2 text-xl text-center ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                    dangerous_inner_html: "{title_ref}",
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Renders the app and returns the rendered Element.
 | 
			
		||||
    pub fn HomePage(cx: Scope<DarkModeProps>) -> Element {
 | 
			
		||||
| 
						 | 
				
			
			@ -240,19 +68,11 @@ pub mod void_app {
 | 
			
		|||
        let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        let slug = "".to_string();
 | 
			
		||||
        let oldest_uri = format!("/poems/{}", get_oldest_entry());
 | 
			
		||||
        let latest_uri = format!("/poems/{}", get_latest_entry());
 | 
			
		||||
        let title = "A Letter to the Void".to_string();
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey",
 | 
			
		||||
                div { class: "container space-y-4 mx-auto p-4",
 | 
			
		||||
                    div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                        p { class: "flex flex-row mx-auto max-w-full justify-center text-xl text-center",
 | 
			
		||||
                            "{title}"
 | 
			
		||||
                            DarkModeButton { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Title { title: title, is_html: false }
 | 
			
		||||
                    div { class: "flex p-4 ml-2 mr-2 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                        p { class: "text-lg text-center",
 | 
			
		||||
                            "Welcome, and I hope you enjoy your stay!"
 | 
			
		||||
| 
						 | 
				
			
			@ -267,112 +87,30 @@ pub mod void_app {
 | 
			
		|||
                            "🖤 Alice Icehart Werefox"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    NavigationButton { title: "See Latest Entry".to_string(), slug: latest_uri }
 | 
			
		||||
                    NavigationButton { title: "See Oldest Entry".to_string(), slug: oldest_uri }
 | 
			
		||||
                    NavigationButton { title: "See Latest Entry".to_string(), slug: helpers::get_latest_entry(slug.clone()) }
 | 
			
		||||
                    NavigationButton { title: "See Oldest Entry".to_string(), slug: helpers::get_oldest_entry(slug.clone()) }
 | 
			
		||||
                    NavigationButton { title: "See All Entries".to_string(), slug: "/poems".to_string() }
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                    Footer {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_poem_list() -> Vec<(String, String)> {
 | 
			
		||||
        let mut poem_list = Vec::new();
 | 
			
		||||
        for p in Poems::iter() {
 | 
			
		||||
            let filename = p.to_string();
 | 
			
		||||
            let poem_content = Poems::get(&filename).expect("Found poem {filename:?}");
 | 
			
		||||
            let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref())
 | 
			
		||||
                .expect("Title is valid UT8.")
 | 
			
		||||
                .lines();
 | 
			
		||||
            let title_markdown = poem_to_str.next().expect("No title specified.");
 | 
			
		||||
            let title = markdown::to_html_with_options(title_markdown, &Options::gfm()).unwrap();
 | 
			
		||||
            let slug = String::from(filename.trim_end_matches(".md"));
 | 
			
		||||
            poem_list.push((title.clone(), slug.clone()));
 | 
			
		||||
        }
 | 
			
		||||
        log::trace!("{poem_list:?}");
 | 
			
		||||
        poem_list
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_oldest_entry() -> String {
 | 
			
		||||
        let mut poem_list = VecDeque::from(get_poem_list());
 | 
			
		||||
        poem_list
 | 
			
		||||
            .pop_front()
 | 
			
		||||
            .expect("There is an entry in this list of poems.")
 | 
			
		||||
            .1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_latest_entry() -> String {
 | 
			
		||||
        let mut poem_list = get_poem_list();
 | 
			
		||||
        poem_list
 | 
			
		||||
            .pop()
 | 
			
		||||
            .expect("There is an entry in this list of poems.")
 | 
			
		||||
            .1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_previous_entry(slug: String) -> Option<String> {
 | 
			
		||||
        let poem_list = get_poem_list();
 | 
			
		||||
        poem_list.iter().enumerate().find_map(|(index, p)| {
 | 
			
		||||
            if p.1 == slug {
 | 
			
		||||
                if index != 0 {
 | 
			
		||||
                    Some(poem_list[index - 1].1.clone())
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_next_entry(slug: String) -> Option<String> {
 | 
			
		||||
        let poem_list = get_poem_list();
 | 
			
		||||
        poem_list.iter().enumerate().find_map(|(index, p)| {
 | 
			
		||||
            if p.1 == slug {
 | 
			
		||||
                if index != poem_list.len() - 1 {
 | 
			
		||||
                    Some(poem_list[index + 1].1.clone())
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    /// Renders the app and returns the rendered Element.
 | 
			
		||||
    pub fn PoemListPage(cx: Scope<DarkModeProps>) -> Element {
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey",
 | 
			
		||||
                div { class: "container space-y-4 mx-auto p-4",
 | 
			
		||||
                    div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                        p { class: "flex flex-row mx-auto max-w-full justify-center text-lg text-center",
 | 
			
		||||
                            "A Letter to the Void "
 | 
			
		||||
                            DarkModeButton { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Title { title: "A Letter to the Void".to_string(), is_html: false }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    PoemList {}
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                    Footer {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn PoemList(cx: Scope) -> Element {
 | 
			
		||||
        let poem_list = get_poem_list();
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            ul { class: "flex flex-col space-y-4",
 | 
			
		||||
                poem_list.into_iter().map(|p| {
 | 
			
		||||
                    rsx!{
 | 
			
		||||
                        PoemButton { title: p.0, slug: p.1 }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn PoemPage(cx: Scope<PoemRequest>) -> Element {
 | 
			
		||||
        #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
        let slug = cx.props.slug.clone();
 | 
			
		||||
| 
						 | 
				
			
			@ -382,22 +120,6 @@ pub mod void_app {
 | 
			
		|||
                .segment("slug")
 | 
			
		||||
                .expect("No slug specified."),
 | 
			
		||||
        );
 | 
			
		||||
        let mut oldest_uri = get_oldest_entry();
 | 
			
		||||
        if oldest_uri == slug.to_string() {
 | 
			
		||||
            oldest_uri = format!("/poems/{slug}#");
 | 
			
		||||
        }
 | 
			
		||||
        let previous_uri = match get_previous_entry(slug.clone()) {
 | 
			
		||||
            Some(p) => format!("/poems/{p}"),
 | 
			
		||||
            None => format!("/poems/{slug}#"),
 | 
			
		||||
        };
 | 
			
		||||
        let next_uri = match get_next_entry(slug.clone()) {
 | 
			
		||||
            Some(p) => format!("/poems/{p}"),
 | 
			
		||||
            None => format!("/poems/{slug}#"),
 | 
			
		||||
        };
 | 
			
		||||
        let mut latest_uri = get_latest_entry();
 | 
			
		||||
        if latest_uri == slug.to_string() {
 | 
			
		||||
            latest_uri = format!("{slug}#");
 | 
			
		||||
        }
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
| 
						 | 
				
			
			@ -407,115 +129,17 @@ pub mod void_app {
 | 
			
		|||
            div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey",
 | 
			
		||||
                div { class: "container space-y-4 mx-auto p-4",
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    GetPoem { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                    GetPoem { slug: slug.clone(), dark_mode: dark_mode }
 | 
			
		||||
                    div { class: "grid md:grid-cols-4 md:grid-rows-1 grid-cols-1 grid-rows-4 gap-y-4",
 | 
			
		||||
                        NavigationButton { title: "Oldest".to_string(), slug: oldest_uri }
 | 
			
		||||
                        NavigationButton { title: "Previous".to_string(), slug: previous_uri }
 | 
			
		||||
                        NavigationButton { title: "Next".to_string(), slug: next_uri }
 | 
			
		||||
                        NavigationButton { title: "Latest".to_string(), slug: latest_uri }
 | 
			
		||||
                        NavigationButton { title: "Oldest".to_string(), slug: helpers::get_oldest_entry(slug.clone()) }
 | 
			
		||||
                        NavigationButton { title: "Previous".to_string(), slug: helpers::get_previous_entry(slug.clone()) }
 | 
			
		||||
                        NavigationButton { title: "Next".to_string(), slug: helpers::get_next_entry(slug.clone()) }
 | 
			
		||||
                        NavigationButton { title: "Latest".to_string(), slug: helpers::get_latest_entry(slug.clone()) }
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                    Footer {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn GetPoem(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        // It would be good to implement some kind of "get_poem_data" or "into" functionality for the struct, so I don't have to write all this here.
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Dark mode prop not passed.");
 | 
			
		||||
        let slug = String::from(cx.props.slug.clone().expect("No slug specified."));
 | 
			
		||||
        let filename = String::from(String::from(slug.clone()) + ".md");
 | 
			
		||||
        let creation_date =
 | 
			
		||||
            String::from(String::from("<br>Written on: ") + filename.split("_").next().unwrap());
 | 
			
		||||
        let poem_content = Poems::get(&filename).expect("Found poem {filename:?}");
 | 
			
		||||
        let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref())
 | 
			
		||||
            .expect("Title is valid UT8.")
 | 
			
		||||
            .lines();
 | 
			
		||||
        let poem_title = poem_to_str.next().unwrap();
 | 
			
		||||
        let poem_content = poem_to_str.into_iter().collect::<Vec<&str>>().join("\n");
 | 
			
		||||
        let poem_title_to_html_string =
 | 
			
		||||
            markdown::to_html_with_options(poem_title, &Options::gfm()).unwrap();
 | 
			
		||||
        let poem_content_to_html_string =
 | 
			
		||||
            markdown::to_html_with_options(poem_content.as_str(), &Options::gfm()).unwrap();
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
        MakePoem{
 | 
			
		||||
            RenderPoemTitle { title: poem_title_to_html_string, slug: slug, dark_mode: dark_mode }
 | 
			
		||||
            RenderPoemElement { content: poem_content_to_html_string, creation_date: creation_date }
 | 
			
		||||
        }})
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    fn RenderPoemTitle(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("Slug prop was not passed.");
 | 
			
		||||
        let callback = format!("/poems/{slug}");
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Dark mode prop not passed.");
 | 
			
		||||
        let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
        #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            span { class: "p-4 ml-2 mr-2 flex text-xl text-center ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
 | 
			
		||||
                    "{title} "
 | 
			
		||||
                    DarkModeButton { slug: callback, dark_mode: dark_mode }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx!{
 | 
			
		||||
            span { class: "p-4 ml-2 mr-2 flex text-xl text-center ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
 | 
			
		||||
                    div { dangerous_inner_html: "{title}", }
 | 
			
		||||
                    DarkModeButton { slug: callback, dark_mode: dark_mode }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn RenderPoemElement(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let content = cx.props.content.clone().expect("No content specified.");
 | 
			
		||||
        let creation_date = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .creation_date
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("No creation date specified.");
 | 
			
		||||
        #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
        return cx.render(rsx! {
 | 
			
		||||
            div { class: "flex p-2 mx-auto max-w-full justify-center",
 | 
			
		||||
                details { class: "group p-4 max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-4 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                    summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center p-2 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "font-nerd flex flex-col space-y-4 py-4", "{content}{creation_date}"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        #[cfg(target_family = "wasm")]
 | 
			
		||||
        return cx.render(rsx! {
 | 
			
		||||
            div { class: "flex p-2 mx-auto max-w-full justify-center",
 | 
			
		||||
                details { class: "group p-4 max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-4 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light",
 | 
			
		||||
                    summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center p-2 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-grey-dark dark:text-alice-werefox-grey-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition",
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "font-nerd flex flex-col space-y-4 py-4", 
 | 
			
		||||
                        dangerous_inner_html: "{content}{creation_date}",
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn MakePoem<'a>(cx: Scope<'a, PoemChildren<'a>>) -> Element {
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            div { class: "flex-col space-y-4",
 | 
			
		||||
                &cx.props.children
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								void-fe/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								void-fe/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
pub mod helpers;
 | 
			
		||||
pub mod prop_structs;
 | 
			
		||||
pub mod user_prefs;
 | 
			
		||||
							
								
								
									
										97
									
								
								void-fe/src/utils/helpers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								void-fe/src/utils/helpers.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,97 @@
 | 
			
		|||
use std::collections::VecDeque;
 | 
			
		||||
use crate::void_app::Poems;
 | 
			
		||||
use markdown::Options;
 | 
			
		||||
 | 
			
		||||
pub fn get_poem(slug: String) -> (String, String, String) {
 | 
			
		||||
    let filename = String::from(String::from(slug.clone()) + ".md");
 | 
			
		||||
    let creation_date =
 | 
			
		||||
        String::from(String::from("<br>Written on: ") + filename.split("_").next().unwrap());
 | 
			
		||||
    let poem_content = Poems::get(&filename).expect("Found poem {filename:?}");
 | 
			
		||||
    let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref())
 | 
			
		||||
        .expect("Title is valid UT8.")
 | 
			
		||||
        .lines();
 | 
			
		||||
    let poem_title = poem_to_str.next().unwrap();
 | 
			
		||||
    let poem_content = poem_to_str.into_iter().collect::<Vec<&str>>().join("\n");
 | 
			
		||||
    let poem_title_to_html_string =
 | 
			
		||||
        markdown::to_html_with_options(poem_title, &Options::gfm()).unwrap();
 | 
			
		||||
    let poem_content_to_html_string =
 | 
			
		||||
        markdown::to_html_with_options(poem_content.as_str(), &Options::gfm()).unwrap();
 | 
			
		||||
    (poem_title_to_html_string, poem_content_to_html_string, creation_date)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_poem_list() -> Vec<(String, String)> {
 | 
			
		||||
    let mut poem_list = Vec::new();
 | 
			
		||||
    for p in Poems::iter() {
 | 
			
		||||
        let filename = p.to_string();
 | 
			
		||||
        let poem_content = Poems::get(&filename).expect("Found poem {filename:?}");
 | 
			
		||||
        let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref())
 | 
			
		||||
            .expect("Title is valid UT8.")
 | 
			
		||||
            .lines();
 | 
			
		||||
        let title_markdown = poem_to_str.next().expect("No title specified.");
 | 
			
		||||
        let title = markdown::to_html_with_options(title_markdown, &Options::gfm()).unwrap();
 | 
			
		||||
        let slug = String::from(filename.trim_end_matches(".md"));
 | 
			
		||||
        poem_list.push((title.clone(), slug.clone()));
 | 
			
		||||
    }
 | 
			
		||||
    log::trace!("{poem_list:?}");
 | 
			
		||||
    poem_list
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_oldest_entry(current: String) -> String {
 | 
			
		||||
    let mut poem_list = VecDeque::from(get_poem_list());
 | 
			
		||||
    let oldest = poem_list
 | 
			
		||||
        .pop_front()
 | 
			
		||||
        .expect("There is an entry in this list of poems.")
 | 
			
		||||
        .1;
 | 
			
		||||
    if current == oldest {
 | 
			
		||||
        return format!("/poems/{current}#");
 | 
			
		||||
    }
 | 
			
		||||
    format!("/poems/{oldest}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_latest_entry(current: String) -> String {
 | 
			
		||||
    let mut poem_list = get_poem_list();
 | 
			
		||||
    let latest = poem_list
 | 
			
		||||
        .pop()
 | 
			
		||||
        .expect("There is an entry in this list of poems.")
 | 
			
		||||
        .1;
 | 
			
		||||
    if current == latest {
 | 
			
		||||
        return format!("/poems/{current}#");
 | 
			
		||||
    }
 | 
			
		||||
    format!("/poems/{latest}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_previous_entry(current: String) -> String {
 | 
			
		||||
    let poem_list = get_poem_list();
 | 
			
		||||
    match poem_list.iter().enumerate().find_map(|(index, p)| {
 | 
			
		||||
        if p.1 == current {
 | 
			
		||||
            if index != 0 {
 | 
			
		||||
                Some(poem_list[index - 1].1.clone())
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }) {
 | 
			
		||||
        Some(entry) => format!("/poems/{entry}"),
 | 
			
		||||
        None => format!("/poems/{current}"),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_next_entry(current: String) -> String {
 | 
			
		||||
    let poem_list = get_poem_list();
 | 
			
		||||
    match poem_list.iter().enumerate().find_map(|(index, p)| {
 | 
			
		||||
        if p.1 == current {
 | 
			
		||||
            if index != poem_list.len() - 1 {
 | 
			
		||||
                Some(poem_list[index + 1].1.clone())
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }) {
 | 
			
		||||
        Some(entry) => format!("/poems/{entry}"),
 | 
			
		||||
        None => format!("/poems/{current}"),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								void-fe/src/utils/prop_structs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								void-fe/src/utils/prop_structs.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
use crate::void_app::{Element, Props};
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Props)]
 | 
			
		||||
pub struct PoemRequest {
 | 
			
		||||
    pub slug: String,
 | 
			
		||||
    pub dark_mode: Option<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Props)]
 | 
			
		||||
pub struct DarkModeProps {
 | 
			
		||||
    pub dark_mode: bool,
 | 
			
		||||
    pub slug: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Props)]
 | 
			
		||||
pub struct TitleProps {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub is_html: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Props)]
 | 
			
		||||
pub struct ButtonProps {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub slug: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Props)]
 | 
			
		||||
pub struct PoemData {
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub content: Option<String>,
 | 
			
		||||
    pub creation_date: Option<String>,
 | 
			
		||||
    pub slug: Option<String>,
 | 
			
		||||
    pub dark_mode: Option<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Props)]
 | 
			
		||||
pub struct PoemChildren<'a> {
 | 
			
		||||
    pub children: Element<'a>,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								void-fe/src/utils/user_prefs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								void-fe/src/utils/user_prefs.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,91 @@
 | 
			
		|||
pub struct UserPrefs {
 | 
			
		||||
    theme: ThemePref,
 | 
			
		||||
    font: FontPref,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum ThemePref {
 | 
			
		||||
    Light,
 | 
			
		||||
    Dark,
 | 
			
		||||
    Auto,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum FontPref {
 | 
			
		||||
    NerdFont,
 | 
			
		||||
    OpenDyslexic,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl UserPrefs {
 | 
			
		||||
    pub fn new() -> UserPrefs {
 | 
			
		||||
        UserPrefs {
 | 
			
		||||
            theme: ThemePref::Auto,
 | 
			
		||||
            font: FontPref::OpenDyslexic,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_theme(&self, is_button: bool) -> String {
 | 
			
		||||
        match &self.theme {
 | 
			
		||||
            ThemePref::Light => Self::light_theme_classes(is_button),
 | 
			
		||||
            ThemePref::Dark => Self::dark_theme_classes(is_button),
 | 
			
		||||
            ThemePref::Auto => Self::auto_theme_classes(is_button),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_theme(mut self, theme: ThemePref) {
 | 
			
		||||
        self.theme = theme;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_font(&self) -> String {
 | 
			
		||||
        match &self.font {
 | 
			
		||||
            FontPref::OpenDyslexic => "...".to_string(),
 | 
			
		||||
            FontPref::NerdFont => "...".to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_font(mut self, font: FontPref) {
 | 
			
		||||
        self.font = font;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_prefs(&self, is_button: bool) -> (String, String) {
 | 
			
		||||
        let theme = self.get_theme(is_button).clone();
 | 
			
		||||
        let font = self.get_font().clone();
 | 
			
		||||
        (theme, font)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_prefs(mut self, prefs: UserPrefs) {
 | 
			
		||||
        self.theme = prefs.theme;
 | 
			
		||||
        self.font = prefs.font;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn light_theme_classes(is_button: bool) -> String {
 | 
			
		||||
        let base_classes = "bg-alice-werefox-grey-lightest ring-alice-werefox-red-dark text-alice-werefox-grey-dark"
 | 
			
		||||
            .to_string();
 | 
			
		||||
        let button_classes = "hover:text-alice-werefox-blue-dark hover:ring-alice-werefox-blue hover:animate-yip transition".to_string();
 | 
			
		||||
        if is_button {
 | 
			
		||||
            return format!("{base_classes} {button_classes}");
 | 
			
		||||
        }
 | 
			
		||||
        base_classes
 | 
			
		||||
    }
 | 
			
		||||
    fn dark_theme_classes(is_button: bool) -> String {
 | 
			
		||||
        let base_classes =
 | 
			
		||||
            "bg-alice-werefox-grey-dark ring-alice-werefox-red text-alice-werefox-grey-light"
 | 
			
		||||
                .to_string();
 | 
			
		||||
        let button_classes = "hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue hover:animate-yip transition".to_string();
 | 
			
		||||
        if is_button {
 | 
			
		||||
            return format!("{base_classes} {button_classes}");
 | 
			
		||||
        }
 | 
			
		||||
        base_classes
 | 
			
		||||
    }
 | 
			
		||||
    fn auto_theme_classes(is_button: bool) -> String {
 | 
			
		||||
        format!(
 | 
			
		||||
            "{}{}",
 | 
			
		||||
            Self::light_theme_classes(is_button)
 | 
			
		||||
                .split(" ")
 | 
			
		||||
                .map(|c| if c == "transition" { "" } else { c })
 | 
			
		||||
                .collect::<String>(),
 | 
			
		||||
            Self::dark_theme_classes(is_button)
 | 
			
		||||
                .split(" ")
 | 
			
		||||
                .map(|c| format!(" dark:{c}"))
 | 
			
		||||
                .collect::<String>()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +105,5 @@ module.exports = {
 | 
			
		|||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  darkMode: 'class',
 | 
			
		||||
  plugins: [],
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue