finish blog

 add first blog entry
🎨 rework ui+ux
 add freemono
This commit is contained in:
lia
2025-08-24 03:46:08 +00:00
parent f1c471839f
commit 2e397c5be3
19 changed files with 788 additions and 322 deletions

View File

@@ -1,6 +1,8 @@
use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::{BlogEntry, Post};
use yew::prelude::*;
use crate::pages::blog::authorcard::AuthorCard;
use crate::pages::blog::entrycard::EntryCard;
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct Props {
@@ -27,20 +29,42 @@ impl Component for Author {
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
let id = author.id;
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
.filter(|&id_offset| {
Post::from_id(id_offset as u8)
.authors
.iter()
.any(|id1| *id1 == id)
})
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
.collect();
html! {
<div>
<img alt="insert pfp here" loading="eager" src={author.image_url.clone()} />
<div style="vertical-align: top !important;">
<p style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;">{author.name.clone()}</p>
<p style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
{ for author.keywords.iter().map(|tag| html! { <p>{ tag }</p> }) }
</p>
</div>
<hr style="background-color: #B4BEFE60 !important;" />
<p>{r#"about me"#}</p>
<div>
{ author.about.clone() }
</div>
<div style="
display: flex !important;
width: 100% !important;
flex-direction: column !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-bottom: 2rem !important;
">
<h2 style="text-align: center !important;">{ "about me" }</h2>
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-bottom: 1rem !important;
"><AuthorCard {id} /></div>
<h2 style="text-align: center !important;">{ "my posts" }</h2>
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
">{ for cards }</div>
</div>
}
}

View File

@@ -32,32 +32,41 @@ impl Component for AuthorCard {
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
let keywords = author.keywords.iter().map(|keyword| html! {
<p style="margin: unset !important;">{*keyword}</p>
}).collect::<Html>();
html! {
<>
<div>
<div>
<div>
<div>
<figure>
<img alt="this should normally show an image mew,," src={author.image_url.clone()} />
</figure>
</div>
<div>
<p>{ &author.name }</p>
<p>
{ "I like " }
<b>{ author.keywords.join(", ") }</b>
</p>
</div>
</div>
<div style="
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
background-color: #ECBEE130 !important;
border-radius: 0.5rem !important;
width: fit-content !important;
height: fit-content !important;
text-align: left !important;
margin: 1rem 1rem 0 0 !important;
">
<img style="
float: left !important;
margin-right: 0.3rem !important;
border-radius: 0.5rem !important;
" alt="insert pfp here" width="96" height="96" src={author.image_url} />
<div style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
margin-top: 0.3rem !important;
margin-right: 0.3rem !important;
">
<h3 style="margin: unset !important;">
<Link<Route> to={Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
</h3>
{ keywords }
</div>
<footer>
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.id }}>
{ "Profile" }
</Link<Route>>
</footer>
</div>
</>
}
}
}

View File

@@ -1,31 +1,31 @@
use yew::prelude::*;
use crate::pages::blog::authorcard::AuthorCard;
use crate::pages::blog::content::{Author, BlogEntry};
/*pub enum Msg {
NextAuthors,
}*/
pub struct Authors {
ids: Vec<u8>,
}
pub struct Authors;
impl Component for Authors {
type Message = (); //Msg;
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self { ids: vec![0], }
Self
}
/*fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { Msg::NextAuthors => { self.ids = vec![0]; true } }
}*/
fn view(&self, _: &Context<Self>) -> Html {
html! {
{ for self.ids.iter().map(|&id| { html! {
<AuthorCard {id} />
} }) }
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-top: 2rem !important;
margin-bottom: 2rem !important;
">{ for Author::AUTHORS.map(|author| {
let id = author.id;
html! { <AuthorCard {id} /> }
}) }</div>
}
}
}

View File

@@ -1,58 +1,48 @@
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Author {
pub id: u8,
pub image_url: String,
pub name: String,
pub keywords: Vec<String>,
pub about: String,
pub image_url: &'static str,
pub name: &'static str,
pub keywords: &'static [&'static str],
pub about: &'static str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Post {
pub id: u8,
pub authors: &'static [u8],
pub title: &'static str,
pub utcdate: &'static str,
pub content: &'static str,
}
impl BlogEntry for Author {
fn from_entry(entry: &mut Entry) -> Self {
match entry.id {
0 => Self {
id: entry.id,
image_url: "profile.avif".to_string(),
name: "iouring".to_string(),
keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()],
about: "sup".to_string(),
},
_ => Self {
*Self::AUTHORS
.get(entry.id as usize)
.cloned()
.unwrap_or(&Self {
id: u8::MAX,
image_url: "".to_string(),
name: "not found".to_string(),
keywords: vec![],
about: "".to_string(),
},
}
image_url: "",
name: "not found",
keywords: [].as_slice(),
about: "",
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Post {
pub id: u8,
pub author: Author,
pub title: String,
pub content: Vec<String>,
}
impl BlogEntry for Post {
fn from_entry(entry: &mut Entry) -> Self {
return match entry.id {
0 => Self {
id: 0,
author: Author::from_id(0),
title: "meow".parse().unwrap(),
content: vec!["<h1>meow</h1>", "mrrp", "meow"]
.iter()
.map(|s| s.to_string())
.collect(),
},
_ => Self {
*Self::POSTS
.get(entry.id as usize)
.cloned()
.unwrap_or(&Self {
id: u8::MAX,
author: Author::from_id(u8::MAX),
title: "not found".parse().unwrap(),
content: vec![],
},
};
authors: &[u8::MAX],
title: "not found",
utcdate: "1970-01-01",
content: "",
})
}
}
@@ -65,10 +55,170 @@ impl Entry {
Self { id: seed }
}
}
pub trait BlogEntry: Sized {
fn from_entry(entry: &mut Entry) -> Self;
fn from_id(id: u8) -> Self {
Self::from_entry(&mut Entry::from_id(id))
}
const AUTHORS: [&'static Author; 2] = [
&Author {
id: 0,
image_url: "profile.avif",
name: "lia",
keywords: &[
"certified catgirl™",
"software engineer",
"professional yapper",
"neurospicy and disabled"
],
about: "",
},
&Author {
id: 1,
image_url: "",
name: "mreowww",
keywords: &["meow", "mrrp", "mew"],
about: "",
},
];
const POSTS: [&'static Post; 1] = [
/*&Post {
id: 0,
authors: &[0],
title: "sweet little poison",
utcdate: "1970-01-01",
content: r#"
### this will be a future write-up how i set up iocaine with caddy and docker.
### here's my docker-compose.yml:
```yml
services:
caddy:
image: caddy:alpine
container_name: proxy.caddy
hostname: proxy.caddy
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
networks:
- nginx
volumes:
- ./socks:/run/iocaine:ro
- ./nginx/pages:/var/www/html
- ./caddy/data:/data
- ./caddy/config:/config
- ./caddy/proxy:/etc/caddy/Caddyfile:ro
depends_on:
- iocaine
iocaine:
image: git.madhouse-project.org/iocaine/iocaine:2
container_name: proxy.iocaine
hostname: proxy.iocaine
restart: unless-stopped
ports:
- "42069:42069"
networks:
- nginx
volumes:
- ./iocaine:/data
- ./socks:/run/iocaine:rw
# - iocainesocket:/run/iocaine/waow.socket:rw
environment:
# - IOCAINE__SERVER__BIND="0.0.0.0:42069"
- IOCAINE__SERVER__BIND="/run/iocaine/waow.socket"
- IOCAINE__SERVER__UNIX_LISTEN_ACCESS="everybody"
- IOCAINE__SERVER__REQUEST_HANDLER__PATH="/data"
- IOCAINE__SERVER__REQUEST_HANDLER__LANGUAGE="roto"
- IOCAINE__SERVER__CONTROL__BIND="/run/iocaine/listen.socket"
- IOCAINE__SERVER__CONTROL__UNIX_LISTEN_ACCESS="owner"
- IOCAINE__SOURCES__WORDS="/data/words.txt"
- IOCAINE__SOURCES__MARKOV=["/data/1984.txt", "/data/brave-new-world.txt"]
- IOCAINE__METRICS__ENABLE=false
command: --config-file /data/config.toml
networks:
nginx:
external: true
```
### my file-tree:
```sh
services/proxy » tree iocaine
iocaine
├── 1984.txt
├── brave-new-world.txt
├── nam_shub_of_enki
│   ├── classify
│   │   └── mod.roto
│   ├── config.roto
│   ├── detect
│   │   └── mod.roto
│   ├── mod.roto
│   └── tests
│   └── mod.roto
├── nam-shub-of-enki-20250711.0.tar.zst
├── nam-shub-of-enki-latest.tar.zst
├── pkg.roto
├── robots.json
└── words.txt
```
### and my caddy-snippet with example:
```txt
(iocaine) {
@read method GET HEAD
@not-read not {
method GET HEAD
}
reverse_proxy @read unix//run/iocaine/waow.socket {
#reverse_proxy @read proxy.iocaine:42069 {
@fallback status 421
handle_response @fallback {
{blocks.handler}
}
}
handle @not-read {
{blocks.default}
}
}
example.com {
import iocaine {
handler {
reverse_proxy http://example:8080
}
default {
respond 405
}
}
}
```
"#,
},
*/&Post {
id: 0,
authors: &[0],
title: "haj, world!",
utcdate: "2025-08-24",
content: r#"
### my first blogpost
haj! and welcome to my first blogpost.
this blogpost won't have that much content.
fun fact: this post generates html from markdown :3
so i can include images, formatted text, code and content much easier.
i also added a too overcomplicated blog backend that allows authors and posts by u8::MAX, cause why not.
won't post that much either way, so why bother with higher values. /srs
nonetheless... have a great day, evening, whatever daytime it's for you rn and stay safe. ♡s
"#,
},
];
}

View File

@@ -1,7 +1,7 @@
use yew::prelude::*;
use crate::pages::blog::content::{Author, Post};
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Post;
use crate::pages::blog::entrycard::EntryCard;
pub struct Entries {}
@@ -10,15 +10,18 @@ impl Component for Entries {
type Message = ();
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div>
<h1>{ "entries" }</h1>
<h2>{ "here are some things i yapped about" }</h2>
<h1 style="margin: unset !important;">{ "entries" }</h1>
<h2 style="
margin: unset !important;
margin-bottom: 1rem !important;
">{ "here are some things i yapped about" }</h2>
{ self.view_posts(ctx) }
</div>
}
@@ -26,12 +29,27 @@ impl Component for Entries {
}
impl Entries {
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
let cards: Vec<_> = (0..2) // TODO: ... | add var
.filter(|&id_offset| Post::from_id(id_offset).author.id != u8::MAX)
.map(|id_offset| {
html! {<EntryCard id={id_offset} />}
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
.filter(|&id_offset| {
Post::from_id(id_offset as u8)
.authors
.iter()
.all(|id| *id != u8::MAX)
})
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
.collect();
html! { { for cards } }
html! {
<div style="
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin: unset !important;
margin-bottom: 1rem !important;
width: 100% !important;
">
{ for cards }
</div>
}
}
}

View File

@@ -1,10 +1,11 @@
use std::rc::Rc;
use yew::prelude::*;
use yew_markdown::Markdown;
use yew_router::prelude::*;
use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::{Author, BlogEntry};
use crate::Route;
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
@@ -47,18 +48,30 @@ pub fn Entry(props: &Props) -> Html {
html! {
<>
<h1>
{ &post.title }
</h1>
<h2>
{ "by " }
<Link<Route> to={Route::Author { id: post.author.id }}>
{ &post.author.name }
</Link<Route>>
</h2>
<div>
{ for post.content.iter() }
<h1 style="
color: #F5C2E7FF !important;
margin: unset !important;
filter: url(post.bloom.svg#process) !important;
">{ &*post.title }</h1>
<div style="flex-direction: column !important;">
<h2 style="margin: unset !important;">
{ "written by " } {
post.authors.iter().map(|id| {
let author = Author::from_id(*id);
html! {
<Link<Route> to={crate::Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
}
}).intersperse(html! { ", " }).collect::<Html>()
} { format!(" on {}", &post.utcdate) }
</h2>
</div>
<Markdown
src={post.content}
hard_line_breaks={true}
send_debug_info={Callback::noop()}
/>
</>
}
}

View File

@@ -1,9 +1,8 @@
use std::rc::Rc;
use yew::prelude::*;
use yew_router::components::Link;
use crate::pages::blog::content::{BlogEntry, Post};
use crate::pages::blog::content::{Author, BlogEntry, Post};
use crate::Route;
@@ -48,13 +47,52 @@ pub fn EntryCard(props: &PropsEntryCard) -> Html {
let post = &post.inner;
html! {
<div style="height: fit-content !important; width: fit-content !important;">
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.id }}>
{ &post.title }
</Link<Route>>
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.id }}>
{ &post.author.name }
<div style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
background-color: #ECBEE130 !important;
border-radius: 0.5rem !important;
width: fit-content !important;
height: fit-content !important;
text-align: left !important;
margin: 1rem 1rem 0 0 !important;
">
<Link<Route> to={Route::Entry { id: post.id }}>
<h2 style="
margin: unset !important;
margin-top: 0.3rem !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
">{ &*post.title }</h2>
</Link<Route>>
<h3 style="
margin: unset !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
">
{ format!("published {}", post.utcdate) }
</h3>
<div style="flex-direction: column !important;">
<h3 style="
margin: unset !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
margin-bottom: 0.3rem !important;
">
{ "written by " }
{
post.authors.iter().map(|id| {
let author = Author::from_id(*id);
html! {
<Link<Route> to={crate::Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
}
}).intersperse(html! { ", " }).collect::<Html>()
}
</h3>
</div>
</div>
}
}