implement blog tag filtering
This commit is contained in:
		
							parent
							
								
									139c26158b
								
							
						
					
					
						commit
						a135acf943
					
				
							
								
								
									
										46
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										46
									
								
								src/main.rs
								
								
								
								
							|  | @ -5,6 +5,7 @@ mod scrobbles; | ||||||
| mod skweets; | mod skweets; | ||||||
| 
 | 
 | ||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
|  | use std::collections::HashSet; | ||||||
| 
 | 
 | ||||||
| use rocket::fs::{relative, FileServer}; | use rocket::fs::{relative, FileServer}; | ||||||
| use rocket::http::Status; | use rocket::http::Status; | ||||||
|  | @ -47,24 +48,6 @@ 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>")] | #[get("/blog/<blogpost>")] | ||||||
| async fn blogpost(blogpost: &str) -> Result<Template> { | async fn blogpost(blogpost: &str) -> Result<Template> { | ||||||
|     let mut blogpost = posts::get_blogpost(blogpost).await?; |     let mut blogpost = posts::get_blogpost(blogpost).await?; | ||||||
|  | @ -77,6 +60,33 @@ async fn blogpost(blogpost: &str) -> Result<Template> { | ||||||
|     )) |     )) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[get("/blog?<filter>")] | ||||||
|  | async fn blog(filter: Vec<String>) -> Result<Template> { | ||||||
|  |     let mut blogposts = posts::get_blogposts().await?; | ||||||
|  |     let tags: Vec<String> = posts::get_tags(&blogposts) | ||||||
|  |         .into_iter() | ||||||
|  |         .map(|tag| tag.to_owned()) | ||||||
|  |         .collect(); | ||||||
|  |     let mut filter_hashset: HashSet<String> = HashSet::new(); | ||||||
|  |     if !filter.is_empty() { | ||||||
|  |         filter_hashset.extend(filter.into_iter()); | ||||||
|  |         blogposts = posts::filter_by_tags(blogposts, &filter_hashset); | ||||||
|  |     } | ||||||
|  |     for blogpost in &mut blogposts { | ||||||
|  |         blogpost.render().await?; | ||||||
|  |     } | ||||||
|  |     let reverse = true; | ||||||
|  |     Ok(Template::render( | ||||||
|  |         "blog", | ||||||
|  |         context! { | ||||||
|  |             reverse, | ||||||
|  |             tags, | ||||||
|  |             filter_hashset, | ||||||
|  |             blogposts, | ||||||
|  |         }, | ||||||
|  |     )) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[get("/contact")] | #[get("/contact")] | ||||||
| async fn contact() -> Template { | async fn contact() -> Template { | ||||||
|     Template::render("contact", context! {}) |     Template::render("contact", context! {}) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | mod article; | ||||||
|  | mod note; | ||||||
|  | 
 | ||||||
| use std::collections::HashSet; | use std::collections::HashSet; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
|  | @ -16,12 +19,6 @@ enum PostType { | ||||||
|     Note, |     Note, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| enum TextFormat { |  | ||||||
|     Markdown, |  | ||||||
|     Plaintext, |  | ||||||
|     Html, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct Post<D: Content> { | pub struct Post<D: Content> { | ||||||
|     // id: i64,
 |     // id: i64,
 | ||||||
|  | @ -34,7 +31,7 @@ pub struct Post<D: Content> { | ||||||
|     data: D, |     data: D, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<D: Content + Serialize> Post<D> { | impl<D: Content> Post<D> { | ||||||
|     pub async fn render(&mut self) -> Result<()> { |     pub async fn render(&mut self) -> Result<()> { | ||||||
|         self.render = Some(self.data.render().await?); |         self.render = Some(self.data.render().await?); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|  | @ -74,6 +71,8 @@ pub async fn get_blogposts() -> Result<Vec<Post<Article>>> { | ||||||
|         let blogpost = Post::try_from(blogpost).await.unwrap(); |         let blogpost = Post::try_from(blogpost).await.unwrap(); | ||||||
|         blogposts.push(blogpost); |         blogposts.push(blogpost); | ||||||
|     } |     } | ||||||
|  |     blogposts.sort_by_key(|post| post.created_at); | ||||||
|  |     blogposts.reverse(); | ||||||
|     Ok(blogposts) |     Ok(blogposts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -87,21 +86,45 @@ pub async fn get_blogpost(post_name: &str) -> Result<Post<Article>> { | ||||||
|                 path: file.path().to_str().unwrap_or_default().to_owned(), |                 path: file.path().to_str().unwrap_or_default().to_owned(), | ||||||
|                 name: name.to_owned(), |                 name: name.to_owned(), | ||||||
|             }; |             }; | ||||||
|             let blogpost = Post::try_from(blogpost).await.unwrap(); |             let blogpost = Post::try_from(blogpost).await?; | ||||||
|             return Ok(blogpost); |             return Ok(blogpost); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     Err(BlossomError::NotFound(Status::new(404))) |     Err(BlossomError::NotFound(Status::new(404))) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn get_tags<D: Content>(posts: &Vec<Post<D>>) -> HashSet<String> { | pub fn get_tags<D: Content>(posts: &Vec<Post<D>>) -> Vec<&String> { | ||||||
|     posts.into_iter().fold(HashSet::new(), |mut acc, post| { |     let mut tags = posts | ||||||
|  |         .into_iter() | ||||||
|  |         .fold(HashSet::new(), |mut acc, post| { | ||||||
|             let tags = &post.tags; |             let tags = &post.tags; | ||||||
|             for tag in tags { |             for tag in tags { | ||||||
|             acc.insert(tag.to_owned()); |                 acc.insert(tag); | ||||||
|             } |             } | ||||||
|             acc |             acc | ||||||
|         }) |         }) | ||||||
|  |         .into_iter() | ||||||
|  |         .collect::<Vec<_>>(); | ||||||
|  |     tags.sort(); | ||||||
|  |     tags | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn filter_by_tags<'p, D: Content>( | ||||||
|  |     posts: Vec<Post<D>>, | ||||||
|  |     filter_tags: &HashSet<String>, | ||||||
|  | ) -> Vec<Post<D>> { | ||||||
|  |     posts | ||||||
|  |         .into_iter() | ||||||
|  |         .filter(|post| { | ||||||
|  |             for tag in &post.tags { | ||||||
|  |                 match filter_tags.contains(tag) { | ||||||
|  |                     true => return true, | ||||||
|  |                     false => continue, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             false | ||||||
|  |         }) | ||||||
|  |         .collect() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
|  |  | ||||||
|  | @ -1 +1,7 @@ | ||||||
| struct note | enum TextFormat { | ||||||
|  |     Markdown, | ||||||
|  |     Plaintext, | ||||||
|  |     Html, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct Note {} | ||||||
|  |  | ||||||
|  | @ -329,9 +329,9 @@ iframe { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .tags { | .tags { | ||||||
|   display: flexbox; |   display: flex; | ||||||
|   flex-wrap: wrap; |   flex-wrap: wrap; | ||||||
|   gap: 0.5em; |   gap: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .tags a { | .tags a { | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ | ||||||
|         </ul> |         </ul> | ||||||
|       </nav> |       </nav> | ||||||
| 
 | 
 | ||||||
|       <main class="{% if reverse %}{{ reverse }}{% endif %}"> |       <main class="{% if reverse %}reverse{% endif %}"> | ||||||
|         <div class="main-content"> |         <div class="main-content"> | ||||||
| 
 | 
 | ||||||
|           {% block content %} |           {% block content %} | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <div class="panel content blogpost"> | <div class="panel content blogpost"> | ||||||
|   <h1 class="title">{{ blogpost.subject }}</h1> |   <h1 class="title">{{ blogpost.subject }}</h1> | ||||||
|   <h2 class="created-at">{{ blogpost.created_at }} <a href="/blog/{{ blogpost.data.name }}">permalink</a></h2> |   <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="tags">{% for tag in blogpost.tags %}<a class="tag {% if filter_hashset %}{% if tag in filter_hashset %}active{% endif %}{% endif %}" href="/blog?filter={{ tag }}">{{ tag }}</a>{% endfor %}</div> | ||||||
|   <div class="blogpost-content"> |   <div class="blogpost-content"> | ||||||
|   {{ blogpost.render }} |   {{ blogpost.render }} | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <div class="panel" id="filter-tags"> | <div class="panel" id="filter-tags"> | ||||||
| <h2>filter tag</h2> | <h2>filter by tags</h2> | ||||||
| <div class="tags"> | <div class="tags"> | ||||||
|   {% for tag in tags %}<a href="/blog/tag/{{ tag }}">{{ tag }}</a>{% endfor %} |   {% for tag in tags %}<a class="{% if tag in filter_hashset %}active{% endif %}" href="{% if tag in filter_hashset %}/blog{% else %}/blog?filter={{ tag }}{% endif %}">{{ tag }}</a>{% endfor %} | ||||||
| </div> | </div> | ||||||
| <br> | <br> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue