Compare commits
9 commits
41228d55b6
...
6f7b9b7350
Author | SHA1 | Date | |
---|---|---|---|
6f7b9b7350 | |||
a8a1dca444 | |||
cee11ba07a | |||
38d93a66ba | |||
3623b61fbe | |||
bd093e7c20 | |||
658ddaf820 | |||
342a353b36 | |||
602f57581a |
9 changed files with 76 additions and 35 deletions
29
README.md
29
README.md
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
title: "README"
|
title: README
|
||||||
description: "the README.md file of this project"
|
description: the README.md file of this project
|
||||||
author: "slonkazoid"
|
author: slonkazoid
|
||||||
created_at: 2024-04-18T04:15:26+03:00
|
created_at: 2024-04-18T04:15:26+03:00
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ blazingly fast markdown blog software written in rust memory safe
|
||||||
- [x] RSS
|
- [x] RSS
|
||||||
- [x] finish writing this document
|
- [x] finish writing this document
|
||||||
- [x] document config
|
- [x] document config
|
||||||
|
- [ ] blog thumbnail and favicon
|
||||||
|
- [x] alt text for post icon
|
||||||
- [ ] extend syntect options
|
- [ ] extend syntect options
|
||||||
- [ ] general cleanup of code
|
- [ ] general cleanup of code
|
||||||
- [ ] better error reporting and error pages
|
- [ ] better error reporting and error pages
|
||||||
|
@ -46,6 +48,7 @@ date_format = "RFC3339" # format string used to format dates in the backend
|
||||||
# so the date can be formatted by the browser.
|
# so the date can be formatted by the browser.
|
||||||
# format: https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers
|
# format: https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers
|
||||||
js_enable = true # enable javascript (required for above)
|
js_enable = true # enable javascript (required for above)
|
||||||
|
default_color = "#f5c2e7" # default embed color, optional
|
||||||
|
|
||||||
[rss]
|
[rss]
|
||||||
enable = false # serve an rss field under /feed.xml
|
enable = false # serve an rss field under /feed.xml
|
||||||
|
@ -55,6 +58,7 @@ link = "https://..." # public url of the blog, required if rss is enabled
|
||||||
[dirs]
|
[dirs]
|
||||||
posts = "posts" # where posts are stored
|
posts = "posts" # where posts are stored
|
||||||
media = "media" # directory served under /media/
|
media = "media" # directory served under /media/
|
||||||
|
static = "static" # directory server under /static/ (css and js)
|
||||||
|
|
||||||
[http]
|
[http]
|
||||||
host = "0.0.0.0" # ip to listen on
|
host = "0.0.0.0" # ip to listen on
|
||||||
|
@ -125,15 +129,22 @@ every post **must** begin with a **valid** front matter. else it wont be listed
|
||||||
in / & /posts, and when you navigate to it, you will be met with an error page.
|
in / & /posts, and when you navigate to it, you will be met with an error page.
|
||||||
the error page will tell you what the problem is.
|
the error page will tell you what the problem is.
|
||||||
|
|
||||||
example:
|
full example:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
---
|
---
|
||||||
title: "README"
|
title: My first post # title of the post
|
||||||
description: "the README.md file of this project"
|
description: The first post on this awesome blog! # short description of the post
|
||||||
author: "slonkazoid"
|
author: Blubber256 # author of the post
|
||||||
created_at: 2024-04-18T04:15:26+03:00
|
icon: /media/first-post/icon.png # icon/thumbnail of post used in embeds
|
||||||
#modified_at: ... # see above
|
icon_alt: Picture of a computer running DOOM
|
||||||
|
color: "#00aacc" # color of post, also used in embeds
|
||||||
|
created_at: 2024-04-18T04:15:26+03:00 # date of writing, this is highly
|
||||||
|
# recommended if you are on a system which doesnt have btime (like musl),
|
||||||
|
# because this is fetched from file stats by default
|
||||||
|
#modified_at: ... # see above. this is also fetched from the filesystem
|
||||||
|
tags: # tags, or keywords, used in meta and also in the ui
|
||||||
|
- lifestyle
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
50
src/app.rs
50
src/app.rs
|
@ -27,24 +27,26 @@ pub struct AppState {
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate {
|
struct IndexTemplate<'a> {
|
||||||
title: String,
|
title: &'a str,
|
||||||
description: String,
|
description: &'a str,
|
||||||
posts: Vec<PostMetadata>,
|
posts: Vec<PostMetadata>,
|
||||||
rss: bool,
|
rss: bool,
|
||||||
df: DateFormat,
|
df: &'a DateFormat,
|
||||||
js: bool,
|
js: bool,
|
||||||
|
color: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "post.html")]
|
#[template(path = "post.html")]
|
||||||
struct PostTemplate {
|
struct PostTemplate<'a> {
|
||||||
meta: PostMetadata,
|
meta: &'a PostMetadata,
|
||||||
rendered: String,
|
rendered: String,
|
||||||
rendered_in: RenderStats,
|
rendered_in: RenderStats,
|
||||||
markdown_access: bool,
|
markdown_access: bool,
|
||||||
df: DateFormat,
|
df: &'a DateFormat,
|
||||||
js: bool,
|
js: bool,
|
||||||
|
color: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -54,22 +56,24 @@ struct QueryParams {
|
||||||
num_posts: Option<usize>,
|
num_posts: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index(
|
async fn index<'a>(
|
||||||
State(AppState { config, posts }): State<AppState>,
|
State(AppState { config, posts }): State<AppState>,
|
||||||
Query(query): Query<QueryParams>,
|
Query(query): Query<QueryParams>,
|
||||||
) -> AppResult<IndexTemplate> {
|
) -> AppResult<Response> {
|
||||||
let posts = posts
|
let posts = posts
|
||||||
.get_max_n_post_metadata_with_optional_tag_sorted(query.num_posts, query.tag.as_ref())
|
.get_max_n_post_metadata_with_optional_tag_sorted(query.num_posts, query.tag.as_ref())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(IndexTemplate {
|
Ok(IndexTemplate {
|
||||||
title: config.title.clone(),
|
title: &config.title,
|
||||||
description: config.description.clone(),
|
description: &config.description,
|
||||||
posts,
|
posts,
|
||||||
rss: config.rss.enable,
|
rss: config.rss.enable,
|
||||||
df: config.date_format.clone(),
|
df: &config.date_format,
|
||||||
js: config.js_enable,
|
js: config.js_enable,
|
||||||
})
|
color: config.default_color.as_deref(),
|
||||||
|
}
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn all_posts(
|
async fn all_posts(
|
||||||
|
@ -147,18 +151,16 @@ async fn post(
|
||||||
Path(name): Path<String>,
|
Path(name): Path<String>,
|
||||||
) -> AppResult<Response> {
|
) -> AppResult<Response> {
|
||||||
match posts.get_post(&name).await? {
|
match posts.get_post(&name).await? {
|
||||||
ReturnedPost::Rendered(meta, rendered, rendered_in) => {
|
ReturnedPost::Rendered(ref meta, rendered, rendered_in) => Ok(PostTemplate {
|
||||||
let page = PostTemplate {
|
meta,
|
||||||
meta,
|
rendered,
|
||||||
rendered,
|
rendered_in,
|
||||||
rendered_in,
|
markdown_access: config.markdown_access,
|
||||||
markdown_access: config.markdown_access,
|
df: &config.date_format,
|
||||||
df: config.date_format.clone(),
|
js: config.js_enable,
|
||||||
js: config.js_enable,
|
color: meta.color.as_deref().or(config.default_color.as_deref()),
|
||||||
};
|
|
||||||
|
|
||||||
Ok(page.into_response())
|
|
||||||
}
|
}
|
||||||
|
.into_response()),
|
||||||
ReturnedPost::Raw(body, content_type) => {
|
ReturnedPost::Raw(body, content_type) => {
|
||||||
Ok(([(CONTENT_TYPE, content_type)], body).into_response())
|
Ok(([(CONTENT_TYPE, content_type)], body).into_response())
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ pub struct Config {
|
||||||
pub markdown_access: bool,
|
pub markdown_access: bool,
|
||||||
pub date_format: DateFormat,
|
pub date_format: DateFormat,
|
||||||
pub js_enable: bool,
|
pub js_enable: bool,
|
||||||
|
pub default_color: Option<String>,
|
||||||
pub rss: RssConfig,
|
pub rss: RssConfig,
|
||||||
pub dirs: DirsConfig,
|
pub dirs: DirsConfig,
|
||||||
pub http: HttpConfig,
|
pub http: HttpConfig,
|
||||||
|
@ -90,6 +91,7 @@ impl Default for Config {
|
||||||
markdown_access: true,
|
markdown_access: true,
|
||||||
date_format: Default::default(),
|
date_format: Default::default(),
|
||||||
js_enable: true,
|
js_enable: true,
|
||||||
|
default_color: Some("#f5c2e7".into()),
|
||||||
// i have a love-hate relationship with serde
|
// i have a love-hate relationship with serde
|
||||||
// it was engimatic at first, but then i started actually using it
|
// it was engimatic at first, but then i started actually using it
|
||||||
// writing my own serialize and deserialize implementations.. spending
|
// writing my own serialize and deserialize implementations.. spending
|
||||||
|
|
|
@ -27,6 +27,8 @@ struct FrontMatter {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub author: String,
|
pub author: String,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
pub icon_alt: Option<String>,
|
||||||
|
pub color: Option<String>,
|
||||||
pub created_at: Option<DateTime<Utc>>,
|
pub created_at: Option<DateTime<Utc>>,
|
||||||
pub modified_at: Option<DateTime<Utc>>,
|
pub modified_at: Option<DateTime<Utc>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -46,6 +48,8 @@ impl FrontMatter {
|
||||||
description: self.description,
|
description: self.description,
|
||||||
author: self.author,
|
author: self.author,
|
||||||
icon: self.icon,
|
icon: self.icon,
|
||||||
|
icon_alt: self.icon_alt,
|
||||||
|
color: self.color,
|
||||||
created_at: self.created_at.or_else(|| created.map(|t| t.into())),
|
created_at: self.created_at.or_else(|| created.map(|t| t.into())),
|
||||||
modified_at: self.modified_at.or_else(|| modified.map(|t| t.into())),
|
modified_at: self.modified_at.or_else(|| modified.map(|t| t.into())),
|
||||||
tags: self.tags.into_iter().collect(),
|
tags: self.tags.into_iter().collect(),
|
||||||
|
|
|
@ -17,6 +17,8 @@ pub struct PostMetadata {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub author: String,
|
pub author: String,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
pub icon_alt: Option<String>,
|
||||||
|
pub color: Option<String>,
|
||||||
pub created_at: Option<DateTime<Utc>>,
|
pub created_at: Option<DateTime<Utc>>,
|
||||||
pub modified_at: Option<DateTime<Utc>>,
|
pub modified_at: Option<DateTime<Utc>>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
|
@ -7,11 +7,16 @@
|
||||||
<meta name="description" content="{{ title }}" />
|
<meta name="description" content="{{ title }}" />
|
||||||
<meta property="og:title" content="{{ title }}" />
|
<meta property="og:title" content="{{ title }}" />
|
||||||
<meta property="og:description" content="{{ description }}" />
|
<meta property="og:description" content="{{ description }}" />
|
||||||
|
{% match color %} {% when Some with (color) %}
|
||||||
|
<meta name="theme-color" content="{{ color }}" />
|
||||||
|
{% when None %} {% endmatch %}
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
{% if rss %}
|
{% if rss %}
|
||||||
<link rel="alternate" type="application/rss+xml" title="{{ title }}" href="/feed.xml" />
|
<link rel="alternate" type="application/rss+xml" title="{{ title }}" href="/feed.xml" />
|
||||||
{% endif %} {% if js %}
|
{% endif %}
|
||||||
|
<!-- prettier-br -->
|
||||||
|
{% if js %}
|
||||||
<script src="/static/main.js" defer></script>
|
<script src="/static/main.js" defer></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -7,11 +7,26 @@
|
||||||
<meta name="author" content="{{ meta.author }}" />
|
<meta name="author" content="{{ meta.author }}" />
|
||||||
<meta name="keywords" content="{{ meta.tags|join(", ") }}" />
|
<meta name="keywords" content="{{ meta.tags|join(", ") }}" />
|
||||||
<meta name="description" content="{{ meta.title }}" />
|
<meta name="description" content="{{ meta.title }}" />
|
||||||
|
<!-- you know what I really love? platforms like discord
|
||||||
|
favoring twitter embeds over the open standard. to color
|
||||||
|
your embed or have large images, you have to do _this_. lmao -->
|
||||||
<meta property="og:title" content="{{ meta.title }}" />
|
<meta property="og:title" content="{{ meta.title }}" />
|
||||||
|
<meta property="twitter:title" content="{{ meta.title }}" />
|
||||||
<meta property="og:description" content="{{ meta.description }}" />
|
<meta property="og:description" content="{{ meta.description }}" />
|
||||||
|
<meta property="twitter:description" content="{{ meta.description }}" />
|
||||||
{% match meta.icon %} {% when Some with (url) %}
|
{% match meta.icon %} {% when Some with (url) %}
|
||||||
<meta property="og:image" content="{{ url }}" />
|
<meta property="og:image" content="{{ url }}" />
|
||||||
<link rel="shortcut icon" href="{{ url }}" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:image:src" content="{{ url }}" />
|
||||||
|
{% match meta.icon_alt %} {% when Some with (alt) %}
|
||||||
|
<meta property="og:image:alt" content="{{ alt }}" />
|
||||||
|
<meta property="twitter:image:alt" content="{{ alt }}" />
|
||||||
|
{% when None %} {% endmatch %}
|
||||||
|
<!-- prettier-br -->
|
||||||
|
{% when None %} {% endmatch %}
|
||||||
|
<!-- prettier is annoying -->
|
||||||
|
{% match color %} {% when Some with (color) %}
|
||||||
|
<meta name="theme-color" content="{{ color }}" />
|
||||||
{% when None %} {% endmatch %}
|
{% when None %} {% endmatch %}
|
||||||
<title>{{ meta.title }}</title>
|
<title>{{ meta.title }}</title>
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
|
|
Loading…
Reference in a new issue