diff --git a/site/pages/index.md b/site/pages/index.md index ba1717e..fe8295e 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -1,3 +1,7 @@ +--- +extra: index +--- + # zyl's website hi! i'm zyl, a trans doggirlthing ΘΔ in the pnw. diff --git a/site/sass/index.scss b/site/sass/index.scss index 6570267..106ac61 100644 --- a/site/sass/index.scss +++ b/site/sass/index.scss @@ -35,8 +35,14 @@ a { } } -main.page { - margin: 8px; +#content { + main.page { + margin: 0 8px 8px 8px; + } + + .index-info { + margin: 8px; + } } abbr { diff --git a/site/templates/base.hbs b/site/templates/base.hbs index 6893632..f5a5f57 100644 --- a/site/templates/base.hbs +++ b/site/templates/base.hbs @@ -2,8 +2,10 @@ + + {{title}} {{{head}}} @@ -18,9 +20,11 @@ images | source -
- {{{page}}} -
+
+
+ {{{page}}} +
+
diff --git a/site/templates/extras/index-injection.hbs b/site/templates/extras/index-injection.hbs new file mode 100644 index 0000000..c0cc484 --- /dev/null +++ b/site/templates/extras/index-injection.hbs @@ -0,0 +1,12 @@ +
+
+

most recent blog posts

+
+ {{#each resources}} +
+

{{title}}

+

{{desc}}

+
+ {{/each}} +
+
diff --git a/src/blog.rs b/src/blog.rs index 0dc30f7..313f72d 100644 --- a/src/blog.rs +++ b/src/blog.rs @@ -1,16 +1,15 @@ use serde::{Deserialize, Serialize}; use crate::{ - builder::SiteBuilder, - resource::{ResourceBuilder, ResourceBuilderConfig, ResourceMetadata, ResourceMethods}, - SiteConfig, + resource::{ResourceBuilderConfig, ResourceMetadata, ResourceMethods}, + Site, SiteConfig, }; pub const BLOG_PATH: &str = "blog"; -/// Builds the blog. -pub fn build_blog(site_builder: &SiteBuilder) -> anyhow::Result<()> { - let config = ResourceBuilderConfig { +/// Gets the blog's resource configuration. +pub fn get_blog_resource_config(site: &Site) -> ResourceBuilderConfig { + ResourceBuilderConfig { source_path: BLOG_PATH.to_string(), output_path_short: BLOG_PATH.to_string(), output_path_long: BLOG_PATH.to_string(), @@ -19,17 +18,11 @@ pub fn build_blog(site_builder: &SiteBuilder) -> anyhow::Result<()> { rss_template: "rss/blog-post".to_string(), rss_title: "zyl's blog".to_string(), rss_description: "Feed of recent blog posts on zyl'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::::new(config); - builder.load_all(site_builder)?; - builder.build_all(site_builder)?; - - Ok(()) + list_title: "blog".to_string(), + tag_list_title: "blog tags".to_string(), + resource_name_plural: "blog posts".to_string(), + resources_per_page: site.config.blog_posts_per_page, + } } /// Metadata for a blog post. diff --git a/src/builder.rs b/src/builder.rs index bdf2ee2..d11d3ae 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -10,16 +10,20 @@ use pulldown_cmark::{Options, Parser}; use serde::Serialize; use url::Url; -use crate::{util, PageMetadata, Site, ROOT_PATH, SASS_PATH}; +use crate::{ + extras::Extra, resource::ResourceBuilder, util, PageMetadata, Site, ROOT_PATH, SASS_PATH, +}; /// Struct containing data to be sent to templates when rendering them. #[derive(Debug, Serialize)] struct TemplateData<'a, T> { /// The rendered page. pub page: &'a str, + /// The page's title. + pub title: &'a str, /// Custom template data. #[serde(flatten)] - pub extra: T, + pub extra_data: T, } /// Struct used to build the site. @@ -34,6 +38,13 @@ pub struct SiteBuilder<'a> { pub build_path: PathBuf, /// Whether the site is going to be served locally with the dev server. serving: bool, + + /// Resource builder for the site's images section. + pub images_builder: + ResourceBuilder, + /// Resource builder for the site's blog section. + pub blog_builder: + ResourceBuilder, } impl<'a> SiteBuilder<'a> { @@ -50,6 +61,8 @@ impl<'a> SiteBuilder<'a> { Self { matter: Matter::new(), reg: Handlebars::new(), + images_builder: ResourceBuilder::new(crate::images::get_images_resource_config(&site)), + blog_builder: ResourceBuilder::new(crate::blog::get_blog_resource_config(&site)), site, build_path, serving, @@ -94,48 +107,24 @@ impl<'a> SiteBuilder<'a> { std::fs::create_dir(images_path).context("Failed to create images path")?; } + self.images_builder + .load_all(&self) + .context("Failed to load images metadata")?; + self.blog_builder + .load_all(&self) + .context("Failed to load blog metadata")?; + Ok(self) } - pub fn build_page_raw( - &self, - page_metadata: PageMetadata, - page_html: &str, - ) -> anyhow::Result { - self.build_page_raw_extra(page_metadata, page_html, ()) - } - - /// Helper to build a page without writing it to disk. - pub fn build_page_raw_extra( - &self, - page_metadata: PageMetadata, - page_html: &str, - extra: T, - ) -> anyhow::Result - where - T: Serialize, - { - let out = self.reg.render( - &page_metadata.template.unwrap_or_else(|| "base".to_string()), - &TemplateData { - page: page_html, - extra, - }, - )?; - - let title = match &page_metadata.title { - Some(page_title) => format!("{} / {}", self.site.config.title, page_title), - _ => self.site.config.title.clone(), - }; - - // Modify HTML output + /// Function to rewrite HTML wow. + pub fn rewrite_html(&self, html: String) -> anyhow::Result { let mut output = Vec::new(); let mut rewriter = HtmlRewriter::new( Settings { element_content_handlers: vec![ element!("head", |el| { el.prepend(r#""#, ContentType::Html); - el.append(&format!("{}", title), ContentType::Html); if self.serving { el.append(r#""#, ContentType::Html); } @@ -180,10 +169,55 @@ impl<'a> SiteBuilder<'a> { |c: &[u8]| output.extend_from_slice(c), ); - rewriter.write(out.as_bytes())?; + rewriter.write(html.as_bytes())?; rewriter.end()?; - let mut out = String::from_utf8(output)?; + Ok(String::from_utf8(output)?) + } + + pub fn build_page_raw( + &self, + page_metadata: PageMetadata, + page_html: &str, + ) -> anyhow::Result { + self.build_page_raw_with_extra_data(page_metadata, page_html, ()) + } + + /// Helper to build a page without writing it to disk. + pub fn build_page_raw_with_extra_data( + &self, + page_metadata: PageMetadata, + page_html: &str, + extra_data: T, + ) -> anyhow::Result + where + T: Serialize, + { + let extra = page_metadata + .extra + .and_then(|extra| crate::extras::get_extra(&extra)); + + let title = match &page_metadata.title { + Some(page_title) => format!("{} / {}", self.site.config.title, page_title), + _ => self.site.config.title.clone(), + }; + + let out = self.reg.render( + &page_metadata.template.unwrap_or_else(|| "base".to_string()), + &TemplateData { + page: page_html, + title: &title, + extra_data, + }, + )?; + + // Modify HTML output + let mut out = self.rewrite_html(out)?; + + if let Some(Extra::HtmlModification(f)) = extra { + out = f(out, self)?; + } + if !self.serving { out = minifier::html::minify(&out); } @@ -269,11 +303,11 @@ impl<'a> SiteBuilder<'a> { /// Builds the site's various image pages. pub fn build_images(&self) -> anyhow::Result<()> { - crate::images::build_images(self) + self.images_builder.build_all(self) } /// Builds the site's blog. pub fn build_blog(&self) -> anyhow::Result<()> { - crate::blog::build_blog(self) + self.blog_builder.build_all(self) } } diff --git a/src/extras.rs b/src/extras.rs new file mode 100644 index 0000000..a451f31 --- /dev/null +++ b/src/extras.rs @@ -0,0 +1,45 @@ +use lol_html::{element, RewriteStrSettings}; +use serde::Serialize; + +use crate::{blog::BlogPostMetadata, builder::SiteBuilder, resource::ResourceMetadata}; + +#[derive(Debug)] +pub enum Extra { + HtmlModification(fn(page: String, builder: &SiteBuilder) -> anyhow::Result), +} + +/// Gets the extra for the given value. +pub fn get_extra(extra: &str) -> Option { + match extra { + "index" => Some(Extra::HtmlModification(index)), + _ => None, + } +} + +/// Extra to add a sidebar to the index page with recent blog posts on it. +fn index(page: String, builder: &SiteBuilder) -> anyhow::Result { + #[derive(Debug, Serialize)] + struct SidebarTemplateData<'r> { + resources: Vec<&'r ResourceMetadata>, + } + + let lmd = builder.blog_builder.loaded_metadata.borrow(); + + let sidebar = builder.reg.render( + "extras/index-injection", + &SidebarTemplateData { + resources: lmd.iter().take(3).map(|(_, v)| v).collect(), + }, + )?; + + Ok(lol_html::rewrite_str( + &page, + RewriteStrSettings { + element_content_handlers: vec![element!("#content", |el| { + el.append(&sidebar, lol_html::html_content::ContentType::Html); + Ok(()) + })], + ..Default::default() + }, + )?) +} diff --git a/src/images.rs b/src/images.rs index 0ab03f6..db8e534 100644 --- a/src/images.rs +++ b/src/images.rs @@ -1,17 +1,16 @@ use serde::{Deserialize, Serialize}; use crate::{ - builder::SiteBuilder, - resource::{ResourceBuilder, ResourceBuilderConfig, ResourceMetadata, ResourceMethods}, - SiteConfig, + resource::{ResourceBuilderConfig, ResourceMetadata, ResourceMethods}, + Site, SiteConfig, }; pub(crate) const IMAGES_PATH: &str = "images"; pub(crate) const IMAGES_OUT_PATH: &str = "i"; -/// Builds the site's image pages. -pub fn build_images(site_builder: &SiteBuilder) -> anyhow::Result<()> { - let config = ResourceBuilderConfig { +/// Gets the resource configuration for images. +pub fn get_images_resource_config(site: &Site) -> ResourceBuilderConfig { + ResourceBuilderConfig { source_path: IMAGES_PATH.to_string(), output_path_short: IMAGES_OUT_PATH.to_string(), output_path_long: "images".to_string(), @@ -19,18 +18,12 @@ pub fn build_images(site_builder: &SiteBuilder) -> anyhow::Result<()> { resource_list_template: "images".to_string(), rss_template: "rss/image".to_string(), rss_title: "zyl's images".to_string(), - rss_description: "Feed of newly uploaded images from zyl's website.".to_string(), - list_title: "Images".to_string(), - tag_list_title: "Image Tags".to_string(), - resource_name_plural: "Images".to_string(), - resources_per_page: site_builder.site.config.images_per_page, - }; - - let mut builder = ResourceBuilder::::new(config); - builder.load_all(site_builder)?; - builder.build_all(site_builder)?; - - Ok(()) + rss_description: "feed of newly uploaded images from zyl's website.".to_string(), + list_title: "images".to_string(), + tag_list_title: "image tags".to_string(), + resource_name_plural: "images".to_string(), + resources_per_page: site.config.images_per_page, + } } /// Definition for a remote image. @@ -46,7 +39,7 @@ pub struct ImageMetadata { /// Template data for a specific image. #[derive(Debug, Serialize)] -struct ImageTemplateData { +pub struct ImageTemplateData { /// Direct URL to the image's CDN location. /// TODO: link to smaller versions on list pages src: String, diff --git a/src/lib.rs b/src/lib.rs index 1c30343..bce90ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod blog; mod builder; +mod extras; mod images; mod link_list; mod resource; @@ -65,6 +66,8 @@ pub struct PageMetadata { pub title: Option, /// The template to use for the page. If not specified, it defaults to "base". pub template: Option, + /// The extra stuff to run for the page, if any. + pub extra: Option, } /// Struct containing information about the site. diff --git a/src/resource.rs b/src/resource.rs index d708108..6cbeb39 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, collections::BTreeMap, marker::PhantomData, path::{Path, PathBuf}, @@ -13,6 +14,10 @@ use time::{format_description::well_known::Rfc2822, OffsetDateTime}; use crate::{builder::SiteBuilder, link_list::Link, PageMetadata, SiteConfig}; +pub trait ResourceConfig { + fn new(builder: &SiteBuilder) -> Self; +} + /// Metadata for resources. #[derive(Debug, Deserialize, Serialize)] pub struct ResourceMetadata { @@ -126,9 +131,9 @@ pub struct ResourceBuilderConfig { #[derive(Debug)] pub struct ResourceBuilder { /// The builder's config. - config: ResourceBuilderConfig, + pub config: ResourceBuilderConfig, /// The currently loaded resource metadata. - loaded_metadata: Vec<(String, ResourceMetadata)>, + pub loaded_metadata: RefCell)>>, _extra: PhantomData, } @@ -176,8 +181,9 @@ where } /// Loads all resource metadata from the given config. - pub fn load_all(&mut self, builder: &SiteBuilder) -> anyhow::Result<()> { - self.loaded_metadata.clear(); + pub fn load_all(&self, builder: &SiteBuilder) -> anyhow::Result<()> { + let mut lmd = self.loaded_metadata.borrow_mut(); + lmd.clear(); for e in builder .site .site_path @@ -190,11 +196,10 @@ where if cfg!(not(debug_assertions)) && metadata.draft { continue; } - self.loaded_metadata.push((id, metadata)); + lmd.push((id, metadata)); } } - self.loaded_metadata - .sort_by(|a, b| b.1.timestamp.cmp(&a.1.timestamp)); + lmd.sort_by(|a, b| b.1.timestamp.cmp(&a.1.timestamp)); Ok(()) } @@ -224,7 +229,7 @@ where builder.reg.render(&self.config.resource_template, &data)? }; - let out = builder.build_page_raw_extra( + let out = builder.build_page_raw_with_extra_data( PageMetadata { title: Some(resource.title.clone()), ..Default::default() @@ -250,12 +255,14 @@ where std::fs::create_dir_all(&out_long)?; } - for (id, resource) in &self.loaded_metadata { + let lmd = self.loaded_metadata.borrow(); + + for (id, resource) in lmd.iter() { self.build(builder, id.clone(), resource)?; } - let mut data = Vec::with_capacity(self.loaded_metadata.len()); - for (id, resource) in &self.loaded_metadata { + let mut data = Vec::with_capacity(lmd.len()); + for (id, resource) in lmd.iter() { let extra = resource.get_extra_resource_template_data(&builder.site.config)?; data.push(ResourceTemplateData { resource,