diff --git a/site_test/css/main.scss b/site_test/css/main.scss index 879347a..638b69c 100644 --- a/site_test/css/main.scss +++ b/site_test/css/main.scss @@ -120,6 +120,15 @@ header { .article-title { // balance the number of words per line text-wrap: balance; + + &:has(+ .article-meta) { + margin-bottom: 0.2em; + } +} + +.article-meta { + margin-top: 0; + color: var(--secondary-text-color); } .article-content { diff --git a/site_test/layout/article.html b/site_test/layout/article.html index 8c4bd48..563bff0 100644 --- a/site_test/layout/article.html +++ b/site_test/layout/article.html @@ -38,6 +38,19 @@ {{ metadata.title }} {% endif %} +

+ Published on + + in + {% for tag in metadata.tags %} + + {{ tag.name }}{% if loop.last %}.{% else %},{% endif %} + + {% endfor %} + {{ word_count | reading_time }} minute read. +

{{ content }}
diff --git a/src/generator/posts.rs b/src/generator/posts.rs index 7dcb186..d3522be 100644 --- a/src/generator/posts.rs +++ b/src/generator/posts.rs @@ -284,6 +284,12 @@ impl DynamicRule for MakeWritePosts { |post_opt, ctx| { let post = post_opt.as_ref().unwrap(); ctx.insert("metadata", &post.metadata); + ctx.insert( + "word_count", + &post + .word_count + .expect("post should have word count when rendering"), + ); ctx.insert("content", post.content.html()); }, )) diff --git a/src/generator/templates.rs b/src/generator/templates.rs index 8581f87..820def3 100644 --- a/src/generator/templates.rs +++ b/src/generator/templates.rs @@ -24,8 +24,10 @@ pub fn make_graph( watcher: &mut FileWatcher, ) -> Input { let mut empty_templates = Templates::default(); + empty_templates.register_filter("iso_datetime", filters::iso_datetime); empty_templates.register_filter("pretty_date", filters::pretty_date); empty_templates.register_filter("pretty_datetime", filters::pretty_datetime); + empty_templates.register_filter("reading_time", filters::reading_time); let empty_templates = builder.add_value(empty_templates); let default_path = content_path("layout/default.html"); @@ -212,7 +214,7 @@ pub mod filters { use chrono::{DateTime, Datelike, Local}; use serde::Deserialize; use std::collections::HashMap; - use tera::{Result, Value}; + use tera::{Number, Result, Value}; // pub fn iso_date(date: &NaiveDate) -> String { // Utc::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()) @@ -228,6 +230,11 @@ pub mod filters { // datetime.format("%+:0").to_string() // } + pub fn iso_datetime(value: &Value, _args: &HashMap) -> Result { + let date = DateTime::::deserialize(value)?; + Ok(Value::String(date.format("%+:0").to_string())) + } + const MONTHS: &[&str; 12] = &[ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; @@ -260,8 +267,10 @@ pub mod filters { Ok(Value::String(s)) } - // pub fn reading_time(words: &u32) -> u32 { - // let wpm = 225.0; - // (*words as f32 / wpm).max(1.0) as u32 - // } + pub fn reading_time(value: &Value, _args: &HashMap) -> Result { + let words = u32::deserialize(value)?; + let wpm = 225.0; + let minutes = (words as f64 / wpm).max(1.0).round(); + Ok(Value::Number(Number::from_f64(minutes).unwrap())) + } }