diff --git a/404.html b/404.html deleted file mode 100644 index 08d5bf3..0000000 --- a/404.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - 404 Not Found - - -

404 Not Found

- - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 01a445a..87e12f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -153,6 +164,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.2" @@ -354,6 +371,25 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -471,6 +507,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "equivalent" version = "1.0.1" @@ -708,6 +750,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -942,6 +987,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1034,6 +1090,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1054,6 +1116,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1166,6 +1238,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -1210,6 +1292,12 @@ dependencies = [ "regex", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1343,7 +1431,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ - "base64", + "base64 0.21.2", "indexmap 1.9.3", "line-wrap", "quick-xml", @@ -1515,6 +1603,27 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1891,6 +2000,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.7.6" @@ -1953,7 +2071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" dependencies = [ "async-compression", - "base64", + "base64 0.21.2", "bitflags 2.3.3", "bytes", "futures-core", @@ -2306,8 +2424,8 @@ dependencies = [ "cached", "chrono", "color-eyre", + "config", "glob", - "lazy_static", "opentelemetry", "prometheus", "pulldown-cmark", @@ -2318,7 +2436,7 @@ dependencies = [ "syntect", "tera", "tokio", - "toml", + "toml 0.7.6", "tower", "tower-http", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 4633d3d..18fd04e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,8 @@ axum = { version = "0.6.12", features = ["http2", "original-uri"] } cached = "0.44.0" chrono = { version = "0.4.24", features = ["serde"] } color-eyre = "0.6.1" +config = "0.13.3" glob = "0.3.0" -# hyper = { version = "0.14.19", features = ["full"] } -lazy_static = "1.4.0" opentelemetry = { version = "0.19.0", features = ["metrics"] } prometheus = { version = "0.13.3", features = ["process"] } pulldown-cmark = "0.9.2" diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..f008622 --- /dev/null +++ b/config.toml @@ -0,0 +1,3 @@ +base_url = "http://localhost:8080/" +bind_address = "0.0.0.0:8080" +logging = "info" diff --git a/posts/draft-test.md b/posts/draft-test.md new file mode 100644 index 0000000..1c29ae7 --- /dev/null +++ b/posts/draft-test.md @@ -0,0 +1,7 @@ ++++ +title="draft test" +draft=true +date=2023-07-29T17:25:20+02:00 ++++ + +wow look it's a hidden post because it's marked as a draft diff --git a/posts/foldertest/index.md b/posts/foldertest/index.md index 8515882..5a1cfa3 100644 --- a/posts/foldertest/index.md +++ b/posts/foldertest/index.md @@ -13,6 +13,8 @@ here have a squid kid miku to test relative paths: modified post test, see if docker skips build using its cache if only a post has changed. +testing "smart" punctuation --- I don't know if I want it. 'it should' do some fancy stuff. + code hilighting test: ```rs @@ -20,3 +22,11 @@ fn main() { println!("Hello world!") } ``` + +uh oh, here comes a screenshot from a different post! + +![dungeon screenshot](../dungeon/screenshot.png) + +and here it is again, except it should 404! + +![missing dungeon screenshot](../dungeon/screenshot.jpeg) diff --git a/src/feed.rs b/src/feed.rs index 46d50a0..ecc54d7 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -16,7 +16,7 @@ struct FeedContext<'a> { #[instrument(skip(state))] pub fn render_atom_feed(state: &AppState) -> Result { - let mut posts: Vec<_> = state.posts.values().filter(|p| p.is_published()).collect(); + let mut posts: Vec<_> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect(); posts.sort_by_key(|p| &p.date); posts.reverse(); @@ -41,7 +41,7 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result { let mut posts: Vec<_> = state .posts .values() - .filter(|p| p.is_published() && p.tags.contains(&tag.slug)) + .filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag.slug)) .collect(); posts.sort_by_key(|p| &p.date); diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 2f9769f..c4cdc09 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -7,14 +7,14 @@ use axum::{ routing::get, Router, }; +use cached::once_cell::sync::Lazy; use chrono::{DateTime, FixedOffset}; -use lazy_static::lazy_static; use prometheus::{opts, Encoder, IntCounterVec, TextEncoder}; use std::sync::Arc; -use tower_http::services::ServeFile; +use tower_http::services::ServeDir; use tracing::{ instrument, - log::{error, info}, + log::{error, info, debug}, }; use crate::{AppState, WebsiteError}; @@ -22,16 +22,14 @@ use crate::{AppState, WebsiteError}; pub mod posts; pub mod tags; -lazy_static! { - pub static ref HIT_COUNTER: IntCounterVec = prometheus::register_int_counter_vec!( - opts!( - "http_requests_total", - "Total amount of http requests received" - ), - &["route", "method", "status"] - ) - .unwrap(); -} +pub static HIT_COUNTER: Lazy = Lazy::new(|| prometheus::register_int_counter_vec!( + opts!( + "http_requests_total", + "Total amount of http requests received" + ), + &["route", "method", "status"] +) +.unwrap()); pub fn routes(state: &Arc) -> Router> { Router::new() @@ -44,13 +42,12 @@ pub fn routes(state: &Arc) -> Router> { .route("/metrics", get(metrics)) .route_service( "/posts/:slug/*path", - tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")), + ServeDir::new("./"), ) .route_service( "/static/*path", - tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")), + ServeDir::new("./"), ) - .fallback_service(ServeFile::new("./404.html")) } #[instrument(skip(state))] @@ -61,10 +58,11 @@ pub async fn index( if should_return_304(&headers, Some(state.startup_time.into())) { return Ok(StatusCode::NOT_MODIFIED.into_response()); } - let ctx = tera::Context::new(); + let mut ctx = tera::Context::new(); + ctx.insert("base_url", &state.base_url.to_string()); let res = state.tera.render("index.html", &ctx).map_err(|e| { error!("Failed rendering index: {}", e); - WebsiteError::NotFound + WebsiteError::InternalError(e.into()) })?; Ok(( StatusCode::OK, @@ -97,17 +95,17 @@ pub async fn not_found() -> impl IntoResponse { pub async fn metrics_middleware(request: Request, next: Next) -> Response { let path = request.uri().path().to_string(); - let method = request.method().to_string(); + let method = request.method().clone(); let response = next.run(request).await; if !response.status().is_client_error() { HIT_COUNTER - .with_label_values(&[&path, &method, response.status().as_str()]) + .with_label_values(&[&path, method.as_str(), response.status().as_str()]) .inc(); } else if response.status() == StatusCode::NOT_FOUND { HIT_COUNTER - .with_label_values(&["not found", &method, response.status().as_str()]) + .with_label_values(&["not found", method.as_str(), response.status().as_str()]) .inc(); } @@ -116,16 +114,16 @@ pub async fn metrics_middleware(request: Request, next: Next) -> Respon fn should_return_304(headers: &HeaderMap, last_changed: Option>) -> bool { let Some(date) = last_changed else { - info!("no last modified date"); + debug!("no last modified date"); return false; }; let Some(since) = headers.get(header::IF_MODIFIED_SINCE) else { - info!("no IF_MODIFIED_SINCE header"); + debug!("no IF_MODIFIED_SINCE header"); return false; }; let Ok(parsed) = DateTime::::parse_from_rfc2822(since.to_str().unwrap()) else { - info!("failed to parse IF_MODIFIED_SINCE header"); + debug!("failed to parse IF_MODIFIED_SINCE header"); return false; }; @@ -160,7 +158,8 @@ mod tests { #[test] fn render_index() { let tera = tera::Tera::new("templates/**/*").unwrap(); - let ctx = tera::Context::new(); + let mut ctx = tera::Context::new(); + ctx.insert("base_url", "http://localhost/"); let _res = tera.render("index.html", &ctx).unwrap(); } diff --git a/src/handlers/posts.rs b/src/handlers/posts.rs index 254c35a..1c58e3a 100644 --- a/src/handlers/posts.rs +++ b/src/handlers/posts.rs @@ -56,7 +56,7 @@ pub async fn index( State(state): State>, headers: HeaderMap, ) -> Result { - let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect(); + let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect(); let last_changed = posts.iter().filter_map(|p| p.last_modified()).max(); @@ -135,7 +135,7 @@ pub async fn feed( State(state): State>, headers: HeaderMap, ) -> Result { - let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect(); + let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect(); let last_changed = posts.iter().filter_map(|p| p.last_modified()).max(); @@ -203,6 +203,7 @@ mod tests { ]; let page = PageContext { title: "Posts" }; let mut ctx = tera::Context::new(); + ctx.insert("base_url", "http://localhost/"); ctx.insert("page", &page); ctx.insert("posts", &posts); diff --git a/src/handlers/tags.rs b/src/handlers/tags.rs index c20d15e..572cee7 100644 --- a/src/handlers/tags.rs +++ b/src/handlers/tags.rs @@ -57,7 +57,7 @@ pub async fn view( let mut posts: Vec<&Post> = state .posts .values() - .filter(|p| p.is_published() && p.tags.contains(&tag)) + .filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag)) .collect(); let last_changed = posts.iter().filter_map(|p| p.last_modified()).max(); diff --git a/src/helpers.rs b/src/helpers.rs index fdee416..e60c274 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -31,3 +31,28 @@ pub fn uri_with_path(uri: &Uri, path: &str) -> Uri { .build() .unwrap(); } + +#[cfg(test)] +mod tests { + use axum::http::Uri; + + use crate::helpers::uri_with_path; + + #[test] + fn uri_with_relative_path() { + let uri: Uri = "http://localhost/baba/".parse().unwrap(); + assert_eq!(uri_with_path(&uri, "is/you").to_string(), "http://localhost/baba/is/you"); + } + + #[test] + fn uri_with_absolute_path() { + let uri: Uri = "http://localhost/baba/is/you".parse().unwrap(); + assert_eq!(uri_with_path(&uri, "/keke/is/move").to_string(), "http://localhost/keke/is/move"); + } + + #[test] + fn uri_with_index_relative_path() { + let uri: Uri = "http://localhost/baba/is/you".parse().unwrap(); + assert_eq!(uri_with_path(&uri, "happy").to_string(), "http://localhost/baba/is/happy"); + } +} diff --git a/src/main.rs b/src/main.rs index 8513a84..787b340 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use axum::{ use chrono::DateTime; use color_eyre::eyre::{Error, Result}; +use config::Config; use post::Post; use tag::Tag; @@ -39,15 +40,20 @@ pub struct AppState { #[tokio::main] async fn main() -> Result<()> { + let cfg = Config::builder() + .add_source(config::File::with_name("config.toml")) + .add_source(config::Environment::with_prefix("APP")) + .build()?; + color_eyre::install()?; - init_tracing(); + init_tracing(&cfg); info!("Starting server..."); - let base_url: Uri = option_env!("SITE_BASE_URL") - .unwrap_or("http://localhost:8080") + let base_url: Uri = cfg.get_string("base_url")? .parse() .unwrap(); + let tera = Tera::new("templates/**/*")?; let mut state = AppState { startup_time: chrono::offset::Utc::now(), @@ -55,6 +61,7 @@ async fn main() -> Result<()> { tera, ..Default::default() }; + let posts = post::load_all(&state).await?; let tags = tag::get_tags(posts.values()); state.posts = posts; @@ -73,17 +80,23 @@ async fn main() -> Result<()> { info!("Now listening at {}", state.base_url); - axum::Server::bind(&"0.0.0.0:8080".parse().unwrap()) + axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap()) .serve(app.into_make_service()) .await?; Ok(()) } -fn init_tracing() { - let filter = EnvFilter::builder() - .with_default_directive("into".parse().unwrap()) - .from_env_lossy(); +fn init_tracing(cfg: &Config) { + let filter = if let Ok(filter) = cfg.get_string("logging") { + EnvFilter::builder() + .with_default_directive("info".parse().unwrap()) + .parse_lossy(filter) + } else { + EnvFilter::builder() + .with_default_directive("info".parse().unwrap()) + .from_env_lossy() + }; tracing_subscriber::registry() .with(filter) diff --git a/src/markdown.rs b/src/markdown.rs index c93400d..61e819f 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -1,14 +1,26 @@ use crate::helpers; use crate::hilighting; use axum::http::Uri; +use cached::once_cell::sync::Lazy; use pulldown_cmark::Event; use pulldown_cmark::Tag; use pulldown_cmark::{Options, Parser}; +use regex::Regex; + +static STARTS_WITH_SCHEMA_RE: Lazy = Lazy::new(|| Regex::new(r"^\w+:").unwrap()); +static EMAIL_RE: Lazy = Lazy::new(|| Regex::new(r"^.+?@\w+(\.\w+)*$").unwrap()); pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String { - let options = Options::all(); + let mut opt = Options::empty(); + opt.insert(Options::ENABLE_FOOTNOTES); + opt.insert(Options::ENABLE_HEADING_ATTRIBUTES); + opt.insert(Options::ENABLE_STRIKETHROUGH); + opt.insert(Options::ENABLE_TABLES); + opt.insert(Options::ENABLE_TASKLISTS); + opt.insert(Options::ENABLE_SMART_PUNCTUATION); + let mut content_html = String::new(); - let parser = Parser::new_ext(markdown, options); + let parser = Parser::new_ext(markdown, opt); let mut code_block = false; let mut code_lang = None; @@ -25,7 +37,7 @@ pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String } Event::Start(Tag::Link(t, mut link, title)) => { if let Some(uri) = base_uri { - if !link.contains("://") && !link.contains('@') { + if !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(&link) && !EMAIL_RE.is_match(&link) { // convert relative URIs to absolute URIs link = helpers::uri_with_path(uri, &link).to_string().into(); } diff --git a/src/post.rs b/src/post.rs index 02da51b..c1b0d92 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, path::Path}; +use cached::once_cell::sync::Lazy; use chrono::{DateTime, FixedOffset}; use glob::glob; -use lazy_static::lazy_static; - use regex::Regex; use serde_derive::{Deserialize, Serialize}; use tokio::fs; @@ -16,6 +15,11 @@ use tracing::{ use crate::{helpers, markdown, AppState, WebsiteError}; +static FRONTMATTER_REGEX: Lazy = Lazy::new(|| Regex::new( + r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))" +) +.unwrap()); + #[derive(Deserialize, Debug, Default)] pub struct TomlFrontMatter { pub title: String, @@ -29,6 +33,7 @@ pub struct TomlFrontMatter { #[derive(Serialize, Clone, Debug, Default)] pub struct Post { pub title: String, + pub draft: bool, pub date: Option>, pub updated: Option>, pub aliases: Vec, @@ -45,6 +50,7 @@ impl Post { slug, content, title: fm.title, + draft: fm.draft.unwrap_or(false), date: fm .date .map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")), @@ -124,13 +130,6 @@ pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result fn parse_frontmatter( src: String, ) -> color_eyre::eyre::Result<(Option, Option)> { - lazy_static! { - static ref FRONTMATTER_REGEX: Regex = regex::Regex::new( - r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))" - ) - .unwrap(); - }; - Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) { ( Some(toml::from_str(captures.get(1).unwrap().as_str())?), @@ -162,10 +161,11 @@ mod tests { #[tokio::test] async fn render_all_posts() { let mut state = AppState { - base_url: "localhost:8180".parse().unwrap(), + base_url: "http://localhost:8180".parse().unwrap(), tera: Tera::new("templates/**/*").unwrap(), ..Default::default() }; + state.posts = super::load_all(&state).await.unwrap(); for post in state.posts.values() { super::render_post(&state, post).await.unwrap(); diff --git a/templates/base.html b/templates/base.html index 66fc8c5..2628661 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,16 +2,10 @@ {% include "partials/head.html" %} -
- {% include "partials/header.html" -%} -
-
+{% include "partials/header.html" -%}
{% block main %}{% endblock main -%}
-
-
- {% include "partials/footer.html" -%} -
+{% include "partials/footer.html" -%} diff --git a/templates/partials/footer.html b/templates/partials/footer.html index 7a80ace..e71c15e 100644 --- a/templates/partials/footer.html +++ b/templates/partials/footer.html @@ -1 +1,4 @@ -

footer stuff goes here

\ No newline at end of file +
+
+

footer stuff goes here

+
\ No newline at end of file diff --git a/templates/partials/header.html b/templates/partials/header.html index 328de26..6227b2c 100644 --- a/templates/partials/header.html +++ b/templates/partials/header.html @@ -1,3 +1,5 @@ - +
+ +
diff --git a/templates/post.html b/templates/post.html index ce9f752..8d475f6 100644 --- a/templates/post.html +++ b/templates/post.html @@ -6,9 +6,11 @@

{{ page.title }}

{% endif -%} {% if page.date -%} - Posted on + + {%- if page.draft %}Draft {% endif -%} + Published {%- if page.updated -%} - , Updated + , Updated {%- endif -%} {%- endif %} diff --git a/templates/posts_index.html b/templates/posts_index.html index 30acb5a..1842e4b 100644 --- a/templates/posts_index.html +++ b/templates/posts_index.html @@ -5,7 +5,7 @@