show top tags on index page
This commit is contained in:
@ -10,6 +10,7 @@ mod pages;
|
||||
mod post_list;
|
||||
mod post_parser;
|
||||
mod router;
|
||||
mod tag_list;
|
||||
// mod template;
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -1,8 +1,12 @@
|
||||
use askama::Template;
|
||||
use axum::http::StatusCode;
|
||||
|
||||
use crate::components::{
|
||||
site_footer::{render_site_footer, SiteFooter},
|
||||
site_header::HeaderProps,
|
||||
use crate::{
|
||||
components::{
|
||||
site_footer::{render_site_footer, SiteFooter},
|
||||
site_header::HeaderProps,
|
||||
},
|
||||
tag_list::get_popular_blog_tags,
|
||||
};
|
||||
|
||||
#[derive(Template)]
|
||||
@ -10,12 +14,24 @@ use crate::components::{
|
||||
pub struct IndexTemplate {
|
||||
site_footer: SiteFooter,
|
||||
header_props: HeaderProps,
|
||||
blog_tags: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn render_index() -> IndexTemplate {
|
||||
let site_footer = render_site_footer().await;
|
||||
IndexTemplate {
|
||||
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
||||
let site_footer = tokio::spawn(render_site_footer());
|
||||
let blog_tags = tokio::spawn(get_popular_blog_tags());
|
||||
|
||||
let blog_tags = blog_tags
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)??;
|
||||
|
||||
let site_footer = site_footer
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(IndexTemplate {
|
||||
site_footer,
|
||||
header_props: HeaderProps::default(),
|
||||
}
|
||||
blog_tags,
|
||||
})
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTempl
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// TODO if we have a tag we want to go back to all posts, otherwise we don't
|
||||
let header_props = match tag {
|
||||
Some(_) => HeaderProps::with_back_link(Link {
|
||||
href: "/blog".to_string(),
|
||||
@ -68,6 +67,3 @@ pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTempl
|
||||
header_props,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Do we want pagination or not? Ask designer/ We don't want itt
|
||||
// TODO when tags are true render different "see all post" message
|
||||
|
34
axum_server/src/tag_list.rs
Normal file
34
axum_server/src/tag_list.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::{pages::post::PostMetadata, post_list::get_post_list};
|
||||
use axum::http::StatusCode;
|
||||
use std::collections::HashMap;
|
||||
use tracing::debug;
|
||||
|
||||
pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
|
||||
const TAGS_LENGTH: usize = 7;
|
||||
|
||||
let post_list = get_post_list::<PostMetadata>().await?;
|
||||
let tags_sum = post_list
|
||||
.into_iter()
|
||||
.flat_map(|post| post.metadata.tags)
|
||||
.fold(HashMap::new(), |mut acc, tag| {
|
||||
*acc.entry(tag).or_insert(0) += 1;
|
||||
acc
|
||||
});
|
||||
|
||||
let mut sorted_tags_by_count: Vec<_> = tags_sum.into_iter().collect();
|
||||
sorted_tags_by_count.sort_by_key(|&(_, count)| std::cmp::Reverse(count));
|
||||
|
||||
// Log the counts
|
||||
for (tag, count) in &sorted_tags_by_count {
|
||||
debug!("Tag: {}, Count: {}", tag, count);
|
||||
}
|
||||
|
||||
let popular_blog_tags = sorted_tags_by_count
|
||||
.into_iter()
|
||||
.map(|tag_count| tag_count.0)
|
||||
.filter(|tag| tag != "dev")
|
||||
.take(TAGS_LENGTH)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Ok(popular_blog_tags)
|
||||
}
|
@ -2,6 +2,14 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
a {
|
||||
@apply text-pink-600 underline underline-offset-2;
|
||||
|
||||
&:hover {
|
||||
@apply transition text-blue-400;
|
||||
}
|
||||
}
|
||||
|
||||
.article-body {
|
||||
h1 {
|
||||
@apply px-4 text-2xl text-blue-900 my-2;
|
||||
|
@ -562,16 +562,50 @@ video {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.m-5 {
|
||||
margin: 1.25rem;
|
||||
}
|
||||
|
||||
.mx-0 {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mx-0\.5 {
|
||||
margin-left: 0.125rem;
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
|
||||
.mx-2 {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mx-4 {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mx-5 {
|
||||
margin-left: 1.25rem;
|
||||
margin-right: 1.25rem;
|
||||
}
|
||||
|
||||
.mx-6 {
|
||||
margin-left: 1.5rem;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.my-0 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.my-0\.5 {
|
||||
margin-top: 0.125rem;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.my-12 {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
@ -587,16 +621,6 @@ video {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mx-2 {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.my-2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
@ -605,6 +629,10 @@ video {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
@ -613,6 +641,10 @@ video {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
@ -707,6 +739,11 @@ video {
|
||||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-blue-950 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(23 37 84 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
|
||||
@ -726,6 +763,14 @@ video {
|
||||
fill: #172554;
|
||||
}
|
||||
|
||||
.p-0 {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.p-0\.5 {
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
@ -740,16 +785,6 @@ video {
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.py-3 {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-4 {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.py-5 {
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
@ -805,6 +840,10 @@ video {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
@ -843,6 +882,11 @@ video {
|
||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-pink-950 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(80 7 36 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.drop-shadow-md {
|
||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
@ -858,6 +902,24 @@ video {
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
a {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(219 39 119 / var(--tw-text-opacity));
|
||||
text-decoration-line: underline;
|
||||
text-underline-offset: 2px;
|
||||
&:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(96 165 250 / var(--tw-text-opacity));
|
||||
}
|
||||
&:hover {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
}
|
||||
|
||||
.article-body {
|
||||
h1 {
|
||||
margin-top: 0.5rem;
|
||||
@ -959,11 +1021,6 @@ video {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.hover\:bg-blue-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(191 219 254 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-pink-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(251 207 232 / var(--tw-bg-opacity));
|
||||
|
@ -10,7 +10,7 @@
|
||||
<header>
|
||||
<h3 class="text-lg font-medium mb-1">{{heading}}</h3>
|
||||
</header>
|
||||
<p class="text-sm leading-5 text-gray-800">{{description}}</p>
|
||||
<p class="text-sm leading-5 text-gray-800">{{description|safe}}</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
@ -22,20 +22,38 @@
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<h2 class="text-blue-950 font-semibold text-2xl">About me</h2>
|
||||
<h2 class="text-blue-950 font-semibold text-2xl m-5">About me</h2>
|
||||
|
||||
<p>
|
||||
<p class="mx-5">
|
||||
Welcome to my personal website. My name is
|
||||
<strong>Michal Vanko</strong>
|
||||
and I'm a
|
||||
<em> <a href="https://en.wikipedia.org/wiki/Programmer">programmer</a> </em>
|
||||
. I am developing software for more than half of my life and <strong>I love it!</strong> Sometimes I stream working on my side projects and building a <a href="TODO DISCORD">community of like minded people</a>. Here you can find blogs of my thoughts and journeys, as well as links to my socials where you can see other content.</p>
|
||||
. I am developing software for more than half of my life and <strong>I love it!</strong> Sometimes I stream working on my side projects and building a <a href="https://discord.gg/2cGg7kwZEh">community of like minded people</a>. Here you can find blogs of my thoughts and journeys, as well as links to my socials where you can see other content.</p>
|
||||
|
||||
<section class="talent-cards">
|
||||
<section id="talent-cards">
|
||||
{% call tc::talent_card("code", "Web development", "Extensive expertise in creating performant, live web applications and websites") %}
|
||||
{% call tc::talent_card("gamepad", "Game development", "Extensive expertise in creating performant, live web applications and websites") %}
|
||||
{% call tc::talent_card("person-chalkboard", "Mentoring & Consulting", "I offer consulting sessions to assist you in developing <strong>higher-quality software</strong> and share insights from crafting robust, professional web applications. <a href=\"TODO callendly\">Schedule a session with me</a> and elevate your projects together.") %}
|
||||
</section
|
||||
|
||||
<section id="blog">
|
||||
<h2 class="text-blue-950 font-semibold text-2xl m-5">Blog</h2>
|
||||
<section id="tags">
|
||||
<ul class="mx-5">
|
||||
{% for tag in blog_tags %}
|
||||
<li class="inline-block mx-0.5 p-0.5">
|
||||
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<hr class="border-blue-950 m-5">
|
||||
|
||||
<ul class="mx-5">
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="twitch-stream-promo">
|
||||
<h2>Follow my twitch stream</h2>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<header class="min-h-full bg-blue-50">
|
||||
<header class="min-h-full bg-blue-50 mb-5">
|
||||
<nav class="flex">
|
||||
{% match header_props.back_link %}
|
||||
{% when Some with (link) %}
|
||||
@ -16,4 +16,5 @@
|
||||
</a>
|
||||
</aside>
|
||||
</nav>
|
||||
<hr class="border-blue-950 mx-5">
|
||||
</header>
|
||||
|
Reference in New Issue
Block a user