-
-
\ 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" -%}
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
+
\ 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 @@