previous and next entries, major refactoring on Dioxus frontend, more reliable routing on backend, some minor style adjustments.
This commit is contained in:
		
							parent
							
								
									c5e4cd605d
								
							
						
					
					
						commit
						4830f0a600
					
				
					 9 changed files with 480 additions and 285 deletions
				
			
		
							
								
								
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
FROM rust:alpine
 | 
			
		||||
 | 
			
		||||
WORKDIR /usr/src/app
 | 
			
		||||
 | 
			
		||||
RUN apk add git musl-dev
 | 
			
		||||
 | 
			
		||||
COPY public/ public/
 | 
			
		||||
COPY void-be/ void-be/
 | 
			
		||||
 | 
			
		||||
# We don't need all the front end directory files
 | 
			
		||||
RUN mkdir void-fe
 | 
			
		||||
COPY void-fe/src/ void-fe/src/
 | 
			
		||||
COPY void-fe/data/ void-fe/data/
 | 
			
		||||
COPY void-fe/Cargo.toml void-fe/Cargo.toml
 | 
			
		||||
 | 
			
		||||
COPY src/ src/
 | 
			
		||||
COPY templates/ templates/
 | 
			
		||||
COPY Cargo.toml .
 | 
			
		||||
COPY Rocket.toml .
 | 
			
		||||
 | 
			
		||||
RUN cargo install --config "net.git-fetch-with-cli=true" --path .
 | 
			
		||||
 | 
			
		||||
ENV RUST_ADDRESS=0.0.0.0
 | 
			
		||||
 | 
			
		||||
CMD ["cargo", "run", "--release"]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
[default]
 | 
			
		||||
port = 3469
 | 
			
		||||
port = 8345
 | 
			
		||||
# workers = 16
 | 
			
		||||
# max_blocking = 512
 | 
			
		||||
# keep_alive = 5
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
---
 | 
			
		||||
version: "3"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  app:
 | 
			
		||||
    image: void-werefox-cafe:latest
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8345:8345"
 | 
			
		||||
							
								
								
									
										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
											
										
									
								
							| 
						 | 
				
			
			@ -8,24 +8,34 @@ extern crate rocket;
 | 
			
		|||
/// A module that handles the backend for the site.
 | 
			
		||||
pub mod web_app_backend {
 | 
			
		||||
 | 
			
		||||
    use std::path::{PathBuf, Path};
 | 
			
		||||
 | 
			
		||||
    use rocket::fs::FileServer;
 | 
			
		||||
    use rocket::http::{Cookie, CookieJar};
 | 
			
		||||
    use rocket::response::Redirect;
 | 
			
		||||
    use rocket::{Build, Rocket};
 | 
			
		||||
    use rocket_dyn_templates::{context, Template};
 | 
			
		||||
    use void_fe::void_app::{self, PoemRequest, VirtualDom, HomeProps};
 | 
			
		||||
    use void_fe::void_app::{self, DarkModeProps, PoemRequest, VirtualDom};
 | 
			
		||||
 | 
			
		||||
    #[get("/")]
 | 
			
		||||
    async fn index(cookies: &CookieJar<'_>) -> Template {
 | 
			
		||||
        let dark_mode = match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => true,
 | 
			
		||||
            Some(c) => {
 | 
			
		||||
                if c.value() == "true" {
 | 
			
		||||
                    true
 | 
			
		||||
                } else if c.value() == "false" {
 | 
			
		||||
                    false
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => false,
 | 
			
		||||
        };
 | 
			
		||||
        let mut vdom = VirtualDom::new_with_props(void_app::HomePage, HomeProps {
 | 
			
		||||
            dark_mode: dark_mode,
 | 
			
		||||
        });
 | 
			
		||||
        let mut vdom = VirtualDom::new_with_props(
 | 
			
		||||
            void_app::HomePage,
 | 
			
		||||
            DarkModeProps {
 | 
			
		||||
                slug: Some(String::new()),
 | 
			
		||||
                dark_mode,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        let _ = vdom.rebuild();
 | 
			
		||||
        let output = dioxus_ssr::render(&vdom);
 | 
			
		||||
        Template::render(
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +52,7 @@ pub mod web_app_backend {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/?set-dark-mode")]
 | 
			
		||||
    #[get("/toggle-dark-mode")]
 | 
			
		||||
    async fn dark_mode_root(cookies: &CookieJar<'_>) -> Redirect {
 | 
			
		||||
        match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => cookies.remove(Cookie::named("dark-mode")),
 | 
			
		||||
| 
						 | 
				
			
			@ -51,15 +61,27 @@ pub mod web_app_backend {
 | 
			
		|||
        Redirect::to("/")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/poems")]
 | 
			
		||||
    #[get("/")]
 | 
			
		||||
    async fn poem_list(cookies: &CookieJar<'_>) -> Template {
 | 
			
		||||
        let dark_mode = match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => true,
 | 
			
		||||
            Some(c) => {
 | 
			
		||||
                if c.value() == "true" {
 | 
			
		||||
                    true
 | 
			
		||||
                } else if c.value() == "false" {
 | 
			
		||||
                    false
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => false,
 | 
			
		||||
        };
 | 
			
		||||
        let mut vdom = VirtualDom::new_with_props(void_app::PoemListPage, HomeProps {
 | 
			
		||||
            dark_mode: dark_mode,
 | 
			
		||||
        });
 | 
			
		||||
        let mut vdom = VirtualDom::new_with_props(
 | 
			
		||||
            void_app::PoemListPage,
 | 
			
		||||
            DarkModeProps {
 | 
			
		||||
                slug: Some(String::from("/poems")),
 | 
			
		||||
                dark_mode,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        let _ = vdom.rebuild();
 | 
			
		||||
        let output = dioxus_ssr::render(&vdom);
 | 
			
		||||
        Template::render(
 | 
			
		||||
| 
						 | 
				
			
			@ -76,45 +98,65 @@ pub mod web_app_backend {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/poems?set-dark-mode")]
 | 
			
		||||
    #[get("/toggle-dark-mode")]
 | 
			
		||||
    async fn poems_dark_mode(cookies: &CookieJar<'_>) -> Redirect {
 | 
			
		||||
        dark_mode(cookies, PathBuf::new()).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #[get("/poems/<entry>?set-dark-mode")]
 | 
			
		||||
    async fn entry_dark_mode(cookies: &CookieJar<'_>, entry: PathBuf) -> Redirect {
 | 
			
		||||
        dark_mode(cookies, entry).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn dark_mode(cookies: &CookieJar<'_>, entry: PathBuf) -> Redirect {
 | 
			
		||||
        match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => cookies.remove(Cookie::named("dark-mode")),
 | 
			
		||||
            None => cookies.add(Cookie::new("dark-mode", "true")),
 | 
			
		||||
        };
 | 
			
		||||
        let path_str = String::from(Path::new("/poems/").join(entry).to_str().expect("valid path"));
 | 
			
		||||
        let _angy = path_str.as_str().to_owned();
 | 
			
		||||
        Redirect::to(path_str.as_str().to_owned().clone())
 | 
			
		||||
        Redirect::to("/poems/")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/poems/latest")]
 | 
			
		||||
    #[get("/<entry>/toggle-dark-mode")]
 | 
			
		||||
    async fn entry_dark_mode(cookies: &CookieJar<'_>, entry: &str) -> Redirect {
 | 
			
		||||
        match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => cookies.remove(Cookie::named("dark-mode")),
 | 
			
		||||
            None => cookies.add(Cookie::new("dark-mode", "true")),
 | 
			
		||||
        };
 | 
			
		||||
        Redirect::to(format!("/poems/{entry}"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/")]
 | 
			
		||||
    async fn latest_entry() -> Redirect {
 | 
			
		||||
        let slug = void_app::get_latest_entry();
 | 
			
		||||
        let uri = String::from("/poems/".to_string() + slug.as_str());
 | 
			
		||||
        Redirect::to(uri)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/poems/oldest")]
 | 
			
		||||
    #[get("/")]
 | 
			
		||||
    async fn oldest_entry() -> Redirect {
 | 
			
		||||
        let slug = void_app::get_oldest_entry();
 | 
			
		||||
        let uri = String::from("/poems/".to_string() + slug.as_str());
 | 
			
		||||
        Redirect::to(uri)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/poems/<entry>")]
 | 
			
		||||
    #[get("/?<current>")]
 | 
			
		||||
    async fn previous_entry(current: &str) -> Redirect {
 | 
			
		||||
        let previous =
 | 
			
		||||
            void_app::get_previous_entry(current.to_string()).expect("There is a previous entry.");
 | 
			
		||||
        let uri = String::from("/poems/".to_string() + previous.as_str());
 | 
			
		||||
        Redirect::to(uri)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/?<current>")]
 | 
			
		||||
    async fn next_entry(current: &str) -> Redirect {
 | 
			
		||||
        let next = void_app::get_next_entry(current.to_string()).expect("There is a next entry.");
 | 
			
		||||
        let uri = String::from("/poems/".to_string() + next.as_str());
 | 
			
		||||
        Redirect::to(uri)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[get("/<entry>")]
 | 
			
		||||
    async fn poem(cookies: &CookieJar<'_>, entry: &str) -> Template {
 | 
			
		||||
        let dark_mode = match cookies.get("dark-mode") {
 | 
			
		||||
            Some(_) => true,
 | 
			
		||||
            Some(c) => {
 | 
			
		||||
                if c.value() == "true" {
 | 
			
		||||
                    true
 | 
			
		||||
                } else if c.value() == "false" {
 | 
			
		||||
                    false
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => false,
 | 
			
		||||
        };
 | 
			
		||||
        let mut vdom = VirtualDom::new_with_props(
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +188,15 @@ pub mod web_app_backend {
 | 
			
		|||
            .mount("/images", FileServer::from("public/images"))
 | 
			
		||||
            .mount("/styles", FileServer::from("public/styles"))
 | 
			
		||||
            .mount("/fonts", FileServer::from("public/fonts"))
 | 
			
		||||
            .mount("/", routes![dark_mode_root, index, poems_dark_mode, poem_list, latest_entry, oldest_entry, entry_dark_mode, poem])
 | 
			
		||||
            .mount("/poems/oldest", routes![oldest_entry])
 | 
			
		||||
            .mount("/poems/previous", routes![previous_entry])
 | 
			
		||||
            .mount("/poems/next", routes![next_entry])
 | 
			
		||||
            .mount("/poems/latest", routes![latest_entry])
 | 
			
		||||
            .mount(
 | 
			
		||||
                "/poems",
 | 
			
		||||
                routes![poems_dark_mode, poem_list, entry_dark_mode, poem],
 | 
			
		||||
            )
 | 
			
		||||
            .mount("/", routes![dark_mode_root, index])
 | 
			
		||||
            .attach(Template::fairing())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								void-fe/.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								void-fe/.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
{
 | 
			
		||||
    "rust-analyzer.linkedProjects": [
 | 
			
		||||
        "./Cargo.toml"
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								void-fe/src/components/MakePoem.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								void-fe/src/components/MakePoem.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -9,9 +9,9 @@
 | 
			
		|||
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 std::collections::VecDeque;
 | 
			
		||||
    use markdown::{self, Options};
 | 
			
		||||
    use rust_embed::RustEmbed;
 | 
			
		||||
    use std::collections::VecDeque;
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "wasm"))]
 | 
			
		||||
    use dioxus_helmet::Helmet;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +25,15 @@ pub mod void_app {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    pub struct HomeProps {
 | 
			
		||||
    pub struct DarkModeProps {
 | 
			
		||||
        pub dark_mode: bool,
 | 
			
		||||
        pub slug: Option<String>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
    struct ButtonProps {
 | 
			
		||||
        title: String,
 | 
			
		||||
        slug: String,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Props)]
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +45,11 @@ pub mod void_app {
 | 
			
		|||
        dark_mode: Option<bool>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Props)]
 | 
			
		||||
    struct PoemChildren<'a> {
 | 
			
		||||
        children: Element<'a>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(RustEmbed)]
 | 
			
		||||
    #[folder = "data/poems"]
 | 
			
		||||
    pub struct Poems;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,39 +76,173 @@ pub mod void_app {
 | 
			
		|||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn MutantStandardFooter(cx: Scope) -> Element {
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            div { class: "flex p-4 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"
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
    fn BackToHomePage(cx: Scope) -> Element {
 | 
			
		||||
        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")]
 | 
			
		||||
    fn BackToHomePage(cx: Scope) -> Element {
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Link { to: "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(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
    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();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            a { class: "flex mx-auto max-w-full justify-center p-4 ml-6 mr-6 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 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")]
 | 
			
		||||
    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();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Link { class: "flex mx-auto max-w-full justify-center p-4 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 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,
 | 
			
		||||
                title_ref
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "windows", target_family = "unix"))]
 | 
			
		||||
    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;
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            a { href: "{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.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    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;
 | 
			
		||||
        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.",
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    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();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            a { href: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-6 mr-6 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")]
 | 
			
		||||
    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();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Link { to: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-6 mr-6 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<HomeProps>) -> Element {
 | 
			
		||||
    pub fn HomePage(cx: Scope<DarkModeProps>) -> Element {
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
 | 
			
		||||
        // let slug_ref = slug.as_str();
 | 
			
		||||
        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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                    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",
 | 
			
		||||
                            "A Letter to the Void "
 | 
			
		||||
                            a { class: "max-h-sm",
 | 
			
		||||
                                href: "?set-dark-mode",
 | 
			
		||||
                                match dark_mode {
 | 
			
		||||
                                    true => {
 | 
			
		||||
                                        rsx! {
 | 
			
		||||
                                            img { class: "",
 | 
			
		||||
                                                src: "/images/white_square_button.png",
 | 
			
		||||
                                                alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    },
 | 
			
		||||
                                    false => {
 | 
			
		||||
                                        rsx! {
 | 
			
		||||
                                            img { class: "",
 | 
			
		||||
                                                src: "/images/black_square_button.png",
 | 
			
		||||
                                                alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    },
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            DarkModeButton { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex p-4 ml-6 mr-6 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                    div { class: "flex p-4 ml-6 mr-6 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!"
 | 
			
		||||
                            "\"A Letter to the Void\" is a passion project of mine in which I wrote poems about my past life experiences, present, and hopes for the future throughout my transition."
 | 
			
		||||
| 
						 | 
				
			
			@ -108,39 +254,10 @@ pub mod void_app {
 | 
			
		|||
                            "🖤 Alice Icehart Werefox"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex mx-auto max-w-full justify-center",
 | 
			
		||||
                        a { 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-red-dark dark:text-alice-werefox-red-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: "/poems/latest",
 | 
			
		||||
                                div { class: "",
 | 
			
		||||
                                    p { class: "text-xl text-center",
 | 
			
		||||
                                        "See Latest Entry"
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex mx-auto max-w-full justify-center",
 | 
			
		||||
                        a { 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-red-dark dark:text-alice-werefox-red-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: "/poems/oldest",
 | 
			
		||||
                                div { class: "",
 | 
			
		||||
                                    p { class: "text-xl text-center",
 | 
			
		||||
                                        "See Oldest Entry"
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex mx-auto max-w-full justify-center",
 | 
			
		||||
                        a { 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-red-dark dark:text-alice-werefox-red-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: "/poems",
 | 
			
		||||
                                div { class: "",
 | 
			
		||||
                                    p { class: "text-xl text-center",
 | 
			
		||||
                                        "See All Entries"
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex p-4 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                        "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
                    }
 | 
			
		||||
                    NavigationButton { title: "See Earliest Entry".to_string(), slug: "/poems/latest".to_string() }
 | 
			
		||||
                    NavigationButton { title: "See Oldest Entry".to_string(), slug: "/poems/oldest".to_string() }
 | 
			
		||||
                    NavigationButton { title: "See All Entries".to_string(), slug: "/poems".to_string() }
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -165,66 +282,66 @@ pub mod void_app {
 | 
			
		|||
 | 
			
		||||
    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
 | 
			
		||||
        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
 | 
			
		||||
        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<HomeProps>) -> 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                    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 "
 | 
			
		||||
                            a { class: "max-h-sm",
 | 
			
		||||
                                href: "?set-dark-mode",
 | 
			
		||||
                                match dark_mode {
 | 
			
		||||
                                    true => {
 | 
			
		||||
                                        rsx! {
 | 
			
		||||
                                            img { class: "",
 | 
			
		||||
                                                src: "/images/white_square_button.png",
 | 
			
		||||
                                                alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    },
 | 
			
		||||
                                    false => {
 | 
			
		||||
                                        rsx! {
 | 
			
		||||
                                            img { class: "",
 | 
			
		||||
                                                src: "/images/black_square_button.png",
 | 
			
		||||
                                                alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    },
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            DarkModeButton { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    a { class: "flex 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-red-dark dark:text-alice-werefox-red-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: "/",
 | 
			
		||||
                            div { class: "mx-auto max-w-full justify-center",
 | 
			
		||||
                                p { class: "text-lg text-center",
 | 
			
		||||
                                    "Back to the homepage"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    PoemList {}
 | 
			
		||||
                    a { class: "flex 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-red-dark dark:text-alice-werefox-red-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: "/",
 | 
			
		||||
                            div { class: "mx-auto max-w-full justify-center",
 | 
			
		||||
                                p { class: "text-lg text-center",
 | 
			
		||||
                                    "Back to the homepage"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "flex p-4 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                        "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -233,10 +350,10 @@ pub mod void_app {
 | 
			
		|||
    fn PoemList(cx: Scope) -> Element {
 | 
			
		||||
        let poem_list = get_poem_list();
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            ul { class: "space-y-4",
 | 
			
		||||
            ul { class: "flex flex-col space-y-4",
 | 
			
		||||
                poem_list.into_iter().map(|p| {
 | 
			
		||||
                    rsx!{
 | 
			
		||||
                        div { PoemButton { title: p.0, slug: p.1 } }
 | 
			
		||||
                        PoemButton { title: p.0, slug: p.1 }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -245,31 +362,41 @@ pub mod void_app {
 | 
			
		|||
 | 
			
		||||
    #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    pub fn PoemPage(cx: Scope<PoemRequest>) -> Element {
 | 
			
		||||
        let slug = String::from(cx.props.slug.clone());
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        let slug = cx.props.slug.clone();
 | 
			
		||||
        let mut oldest_uri = "#".to_string();
 | 
			
		||||
        if get_oldest_entry() != slug.to_string() {
 | 
			
		||||
            oldest_uri = format!("/poems/oldest");
 | 
			
		||||
        }
 | 
			
		||||
        let mut previous_uri = "#".to_string();
 | 
			
		||||
        if let Some(_) = get_previous_entry(slug.clone()) {
 | 
			
		||||
            previous_uri = format!("/poems/previous/?current={slug}");
 | 
			
		||||
        }
 | 
			
		||||
        let mut next_uri = "#".to_string();
 | 
			
		||||
        if let Some(_) = get_next_entry(slug.clone()) {
 | 
			
		||||
            next_uri = format!("/poems/next/?current={slug}");
 | 
			
		||||
        }
 | 
			
		||||
        let mut latest_uri = "#".to_string();
 | 
			
		||||
        if get_latest_entry() != slug.to_string() {
 | 
			
		||||
            latest_uri = format!("/poems/latest");
 | 
			
		||||
        }
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Dark mode prop not passed.");
 | 
			
		||||
        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",
 | 
			
		||||
                    a { class: "flex 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-red-dark dark:text-alice-werefox-red-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: "/",
 | 
			
		||||
                            div { class: "mx-auto max-w-full justify-center",
 | 
			
		||||
                                p { class: "text-lg text-center",
 | 
			
		||||
                                    "Back to the homepage"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                    GetPoem { slug: slug, dark_mode: dark_mode } 
 | 
			
		||||
                    a { class: "flex 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-red-dark dark:text-alice-werefox-red-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: "/",
 | 
			
		||||
                            div { class: "mx-auto max-w-full justify-center",
 | 
			
		||||
                                p { class: "text-lg text-center",
 | 
			
		||||
                                    "Back to the homepage"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                    div { class: "p-4 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                        "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    GetPoem { slug: slug, 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 }
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -282,61 +409,56 @@ pub mod void_app {
 | 
			
		|||
                .segment("slug")
 | 
			
		||||
                .expect("No slug specified."),
 | 
			
		||||
        );
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Dark mode prop not passed.");
 | 
			
		||||
        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",
 | 
			
		||||
                    Link { to: "/"
 | 
			
		||||
                        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-red-dark dark:text-alice-werefox-red-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",
 | 
			
		||||
                            p { class: "text-lg text-center",
 | 
			
		||||
                                "Back to the homepage"
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    GetPoem { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                    div { class: "p-4 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                        "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License"
 | 
			
		||||
                    }
 | 
			
		||||
                    BackToHomePage {}
 | 
			
		||||
                    MutantStandardFooter {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    fn PoemButton(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("No slug specified.");
 | 
			
		||||
        let slug_ref = slug.as_str();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            a { href: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-6 mr-6 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-red-dark dark:text-alice-werefox-red-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: "text-xl text-center", "{title}" }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    // fn PoemButton(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
    //     let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
    //     let slug = cx.props.slug.clone().expect("No slug specified.");
 | 
			
		||||
    //     let class = String::from("p-4 ml-6 mr-6 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");
 | 
			
		||||
    //     cx.render(rsx!{
 | 
			
		||||
    //         PoemButtonLink { title: title, slug: slug, classes: class }
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    fn PoemButton(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("No slug specified.");
 | 
			
		||||
        let slug_ref = slug.as_str();
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            Link { to: "/poems/{slug_ref}",
 | 
			
		||||
                li { class: "p-4 ml-6 mr-6 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-red-dark dark:text-alice-werefox-red-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: "mx-auto max-w-fit flex justify-center text-lg text-center", 
 | 
			
		||||
                        dangerous_inner_html: "{title}" 
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    // fn PoemButton(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
    //     let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
    //     let slug = cx.props.slug.clone().expect("No slug specified.");
 | 
			
		||||
    //     let slug_ref = slug.as_str();
 | 
			
		||||
    //     cx.render(rsx!{
 | 
			
		||||
    //         Link { to: "/poems/{slug_ref}",
 | 
			
		||||
    //             li { class: "p-4 ml-6 mr-6 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: "mx-auto max-w-fit flex justify-center text-lg text-center",
 | 
			
		||||
    //                     dangerous_inner_html: "{title}"
 | 
			
		||||
    //                 }
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    fn GetPoem(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        // 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 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:?}");
 | 
			
		||||
| 
						 | 
				
			
			@ -349,82 +471,75 @@ pub mod void_app {
 | 
			
		|||
            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{ title: poem_title_to_html_string, content: poem_content_to_html_string, creation_date: creation_date, slug: slug, dark_mode: dark_mode } }
 | 
			
		||||
        )
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
        MakePoem{
 | 
			
		||||
            // data: PoemData{
 | 
			
		||||
            //     title: None,
 | 
			
		||||
            //     content: Some(poem_content_to_html_string.clone()),
 | 
			
		||||
            //     creation_date: Some(creation_date.clone()),
 | 
			
		||||
            //     slug: Some(slug.clone()),
 | 
			
		||||
            //     dark_mode: Some(dark_mode),
 | 
			
		||||
            // },
 | 
			
		||||
            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"))]
 | 
			
		||||
    // #[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 dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        let dark_mode = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .dark_mode
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("Dark mode prop not passed.");
 | 
			
		||||
        let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            span { class: "p-4 ml-4 mr-4 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
            span { class: "p-4 ml-4 mr-4 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} "
 | 
			
		||||
                    a { class: "",
 | 
			
		||||
                        href: "/poems/{slug}?set-dark-mode",
 | 
			
		||||
                        match dark_mode {
 | 
			
		||||
                            true => {
 | 
			
		||||
                                rsx! {
 | 
			
		||||
                                    img { class: "",
 | 
			
		||||
                                        src: "/images/white_square_button.png",
 | 
			
		||||
                                        alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            false => {
 | 
			
		||||
                                rsx! {
 | 
			
		||||
                                    img { class: "",
 | 
			
		||||
                                        src: "/images/black_square_button.png",
 | 
			
		||||
                                        alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                        }
 | 
			
		||||
                    } 
 | 
			
		||||
                    DarkModeButton { slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(target_family = "wasm")]
 | 
			
		||||
    fn RenderPoemTitle(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let slug = cx.props.slug.clone().expect("Slug prop was not passed.");
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        let title = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .title
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("This poem has an empty title.");
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
            p { class: "mx-auto max-w-fit flex flex-row justify-center bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light p-4",
 | 
			
		||||
                dangerous_inner_html: "{title} ",
 | 
			
		||||
                a { class: "",
 | 
			
		||||
                    href: "/poems/{slug}?set-dark-mode",
 | 
			
		||||
                    match dark_mode {
 | 
			
		||||
                        true => {
 | 
			
		||||
                            rsx! {
 | 
			
		||||
                                img { class: "",
 | 
			
		||||
                                    src: "/images/white_square_button.png",
 | 
			
		||||
                                    alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        false => {
 | 
			
		||||
                            rsx! {
 | 
			
		||||
                                img { class: "",
 | 
			
		||||
                                    src: "/images/black_square_button.png",
 | 
			
		||||
                                    alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    }
 | 
			
		||||
                } 
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    // #[cfg(target_family = "wasm")]
 | 
			
		||||
    // fn RenderPoemTitle(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
    //     let slug = cx.props.slug.clone().expect("Slug prop was not passed.");
 | 
			
		||||
    //     let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
    //     let title = cx
 | 
			
		||||
    //         .props
 | 
			
		||||
    //         .title
 | 
			
		||||
    //         .clone()
 | 
			
		||||
    //         .expect("This poem has an empty title.");
 | 
			
		||||
    //     cx.render(rsx!{
 | 
			
		||||
    //         p { class: "mx-auto max-w-fit flex flex-row justify-center 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-4",
 | 
			
		||||
    //             dangerous_inner_html: "{title} ",
 | 
			
		||||
    //             a { class: "",
 | 
			
		||||
    //                 href: "/poems/{slug}/toggle-dark-mode",
 | 
			
		||||
    //                 match dark_mode {
 | 
			
		||||
    //                     true => {
 | 
			
		||||
    //                         rsx! {
 | 
			
		||||
    //                             img { class: "",
 | 
			
		||||
    //                                 src: "/images/white_square_button.png",
 | 
			
		||||
    //                                 alt: "A white square button that can toggle dark mode.",
 | 
			
		||||
    //                             }
 | 
			
		||||
    //                         }
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                     false => {
 | 
			
		||||
    //                         rsx! {
 | 
			
		||||
    //                             img { class: "",
 | 
			
		||||
    //                                 src: "/images/black_square_button.png",
 | 
			
		||||
    //                                 alt: "A black square button that can toggle dark mode.",
 | 
			
		||||
    //                             }
 | 
			
		||||
    //                         }
 | 
			
		||||
    //                     },
 | 
			
		||||
    //                 }
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    #[cfg(any(target_family = "unix", target_family = "windows"))]
 | 
			
		||||
    fn RenderPoemElement(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
| 
						 | 
				
			
			@ -435,7 +550,13 @@ pub mod void_app {
 | 
			
		|||
            .clone()
 | 
			
		||||
            .expect("No creation date specified.");
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            div { class: "font-nerd flex flex-col space-y-4 mx-4 py-4", "{content}{creation_date} "
 | 
			
		||||
            div { class: "flex p-4 ml-6 mr-6 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-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 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 mx-4 py-4", "{content}{creation_date}"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -456,26 +577,10 @@ pub mod void_app {
 | 
			
		|||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn MakePoem(cx: Scope<PoemData>) -> Element {
 | 
			
		||||
        let slug = String::from(cx.props.slug.clone().expect("Slug prop was not passed."));
 | 
			
		||||
        let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed.");
 | 
			
		||||
        let title = cx.props.title.clone().expect("No title specified.");
 | 
			
		||||
        let creation_date = cx
 | 
			
		||||
            .props
 | 
			
		||||
            .creation_date
 | 
			
		||||
            .clone()
 | 
			
		||||
            .expect("No creation date specified.");
 | 
			
		||||
        let content = cx.props.content.clone().expect("No content specified.");
 | 
			
		||||
        cx.render(rsx!{
 | 
			
		||||
    fn MakePoem<'a>(cx: Scope<'a, PoemChildren<'a>>) -> Element {
 | 
			
		||||
        cx.render(rsx! {
 | 
			
		||||
            div { class: "flex-col space-y-4",
 | 
			
		||||
                RenderPoemTitle { title: title.clone(), slug: slug, dark_mode: dark_mode }
 | 
			
		||||
                div { class: "flex p-4 ml-6 mr-6 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-red-dark dark:text-alice-werefox-red-light",
 | 
			
		||||
                        summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center 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-red-dark dark:text-alice-werefox-red-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",
 | 
			
		||||
                        }
 | 
			
		||||
                        RenderPoemElement { content: content.clone(), creation_date: creation_date.clone() }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                &cx.props.children
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										0
									
								
								void-fe/src/utils/file_management.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								void-fe/src/utils/file_management.rs
									
									
									
									
									
										Normal file
									
								
							
		Loading…
	
		Reference in a new issue