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()))
+ }
}