diff --git a/Cargo.lock b/Cargo.lock index 198f555..c20dd3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "estimated_read_time" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649cfd341410b1f8906e8ca1b39e5534be9312fda9182edd770cec34dfbce8d7" + [[package]] name = "fnv" version = "1.0.7" @@ -660,6 +666,7 @@ dependencies = [ "axum", "chrono", "dotenv", + "estimated_read_time", "lazy_static", "markdown", "mime_guess", diff --git a/Cargo.toml b/Cargo.toml index 9ede941..b471102 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ serde_json = "1.0.132" tera = { version = "1.20.0", default-features = false } tokio = { version = "1.41.1", features = ["rt-multi-thread"] } chrono = "0.4.38" +estimated_read_time="1.0.0" [lints.clippy] all = "warn" diff --git a/src/data/blog.rs b/src/data/blog.rs index 03944b1..8ec244c 100644 --- a/src/data/blog.rs +++ b/src/data/blog.rs @@ -2,6 +2,7 @@ use atom_syndication::{ ContentBuilder, EntryBuilder, FeedBuilder, LinkBuilder, PersonBuilder, TextBuilder, }; use chrono::TimeZone; +use lazy_static::lazy_static; use nom::{branch::permutation, IResult}; use rust_embed::{Embed, EmbeddedFile}; use serde::{Deserialize, Serialize}; @@ -10,6 +11,10 @@ use std::error::Error; const DATE_FORMAT: &str = "%d/%m/%Y"; +lazy_static! { + static ref ERT_OPTIONS: estimated_read_time::Options = estimated_read_time::Options::new(); +} + #[derive(Embed)] #[folder = "content/"] #[include = "*.md"] @@ -23,6 +28,7 @@ pub struct Post { slug: String, tags: Vec, content: String, + read_time: u64, id: Option, } @@ -67,6 +73,9 @@ impl Post { match content { Ok(content) => { + let read_time_seconds = estimated_read_time::text(&content, &ERT_OPTIONS).seconds(); + let read_time = read_time_seconds / 60; + let ret = Self { title: meta.title, date: meta.date, @@ -74,6 +83,7 @@ impl Post { tags: meta.tags, slug: String::new(), content, + read_time, id: None, }; @@ -313,6 +323,7 @@ mod tests { slug: "hello-world".to_string(), tags: vec!["test".to_string(), "post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(1), }, Post { @@ -322,6 +333,7 @@ mod tests { slug: "hello-world".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(2), }, ]); @@ -341,6 +353,7 @@ mod tests { slug: "hello-world-1".to_string(), tags: vec!["test".to_string(), "post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(1), }, Post { @@ -350,6 +363,7 @@ mod tests { slug: "hello-world-2".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(2), }, ]); @@ -369,6 +383,7 @@ mod tests { slug: "hello-world-1".to_string(), tags: vec!["test".to_string(), "post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(1), }, Post { @@ -378,6 +393,7 @@ mod tests { slug: "hello-world-2".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(2), }, ]); @@ -397,6 +413,7 @@ mod tests { slug: "hello-world-1".to_string(), tags: vec!["test".to_string(), "post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(1), }, Post { @@ -406,6 +423,7 @@ mod tests { slug: "hello-world-2".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(2), }, Post { @@ -415,6 +433,7 @@ mod tests { slug: "hello-world-2".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(3), }, Post { @@ -424,6 +443,7 @@ mod tests { slug: "hello-world-2".to_string(), tags: vec!["post".to_string()], content: "

Hello, World!

\n

This is a test post.

\n".to_string(), + read_time: 0, id: Some(4), }, ]; diff --git a/styles/pages/blog.scss b/styles/pages/blog.scss index a3d8905..61c0b69 100644 --- a/styles/pages/blog.scss +++ b/styles/pages/blog.scss @@ -7,3 +7,8 @@ color: var(--fg); } } + +.readtime { + color: var(--fg-lighter); + font-size: 1rem; +} diff --git a/styles/text.scss b/styles/text.scss index ce3d9a0..6ece7a3 100644 --- a/styles/text.scss +++ b/styles/text.scss @@ -19,6 +19,14 @@ padding-top: 1rem; } + h3 { + font-size: 1.5rem; + line-height: 2rem; + + padding-bottom: .5rem; + padding-top: 1rem; + } + ol, ul { padding-left: 2rem; } diff --git a/templates/pages/post.tera b/templates/pages/post.tera index 99c7fbd..2f19f61 100644 --- a/templates/pages/post.tera +++ b/templates/pages/post.tera @@ -16,6 +16,7 @@

{{ post.title }}

{{ post.description }}

+

Read time: {{ post.read_time }}mins