many many changes

This commit is contained in:
Zoey 2023-06-09 21:55:04 -07:00
parent 80bc863cdf
commit efa0d03315
22 changed files with 360 additions and 45 deletions

View file

@ -0,0 +1,29 @@
---
title: Estradiol Delivery Methods
timestamp: 2023-05-28T16:00:00.00Z
tags: [trans, hrt]
desc: A comparison of the various ways I've taken estradiol since starting HRT.
header_image_file: cat.jpeg # TODO: placeholder image
header_image_alt: placeholder
draft: true
---
I thought it might be useful to write out my experiences with the four methods of taking estradiol I've tried.
- patches
- wouldn't consistently stay on me
- could feel the effects wearing off 3-4 days after application
- insurance sucks
- sublingual pills
- worked alright, except:
- could feel them wearing off ~5-6 hours after taking a dose
- apparently hard to measure levels? had one test at ~200 mg/ml and another at 400+ mg/ml at same dosage
- swallowed pills
- could not get ideal levels with a reasonable number of pills, but:
- couldn't actually feel them wearing off even at 12 hours between doses
- injections
- holy shit these are great
- was kinda nervous the first few times I injected myself, but I got used to it
- cannot feel it wearing off between doses
- levels are actually good??
- insurance sucks pt. 2

View file

@ -0,0 +1,23 @@
---
title: so now i have a blog
timestamp: 2023-06-09T16:00:00.00Z
tags: [meta, technical]
desc: I added a blog to my site.
header_image_file: cat.jpeg # TODO: placeholder image
header_image_alt: placeholder
draft: true
---
So now I have a blog on my site. I don't really have any plans to post here regularly, but idk maybe that'll change in the future.
That's pretty much it as far as the non-technical side of things goes.
## The Technical Side of Things
I haven't really written anything about how my site works before, so this is also going to contain some general information about the site as a whole.
`zyl.gay` is a static website built with a custom static site builder I built for it. It started by taking Markdown pages and rendering them on top of the appropriate template.
When I added the [images section](/images/) to the site I added the first abstraction on top of this: YAML files with the relevant metadata for the image (including a short but unstyled description) which then get rendered not only into pages for the individual pages, but also a paginated display for all the images _and_ a method to view images by tag.
To get blogs working I modified the image page code to be generic over provided resource types, so really the images and the blog posts are rendered the same way, just with different configurations.

View file

@ -3,5 +3,6 @@ title: Zyllian
description: "Zoey's website."
sass_styles: [index.scss]
images_per_page: 10
blog_posts_per_page: 20
cdn_url: "https://i.zyl.gay"
s3_prefix: # no longer prefixed

View file

@ -1,6 +1,10 @@
---
title: among us 😱
timestamp: 2022-12-14T00:00:00.00Z
alt: Screenshot of Pokémon White on an evolution screen. Text reads "Congratulations! Your amogus evolved into Amoonguss!"
desc: aaahhhh
file: amogus.png
tags: [pokémon, sussy]
---
aaahhhh

View file

@ -1,5 +1,7 @@
---
title: cat 3
timestamp: 2022-12-14T00:00:00.00Z
alt: Picture of my cat sleeping in a box barely large enough for them. Their head is resting on one edge of the box.
file: boxtop.jpeg
tags: [cat]
---

View file

@ -1,5 +1,7 @@
---
title: cat
timestamp: 2022-12-14T00:00:00.00Z
alt: Picture of my cat sleeping curled up on top of some pillows.
file: cat.jpeg
tags: [cat]
---

View file

@ -1,5 +1,7 @@
---
title: cat 2
timestamp: 2022-12-14T00:00:00.00Z
alt: Close up picture of my cat laying on a shelf while staring not quite at the camera.
file: cat2.jpeg
tags: [cat]
---

View file

@ -0,0 +1,13 @@
---
title: first time applying makeup
timestamp: 2023-02-10T21:40:00.00Z
alt: Bathroom mirror selfie of me in a cap, dark-rimmed glasses, and a flannel button-up over a mesh shirt wearing some light red lipstick, light and slightly orange blush around the nose, and poorly applied eyeliner.
file: makeup-first-lol.jpeg
tags: [me, selfie]
---
had the house to myself to try out some outfits and makeup without any external pressure
this picture's of my first time applying any makeup where despite thinking it all looked like shit individually, I think all the parts came together well
I'm wearing only lipstick, a little blush, and eyeliner. the foundation in the kit I was given wouldn't stay on 🤷

View file

@ -1,9 +0,0 @@
title: first time applying makeup
timestamp: 2023-02-10T21:40:00.00Z
alt: Bathroom mirror selfie of me in a cap, dark-rimmed glasses, and a flannel button-up over a mesh shirt wearing some light red lipstick, light and slightly orange blush around the nose, and poorly applied eyeliner.
desc: >
<p>had the house to myself to try out some outfits and makeup without any external pressure</p>
<p>this picture's of my first time applying any makeup where despite thinking it all looked like shit individually, I think all the parts came together well</p>
<p>I'm wearing only lipstick, a little blush, and eyeliner. the foundation in the kit I was given wouldn't stay on 🤷</p>
file: makeup-first-lol.jpeg
tags: [me, selfie]

View file

@ -1,6 +1,9 @@
---
title: shorts to dresses
timestamp: 2022-12-14T00:00:00.00Z
alt: Screenshot from Pokémon Black 2 of an NPC saying "This dress is comfy and easy to wear..."
desc: yooo they're turning the comfy shorts kid trans
file: trans-comfy.png
tags: [pokémon, trans]
---
yooo they're turning the comfy shorts kid trans

View file

@ -9,11 +9,15 @@ body {
--text-color: black;
--accent-color: #b520e7;
--accent-text-color: var(--bg-color);
--accent-color-neutral: #aaa;
--accent-text-color-neutral: #eee;
@media (prefers-color-scheme: dark) {
--bg-color: #333;
--text-color: #ccc;
--accent-text-color: black;
--accent-color-neutral: #555;
--accent-text-color-neutral: #bbb;
}
}
@ -55,7 +59,7 @@ abbr {
.images-list {
display: flex;
flex-wrap: wrap;
.image {
position: relative;
padding: 4px;
@ -81,7 +85,8 @@ abbr {
}
.image-full {
.title, .tags-title {
.title,
.tags-title {
margin-bottom: 0;
}
@ -92,3 +97,68 @@ abbr {
background-color: rgba(0, 0, 0, 0.3);
}
}
.blog-post-list {
.post {
p {
margin: 0;
}
.title {
font-size: 1.2rem;
}
.short-desc {
font-size: 0.9rem;
font-style: italic;
&:before {
content: "";
}
}
}
}
.blog-post {
margin-left: auto;
margin-right: auto;
max-width: max(1000px, 40%);
.title,
.tags-title {
margin: 0;
}
.header-image-wrapper {
color: var(--accent-text-color-neutral);
background-color: var(--accent-color-neutral);
font-style: italic;
font-size: 1.2rem;
margin-bottom: 8px;
.short-desc {
margin: 0;
padding: 8px;
}
.header-image {
width: 100%;
max-height: 60vh;
object-fit: cover;
object-position: 50% 50%;
}
}
.content {
text-indent: 1rem;
h1,
h2,
h3,
h4,
h5,
h6 {
text-indent: 0;
}
}
}

View file

@ -13,6 +13,7 @@
<span class="pronouns">she/they</span>
</span>
<span class="spacer"></span>
{{!-- <a href="/blog/">Blog</a> | --}}
<a href="/images/">Images</a> |
<a href="https://github.com/Zyllian/zyllian.github.io" rel="noopener noreferrer">Source</a>
</header>

View file

@ -0,0 +1,23 @@
{{#if tag}}
<h1>Blog posts tagged {{tag}}</h1>
<p><a href="/blog/">View all blog posts</a></p>
{{else}}
<h1>Blog Posts</h1>
<p><a href="tags">View blog tags</a></p>
<p><a href="rss.xml">RSS Feed</a></p>
{{/if}}
<h1>Page {{page}}/{{page_max}}</h1>
{{#if previous}}
<a href="./{{previous}}">Previous page</a>
{{/if}}
{{#if next}}
<a href="./{{next}}">Next page</a>
{{/if}}
<div class="blog-post-list">
{{#each resources}}
<div class="post">
<p class="title"><a href="/blog/{{id}}">{{title}}</a></p>
<p class="short-desc">{{desc}}</p>
</div>
{{/each}}
</div>

View file

@ -0,0 +1,22 @@
<div class="blog-post">
<h1 class="title">{{title}}</h1>
<span class="timestamp">Published {{timestamp}}</span>
{{#if draft}}
<h2>DRAFT</h2>
{{/if}}
<div class="header-image-wrapper">
<p class="short-desc">{{desc}}</p>
<img class="header-image" src="{{header_image}}" alt="{{header_image_alt}}"
style="object-fit: {{object_fit}}; object-position: {{object_position}};">
</div>
<div class="content">
{{{content}}}
</div>
<hr />
<h3 class="tags-title">Tags</h3>
<div class="post-tags">
{{#each tags}}
<a class="tag" href="/blog/tag/{{this}}">{{this}}</a>{{#unless @last}},{{/unless}}
{{/each}}
</div>
</div>

View file

@ -2,9 +2,7 @@
<h1 class="title">{{title}}</h1>
<span class="timestamp">Published {{timestamp}}</span>
<img class="image-actual" src="{{src}}" alt="{{alt}}">
{{#if desc}}
<p>{{{desc}}}</p>
{{/if}}
{{{content}}}
<p><a href="{{src}}">View full size image</a></p>
<h3 class="tags-title">Tags</h3>
<div class="image-tags">

View file

@ -0,0 +1 @@
<div>{{{source}}}</div>

86
src/blog.rs Normal file
View file

@ -0,0 +1,86 @@
use serde::{Deserialize, Serialize};
use crate::{
builder::SiteBuilder,
resource::{ResourceBuilder, ResourceBuilderConfig, ResourceMetadata, ResourceMethods},
};
pub const BLOG_PATH: &str = "blog";
/// Builds the blog.
pub fn build_blog(site_builder: &SiteBuilder) -> anyhow::Result<()> {
let config = ResourceBuilderConfig {
source_path: BLOG_PATH.to_string(),
output_path_short: BLOG_PATH.to_string(),
output_path_long: BLOG_PATH.to_string(),
resource_template: "blog-post".to_string(),
resource_list_template: "blog-list".to_string(),
rss_template: "rss/blog-post".to_string(),
rss_title: "Zyllian's blog".to_string(),
rss_description: "Feed of recent blog posts on Zyllian's website.".to_string(),
list_title: "Blog".to_string(),
tag_list_title: "Blog Tags".to_string(),
resource_name_plural: "Blog posts".to_string(),
resources_per_page: site_builder.site.config.blog_posts_per_page,
};
let mut builder = ResourceBuilder::<BlogPostMetadata, BlogPostTemplateData>::new(config);
builder.load_all(site_builder)?;
builder.build_all(site_builder)?;
Ok(())
}
/// Metadata for a blog post.
#[derive(Debug, Serialize, Deserialize)]
pub struct BlogPostMetadata {
/// A short description about the post.
pub desc: String,
/// Path to the post's header image.
pub header_image_file: String,
/// Alt text for the post's header image.
pub header_image_alt: String,
/// Optional custom object fit value.
pub image_fit: Option<String>,
/// Optional custom object position value.
pub image_center: Option<String>,
}
/// Template data for a blog post.
#[derive(Debug, Serialize)]
pub struct BlogPostTemplateData {
/// CDN path to the post's header image.
pub header_image: String,
/// Custom object fit value.
pub object_fit: String,
/// Custom object position value.
pub object_position: String,
}
impl ResourceMethods<BlogPostTemplateData> for ResourceMetadata<BlogPostMetadata> {
fn get_short_desc(&self) -> String {
self.inner.desc.clone()
}
fn get_extra_resource_template_data(
&self,
site_config: &crate::SiteConfig,
) -> anyhow::Result<BlogPostTemplateData> {
// TODO: render markdown
Ok(BlogPostTemplateData {
header_image: site_config
.cdn_url(&self.inner.header_image_file)?
.to_string(),
object_fit: self
.inner
.image_fit
.clone()
.unwrap_or_else(|| "cover".to_string()),
object_position: self
.inner
.image_center
.clone()
.unwrap_or_else(|| "50% 50%".to_string()),
})
}
}

View file

@ -25,7 +25,7 @@ struct TemplateData<'a> {
/// Struct used to build the site.
pub struct SiteBuilder<'a> {
/// The matter instance used to extract front matter.
matter: Matter<YAML>,
pub(crate) matter: Matter<YAML>,
/// The Handlebars registry used to render templates.
pub(crate) reg: Handlebars<'a>,
/// The site info used to build the site.
@ -242,4 +242,9 @@ impl<'a> SiteBuilder<'a> {
pub fn build_images(&self) -> anyhow::Result<()> {
crate::images::build_images(self)
}
/// Builds the site's blog.
pub fn build_blog(&self) -> anyhow::Result<()> {
crate::blog::build_blog(self)
}
}

View file

@ -1,5 +1,4 @@
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
builder::SiteBuilder,
@ -28,7 +27,7 @@ pub fn build_images(site_builder: &SiteBuilder) -> anyhow::Result<()> {
};
let mut builder = ResourceBuilder::<ImageMetadata, ImageTemplateData>::new(config);
builder.load_all(&site_builder.site.site_path)?;
builder.load_all(site_builder)?;
builder.build_all(site_builder)?;
Ok(())
@ -45,13 +44,6 @@ pub struct ImageMetadata {
pub file: String,
}
impl ImageMetadata {
/// Gets an image's CDN url.
pub fn cdn_url(&self, config: &SiteConfig) -> anyhow::Result<Url> {
Ok(config.cdn_url.join(&config.s3_prefix)?.join(&self.file)?)
}
}
/// Template data for a specific image.
#[derive(Debug, Serialize)]
struct ImageTemplateData {
@ -70,7 +62,7 @@ impl ResourceMethods<ImageTemplateData> for ResourceMetadata<ImageMetadata> {
site_config: &SiteConfig,
) -> anyhow::Result<ImageTemplateData> {
Ok(ImageTemplateData {
src: self.inner.cdn_url(site_config)?.to_string(),
src: site_config.cdn_url(&self.inner.file)?.to_string(),
})
}
}

View file

@ -1,3 +1,4 @@
mod blog;
mod builder;
mod images;
mod link_list;
@ -38,12 +39,21 @@ pub struct SiteConfig {
pub sass_styles: Vec<PathBuf>,
/// The number of images to display on a single page of an image list.
pub images_per_page: usize,
/// The number of blog posts to display on a single page of a post list.
pub blog_posts_per_page: usize,
/// URL to the CDN used for the site's images.
pub cdn_url: Url,
/// Prefix applied to all files uploaded to the site's S3 space.
pub s3_prefix: String,
}
impl SiteConfig {
/// Gets a CDN url from the given file name.
pub fn cdn_url(&self, file: &str) -> anyhow::Result<Url> {
Ok(self.cdn_url.join(&self.s3_prefix)?.join(file)?)
}
}
/// Struct for the front matter in templates. (nothing here yet)
#[derive(Debug, Default, Deserialize)]
pub struct TemplateMetadata {}
@ -134,6 +144,7 @@ impl Site {
builder.site.build_all_pages(&builder)?;
builder.build_sass()?;
builder.build_images()?;
builder.build_blog()?;
Ok(())
}

View file

@ -6,6 +6,7 @@ use std::{
use anyhow::Context;
use itertools::Itertools;
use pulldown_cmark::{Options, Parser};
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
@ -25,6 +26,12 @@ pub struct ResourceMetadata<T> {
/// Extra resource data not included.
#[serde(flatten)]
pub inner: T,
/// Whether the resource is a draft. Drafts can be committed without being published to the live site.
#[serde(default)]
pub draft: bool,
/// The resource's content. Defaults to nothing until loaded in another step.
#[serde(default)]
pub content: String,
}
#[derive(Debug, Serialize)]
@ -141,19 +148,39 @@ where
}
/// Loads resource metadata from the given path.
fn load(path: &Path) -> anyhow::Result<(String, ResourceMetadata<M>)> {
fn load(builder: &SiteBuilder, path: &Path) -> anyhow::Result<(String, ResourceMetadata<M>)> {
let id = Self::get_id(path);
let metadata = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
Ok((id, metadata))
let input = std::fs::read_to_string(path)?;
let mut page = builder
.matter
.parse_with_struct::<ResourceMetadata<M>>(&input)
.ok_or_else(|| anyhow::anyhow!("Failed to parse resource front matter"))?;
let parser = Parser::new_ext(&page.content, Options::all());
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser);
page.data.content = html;
Ok((id, page.data))
}
/// Loads all resource metadata from the given config.
pub fn load_all(&mut self, site_path: &Path) -> anyhow::Result<()> {
pub fn load_all(&mut self, builder: &SiteBuilder) -> anyhow::Result<()> {
self.loaded_metadata.clear();
for e in site_path.join(&self.config.source_path).read_dir()? {
for e in builder
.site
.site_path
.join(&self.config.source_path)
.read_dir()?
{
let p = e?.path();
if let Some("yml") = p.extension().and_then(|e| e.to_str()) {
let (id, metadata) = Self::load(&p)?;
if let Some("md") = p.extension().and_then(|e| e.to_str()) {
let (id, metadata) = Self::load(builder, &p)?;
if cfg!(not(debug_assertions)) && metadata.draft {
continue;
}
self.loaded_metadata.push((id, metadata));
}
}
@ -201,6 +228,16 @@ where
}
pub fn build_all(&self, builder: &SiteBuilder) -> anyhow::Result<()> {
let out_short = builder.build_path.join(&self.config.output_path_short);
let out_long = builder.build_path.join(&self.config.output_path_long);
if !out_short.exists() {
std::fs::create_dir_all(&out_short)?;
}
if !out_long.exists() {
std::fs::create_dir_all(&out_long)?;
}
for (id, resource) in &self.loaded_metadata {
self.build(builder, id.clone(), resource)?;
}
@ -267,8 +304,6 @@ where
Ok(())
}
let out_path = builder.build_path.join(&self.config.output_path_short);
// Build main list of resources
build_list(
builder,
@ -276,7 +311,7 @@ where
data.iter().collect(),
&self.config.list_title,
None,
&builder.build_path.join(&self.config.output_path_long),
&out_long,
self.config.resources_per_page,
)?;
@ -310,7 +345,7 @@ where
links,
&self.config.tag_list_title,
)?;
std::fs::write(out_path.join("tags.html"), out)?;
std::fs::write(out_short.join("tags.html"), out)?;
}
for (tag, data) in tags {
@ -320,7 +355,7 @@ where
data,
&format!("{} tagged {tag}", self.config.resource_name_plural),
Some(tag.as_str()),
&out_path.join("tag").join(&tag),
&out_short.join("tag").join(&tag),
self.config.resources_per_page,
)?;
}
@ -367,13 +402,7 @@ where
.build();
channel.validate().context("Failed to validate RSS feed")?;
let out = channel.to_string();
std::fs::write(
builder
.build_path
.join(&self.config.output_path_long)
.join("rss.xml"),
out,
)?;
std::fs::write(out_long.join("rss.xml"), out)?;
Ok(())
}

View file

@ -64,6 +64,7 @@ fn create(
if build {
builder.site.build_all_pages(builder)?;
builder.build_images()?;
builder.build_blog()?;
}
} else if relative_path.display().to_string() == "config.yaml" {
let new_config = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
@ -78,6 +79,9 @@ fn create(
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
// HACK: this could get very inefficient with a larger number of images. should definitely optimize
builder.build_images()?;
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
// HACK: same as above
builder.build_blog()?;
}
Ok(())
@ -109,6 +113,8 @@ fn remove(builder: &mut SiteBuilder, path: &Path, relative_path: &Path) -> anyho
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
// HACK: same as in `create`
builder.build_images()?;
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
// HACK: same as above
}
Ok(())
@ -138,6 +144,7 @@ impl Site {
builder
.build_images()
.context("Failed to build image pages")?;
builder.build_blog().context("Failed to build blog")?;
// Map of websocket connections
let peers: Arc<Mutex<HashMap<SocketAddr, WebSocket>>> =