diff options
Diffstat (limited to '')
-rw-r--r-- | articles/rot.md (renamed from articles/rot/rot.md) | 0 | ||||
-rw-r--r-- | src/error.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 32 | ||||
-rw-r--r-- | src/posts/mod.rs | 73 | ||||
-rw-r--r-- | static/style.css | 63 | ||||
-rw-r--r-- | templates/base.html.tera | 4 | ||||
-rw-r--r-- | templates/blog.html.tera | 22 | ||||
-rw-r--r-- | templates/blogpost-panel.html.tera | 8 | ||||
-rw-r--r-- | templates/blogpost.html.tera | 7 | ||||
-rw-r--r-- | templates/filtertags.html.tera | 7 |
10 files changed, 202 insertions, 15 deletions
diff --git a/articles/rot/rot.md b/articles/rot.md index 5af2a8d..5af2a8d 100644 --- a/articles/rot/rot.md +++ b/articles/rot.md diff --git a/src/error.rs b/src/error.rs index 81cadd1..eb6f6e6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ pub enum BlossomError { Chrono(Status, #[response(ignore)] chrono::ParseError), Io(Status, #[response(ignore)] std::io::Error), Deserialization(Status, #[response(ignore)] toml::de::Error), + NotFound(Status), NoMetadata(Status), Unimplemented(Status), } diff --git a/src/main.rs b/src/main.rs index dd2bd34..c08c2e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,36 @@ async fn home(clients: &State<Clients>) -> Template { ) } +#[get("/blog")] +async fn blog() -> Template { + let mut blogposts = posts::get_blogposts().await.unwrap_or_default(); + let tags = posts::get_tags(&blogposts); + for blogpost in &mut blogposts { + blogpost.render().await; + } + let reverse = "reverse".to_owned(); + Template::render( + "blog", + context! { + reverse, + blogposts, + tags, + }, + ) +} + +#[get("/blog/<blogpost>")] +async fn blogpost(blogpost: &str) -> Result<Template> { + let mut blogpost = posts::get_blogpost(blogpost).await?; + blogpost.render().await?; + Ok(Template::render( + "blogpost", + context! { + blogpost, + }, + )) +} + #[get("/contact")] async fn contact() -> Template { Template::render("contact", context! {}) @@ -90,7 +120,7 @@ async fn main() -> std::result::Result<(), rocket::Error> { .attach(Template::custom(|engines| { engines.tera.autoescape_on(vec![]); })) - .mount("/", routes![home, contact, plants]) + .mount("/", routes![home, contact, blog, blogpost, plants]) .register("/", catchers![catcher]) .mount("/", FileServer::from(relative!("static"))) .launch() diff --git a/src/posts/mod.rs b/src/posts/mod.rs index b188ee0..be1e1a9 100644 --- a/src/posts/mod.rs +++ b/src/posts/mod.rs @@ -1,6 +1,8 @@ +use std::collections::HashSet; + use async_trait::async_trait; use chrono::{DateTime, Utc}; -use markdown::{mdast::Node, Constructs, ParseOptions}; +use markdown::{mdast::Node, Constructs, Options, ParseOptions}; use rocket::http::Status; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use tokio::fs; @@ -28,15 +30,23 @@ pub struct Post<D: Content> { updated_at: Option<DateTime<Utc>>, tags: Vec<String>, post_type: PostType, + render: Option<String>, data: D, } -impl<D: Content> Serialize for Post<D> { +impl<D: Content + Serialize> Post<D> { + pub async fn render(&mut self) -> Result<()> { + self.render = Some(self.data.render().await?); + Ok(()) + } +} + +impl<D: Content + Serialize> Serialize for Post<D> { fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> where S: serde::Serializer, { - let mut state = serializer.serialize_struct("Post", 5)?; + let mut state = serializer.serialize_struct("Post", 7)?; state.serialize_field("subject", &self.subject)?; state.serialize_field("created_at", &self.created_at.to_string())?; state.serialize_field( @@ -45,6 +55,8 @@ impl<D: Content> Serialize for Post<D> { )?; state.serialize_field("tags", &self.tags)?; state.serialize_field("post_type", &self.post_type)?; + state.serialize_field("render", &self.render)?; + state.serialize_field("data", &self.data)?; state.end() } } @@ -52,29 +64,55 @@ impl<D: Content> Serialize for Post<D> { pub async fn get_blogposts() -> Result<Vec<Post<Article>>> { let mut blogposts: Vec<Post<Article>> = Vec::new(); let mut articles_dir = fs::read_dir("./articles").await?; - while let Some(dir) = articles_dir.next_entry().await? { - let mut file_path = dir.path(); - file_path.push(dir.file_name()); - let file_path = file_path.with_extension("md"); - println!("{:?}", file_path); + while let Some(file) = articles_dir.next_entry().await? { + let name = file.file_name(); + let name = name.to_str().unwrap_or_default()[..name.len() - 3].to_owned(); let blogpost: Article = Article { - path: file_path.to_str().unwrap_or_default().to_owned(), + path: file.path().to_str().unwrap_or_default().to_owned(), + name, }; let blogpost = Post::try_from(blogpost).await.unwrap(); - println!("{:?}", blogpost); blogposts.push(blogpost); } Ok(blogposts) } +pub async fn get_blogpost(post_name: &str) -> Result<Post<Article>> { + let mut articles_dir = fs::read_dir("./articles").await?; + while let Some(file) = articles_dir.next_entry().await? { + let name = file.file_name(); + let name = &name.to_str().unwrap_or_default()[..name.len() - 3]; + if name == post_name { + let blogpost = Article { + path: file.path().to_str().unwrap_or_default().to_owned(), + name: name.to_owned(), + }; + let blogpost = Post::try_from(blogpost).await.unwrap(); + return Ok(blogpost); + } + } + Err(BlossomError::NotFound(Status::new(404))) +} + +pub fn get_tags<D: Content>(posts: &Vec<Post<D>>) -> HashSet<String> { + posts.into_iter().fold(HashSet::new(), |mut acc, post| { + let tags = &post.tags; + for tag in tags { + acc.insert(tag.to_owned()); + } + acc + }) +} + #[async_trait] pub trait Content { async fn render(&self) -> Result<String>; } -#[derive(Debug)] +#[derive(Serialize, Debug)] pub struct Article { path: String, + name: String, } impl Article { @@ -116,7 +154,17 @@ impl Content for Article { let mut file = fs::File::open(&self.path).await?; let mut buf = String::new(); file.read_to_string(&mut buf).await?; - Ok(markdown::to_html(&buf)) + let options = Options { + parse: ParseOptions { + constructs: Constructs { + frontmatter: true, + ..Constructs::default() + }, + ..ParseOptions::default() + }, + ..Options::default() + }; + Ok(markdown::to_html_with_options(&buf, &options).unwrap()) } } @@ -144,6 +192,7 @@ impl Post<Article> { updated_at, tags: metadata.tags, post_type: PostType::Article, + render: None, data: article, }) } diff --git a/static/style.css b/static/style.css index 5aae4df..3542e62 100644 --- a/static/style.css +++ b/static/style.css @@ -124,6 +124,12 @@ main { align-items: flex-start; } +.reverse { + flex-direction: row-reverse; + flex-wrap: wrap-reverse; + align-items: flex-end; +} + .main-content { flex: 12 1 0; min-width: 50%; @@ -316,6 +322,63 @@ iframe { display: none; } +/* blog */ + +.blogpost { + font-family: Sligoil; +} + +.tags { + display: flexbox; + flex-wrap: wrap; + gap: 0.5em; +} + +.tags a { + margin: 0; +} + +.blogpost .tag { + font-family: 'Go Mono'; + margin: 0; +} + +.blogpost .title { + font-family: 'kaeru kaeru'; + font-size: 4em; + margin: 0.5em 0 0; +} + +.blogpost .created-at { + font-family: 'Steps Mono'; + font-size: 1em; + margin: 0 0 1em; +} + +.blogpost .created-at a, +.blogpost-content a { + padding: 0; + margin: 0 0.5em; + background-color: transparent; + color: #b52f6a; + text-decoration: underline; +} + +.blogpost-content ul { + list-style-type: initial; +} + +/* filter-tags */ +#filter-tags { + font-family: 'Go Mono'; + z-index: -1; + background-color: #afd7ff; +} + +#filter-tags h2 { + font-family: 'Go Mono'; +} + /* branches */ .branch { diff --git a/templates/base.html.tera b/templates/base.html.tera index 98ae3f3..3874df3 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -37,7 +37,7 @@ <ul id="nav"> <li><a class="{% block nav_home %}{% endblock %}" href="/">home</a></li> <li><a class="{% block nav_contact %}{% endblock %}" style="font-family: 'Compagnon Roman';" href="/contact">kontakt</a></li> - <li><a class="{% block nav_girlblog %}{% endblock %}" style="font-family: Sligoil" href="/blog">girlblog</a></li> + <li><a class="{% block nav_blog %}{% endblock %}" style="font-family: Sligoil" href="/blog">girlblog</a></li> <li><a class="{% block nav_projects %}{% endblock %}" style="font-family: 'DeGerm LoCase';" href="/projects">projetos</a></li> <li><a class="{% block nav_sound %}{% endblock %}" style="font-family: 'kirieji'" href="/sound">音</a></li> <li><a class="{% block nav_listens %}{% endblock %}" style="font-family: 'Almendra Display'; font-weight: 900;" href="https://listenbrainz.org/celblossom">écoute</a></li> @@ -56,7 +56,7 @@ </ul> </nav> - <main> + <main class="{% if reverse %}{{ reverse }}{% endif %}"> <div class="main-content"> {% block content %} diff --git a/templates/blog.html.tera b/templates/blog.html.tera new file mode 100644 index 0000000..20ec08e --- /dev/null +++ b/templates/blog.html.tera @@ -0,0 +1,22 @@ +{% extends "base" %} + +{% block nav_blog %}active{% endblock %} + +{% block content %} + +{% for blogpost in blogposts %} +{% include "blogpost-panel" %} +{% endfor %} + +{% endblock content %} + +{% block aside %} + +<aside> + + {% include "latestposts" %} + {% include "filtertags" %} + +</aside> + +{% endblock aside %} diff --git a/templates/blogpost-panel.html.tera b/templates/blogpost-panel.html.tera new file mode 100644 index 0000000..b722035 --- /dev/null +++ b/templates/blogpost-panel.html.tera @@ -0,0 +1,8 @@ +<div class="panel content blogpost"> + <h1 class="title">{{ blogpost.subject }}</h1> + <h2 class="created-at">{{ blogpost.created_at }}<a href="/blog/{{ blogpost.data.name }}">permalink</a></h2> + <div class="tags">{% for tag in blogpost.tags %}<a class="tag" href="/tag/{{ tag }}">{{ tag }}</a>{% endfor %}</div> + <div class="blogpost-content"> + {{ blogpost.render }} + </div> +</div> diff --git a/templates/blogpost.html.tera b/templates/blogpost.html.tera new file mode 100644 index 0000000..b73a24b --- /dev/null +++ b/templates/blogpost.html.tera @@ -0,0 +1,7 @@ +{% extends "base" %} + +{% block content %} + +{% include "blogpost-panel" %} + +{% endblock content %} diff --git a/templates/filtertags.html.tera b/templates/filtertags.html.tera new file mode 100644 index 0000000..da001e8 --- /dev/null +++ b/templates/filtertags.html.tera @@ -0,0 +1,7 @@ +<div class="panel" id="filter-tags"> +<h2>filter tag</h2> +<div class="tags"> + {% for tag in tags %}<a href="/blog/tag/{{ tag }}">{{ tag }}</a>{% endfor %} +</div> +<br> +</div> |