aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--articles/rot.md (renamed from articles/rot/rot.md)0
-rw-r--r--src/error.rs1
-rw-r--r--src/main.rs32
-rw-r--r--src/posts/mod.rs73
-rw-r--r--static/style.css63
-rw-r--r--templates/base.html.tera4
-rw-r--r--templates/blog.html.tera22
-rw-r--r--templates/blogpost-panel.html.tera8
-rw-r--r--templates/blogpost.html.tera7
-rw-r--r--templates/filtertags.html.tera7
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>