forked from slonk/bingus-blog
add custom date formatting and client side date formatting
This commit is contained in:
parent
cf102126b3
commit
84932c0d1e
10 changed files with 124 additions and 36 deletions
12
README.md
12
README.md
|
@ -22,7 +22,7 @@ blazingly fast markdown blog software written in rust memory safe
|
|||
- [ ] ^ replace HashMap with HashCache once i implement [this](https://github.com/wvwwvwwv/scalable-concurrent-containers/issues/139)
|
||||
- [x] (de)compress cache with zstd on startup/shutdown
|
||||
- [ ] make date parsing less strict
|
||||
- [ ] make date formatting better
|
||||
- [x] make date formatting better
|
||||
- [ ] date formatting respects user timezone
|
||||
- [x] clean up imports and require less features
|
||||
- [ ] improve home page
|
||||
|
@ -36,10 +36,16 @@ blazingly fast markdown blog software written in rust memory safe
|
|||
the default configuration with comments looks like this
|
||||
|
||||
```toml
|
||||
title = "bingus-blog" # title of the website
|
||||
description = "blazingly fast markdown blog software written in rust memory safe" # description of the website
|
||||
title = "bingus-blog" # title of the blog
|
||||
# description of the blog
|
||||
description = "blazingly fast markdown blog software written in rust memory safe"
|
||||
markdown_access = true # allow users to see the raw markdown of a post
|
||||
# endpoint: /posts/<name>.md
|
||||
date_format = "RFC3339" # format string used to format dates in the backend
|
||||
# it's highly recommended to leave this as default,
|
||||
# so the date can be formatted by the browser.
|
||||
# format: https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers
|
||||
js_enable = true # enable javascript (required for above)
|
||||
|
||||
[rss]
|
||||
enable = false # serve an rss field under /feed.xml
|
||||
|
|
10
src/app.rs
10
src/app.rs
|
@ -14,7 +14,7 @@ use tower_http::services::ServeDir;
|
|||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{info, info_span, Span};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, DateFormat};
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::filters;
|
||||
use crate::post::{MarkdownPosts, PostManager, PostMetadata, RenderStats, ReturnedPost};
|
||||
|
@ -31,6 +31,8 @@ struct IndexTemplate {
|
|||
title: String,
|
||||
description: String,
|
||||
posts: Vec<PostMetadata>,
|
||||
df: DateFormat,
|
||||
js: bool,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
|
@ -40,6 +42,8 @@ struct PostTemplate {
|
|||
rendered: String,
|
||||
rendered_in: RenderStats,
|
||||
markdown_access: bool,
|
||||
df: DateFormat,
|
||||
js: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -61,6 +65,8 @@ async fn index(
|
|||
title: config.title.clone(),
|
||||
description: config.description.clone(),
|
||||
posts,
|
||||
df: config.date_format.clone(),
|
||||
js: config.js_enable,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -145,6 +151,8 @@ async fn post(
|
|||
rendered,
|
||||
rendered_in,
|
||||
markdown_access: config.markdown_access,
|
||||
df: config.date_format.clone(),
|
||||
js: config.js_enable,
|
||||
};
|
||||
|
||||
Ok(page.into_response())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::env;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::net::{IpAddr, Ipv6Addr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::{bail, Context, Result};
|
||||
|
@ -59,13 +59,22 @@ pub struct RssConfig {
|
|||
pub link: Url,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub enum DateFormat {
|
||||
#[default]
|
||||
RFC3339,
|
||||
#[serde(untagged)]
|
||||
Strftime(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub markdown_access: bool,
|
||||
pub num_posts: usize,
|
||||
pub date_format: DateFormat,
|
||||
pub js_enable: bool,
|
||||
pub rss: RssConfig,
|
||||
pub dirs: DirsConfig,
|
||||
pub http: HttpConfig,
|
||||
|
@ -79,7 +88,8 @@ impl Default for Config {
|
|||
title: "bingus-blog".into(),
|
||||
description: "blazingly fast markdown blog software written in rust memory safe".into(),
|
||||
markdown_access: true,
|
||||
num_posts: 5,
|
||||
date_format: Default::default(),
|
||||
js_enable: true,
|
||||
// i have a love-hate relationship with serde
|
||||
// it was engimatic at first, but then i started actually using it
|
||||
// writing my own serialize and deserialize implementations.. spending
|
||||
|
@ -111,7 +121,7 @@ impl Default for DirsConfig {
|
|||
impl Default for HttpConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
|
||||
host: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
|
||||
port: 3000,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
use std::{collections::HashMap, time::Duration};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{DateTime, TimeZone};
|
||||
|
||||
use crate::config::DateFormat;
|
||||
use crate::post::PostMetadata;
|
||||
|
||||
pub fn date<T: TimeZone>(date: &DateTime<T>) -> Result<String, askama::Error> {
|
||||
Ok(date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
|
||||
fn format_date<T>(date: &DateTime<T>, date_format: &DateFormat) -> String
|
||||
where
|
||||
T: TimeZone,
|
||||
T::Offset: Display,
|
||||
{
|
||||
match date_format {
|
||||
DateFormat::RFC3339 => date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
|
||||
DateFormat::Strftime(ref format_string) => date.format(format_string).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date<T>(date: &DateTime<T>, date_format: &DateFormat) -> Result<String, askama::Error>
|
||||
where
|
||||
T: TimeZone,
|
||||
T::Offset: Display,
|
||||
{
|
||||
Ok(format_date(date, date_format))
|
||||
}
|
||||
|
||||
pub fn duration(duration: &&Duration) -> Result<String, askama::Error> {
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::io::Read;
|
||||
|
||||
use crate::config::{Config, RenderConfig};
|
||||
use crate::post::PostMetadata;
|
||||
use color_eyre::eyre::{self, Context};
|
||||
use scc::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::config::{Config, RenderConfig};
|
||||
use crate::post::PostMetadata;
|
||||
|
||||
/// do not persist cache if this version number changed
|
||||
pub const CACHE_VERSION: u16 = 2;
|
||||
|
||||
|
|
4
static/main.js
Normal file
4
static/main.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
for (let el of document.querySelectorAll(".date-rfc3339")) {
|
||||
let date = new Date(Date.parse(el.textContent));
|
||||
el.textContent = date.toLocaleString();
|
||||
}
|
|
@ -84,6 +84,35 @@ div.post {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: grid;
|
||||
/*grid-template-columns: auto auto auto;
|
||||
grid-template-rows: auto auto;*/
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.table > :not(.value)::after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
.table > .value {
|
||||
margin-left: 1em;
|
||||
text-align: end;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.table > .created {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.table > .modified {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.table > .tags {
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
/* BEGIN cool effect everyone liked */
|
||||
|
||||
body {
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
<meta property="og:description" content="{{ description }}" />
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
{% if js %}
|
||||
<script src="/static/main.js" defer></script>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
{% macro span_date(value) %}
|
||||
<span class="{%- match df -%}
|
||||
{% when DateFormat::RFC3339 %}
|
||||
date-rfc3339
|
||||
{% when DateFormat::Strftime(_) %}
|
||||
{%- endmatch -%}">{{ value|date(df) }}</span>
|
||||
{% endmacro %}
|
||||
{% macro table(post) %}
|
||||
<div class="table">
|
||||
{% match post.created_at %}
|
||||
{% when Some(created_at) %}
|
||||
written: {{ created_at|date }}<br />
|
||||
<div class="created">written</div>
|
||||
<div class="created value">{% call span_date(created_at) %}</div>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
{% match post.modified_at %}
|
||||
{% when Some(modified_at) %}
|
||||
last modified: {{ modified_at|date }}<br />
|
||||
<div class="modified">last modified</div>
|
||||
<div class="modified value">{% call span_date(modified_at) %}</div>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
|
||||
{% if !post.tags.is_empty() %}
|
||||
tags:
|
||||
<div class="tags">tags</div>
|
||||
<div class="tags value">
|
||||
{% for tag in post.tags %}
|
||||
<a href="/?tag={{ tag }}" title="view all posts with this tag">{{ tag }}</a>
|
||||
{% endfor %}<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -2,20 +2,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="{{ meta.title }}" />
|
||||
<meta property="og:title" content="{{ meta.title }}" />
|
||||
<meta property="og:description" content="{{ meta.description }}" />
|
||||
{% match meta.icon %} {% when Some with (url) %}
|
||||
<meta property="og:image" content="{{ url }}" />
|
||||
<link rel="shortcut icon" href="{{ url }}" />
|
||||
{% when None %} {% endmatch %}
|
||||
<title>{{ meta.title }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<link rel="stylesheet" href="/static/post.css" />
|
||||
</head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="{{ meta.title }}" />
|
||||
<meta property="og:title" content="{{ meta.title }}" />
|
||||
<meta property="og:description" content="{{ meta.description }}" />
|
||||
{% match meta.icon %} {% when Some with (url) %}
|
||||
<meta property="og:image" content="{{ url }}" />
|
||||
<link rel="shortcut icon" href="{{ url }}" />
|
||||
{% when None %} {% endmatch %}
|
||||
<title>{{ meta.title }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<link rel="stylesheet" href="/static/post.css" />
|
||||
{% if js %}
|
||||
<script src="/static/main.js" defer></script>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
@ -24,11 +25,9 @@
|
|||
<span class="post-author">- by {{ meta.author }}</span>
|
||||
</h1>
|
||||
<p class="post-desc">{{ meta.description }}</p>
|
||||
<div class="" post>
|
||||
<div class="post">
|
||||
<!-- prettier-ignore -->
|
||||
<div>
|
||||
{% call macros::table(meta) %}
|
||||
</div>
|
||||
<a href="/posts/{{ meta.name }}">link</a><br />
|
||||
<a href="/">back to home</a>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue