Compare commits
2 Commits
fec60900f5
...
13820f58eb
Author | SHA1 | Date | |
---|---|---|---|
13820f58eb | |||
f7eb6cc95d |
@ -3,7 +3,9 @@ use axum::extract::OriginalUri;
|
|||||||
use axum::{extract::Path, http::StatusCode};
|
use axum::{extract::Path, http::StatusCode};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::blog_posts::blog_post_model::BLOG_POST_PATH;
|
use crate::blog_posts::blog_post_model::{Segment, BLOG_POST_PATH};
|
||||||
|
use crate::post_utils::post_listing::get_post_list;
|
||||||
|
use crate::post_utils::post_parser::ParseResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
blog_posts::blog_post_model::BlogPostMetadata, components::site_header::Link, filters,
|
blog_posts::blog_post_model::BlogPostMetadata, components::site_header::Link, filters,
|
||||||
post_utils::post_parser::parse_post,
|
post_utils::post_parser::parse_post,
|
||||||
@ -18,10 +20,11 @@ pub struct BlogPostTemplate {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
pub date: DateTime<Utc>,
|
pub date: DateTime<Utc>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub segment: String,
|
pub segment: Segment,
|
||||||
pub header_props: HeaderProps,
|
pub header_props: HeaderProps,
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
pub thumbnail: Option<String>,
|
pub thumbnail: Option<String>,
|
||||||
|
pub recommended_posts: Vec<ParseResult<BlogPostMetadata>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_blog_post(
|
pub async fn render_blog_post(
|
||||||
@ -29,22 +32,27 @@ pub async fn render_blog_post(
|
|||||||
OriginalUri(original_uri): OriginalUri,
|
OriginalUri(original_uri): OriginalUri,
|
||||||
) -> Result<BlogPostTemplate, StatusCode> {
|
) -> Result<BlogPostTemplate, StatusCode> {
|
||||||
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
|
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
|
||||||
let parse_post = parse_post::<BlogPostMetadata>(&path);
|
let post = parse_post::<BlogPostMetadata>(&path).await?;
|
||||||
let parsed = parse_post.await?;
|
|
||||||
let segment = if original_uri.to_string().starts_with("/blog") {
|
let segment = if original_uri.to_string().starts_with("/blog") {
|
||||||
"blog"
|
Segment::Blog
|
||||||
} else if original_uri.to_string().starts_with("/broadcasts") {
|
} else if original_uri.to_string().starts_with("/broadcasts") {
|
||||||
"broadcasts"
|
Segment::Broadcasts
|
||||||
} else {
|
} else {
|
||||||
"blog"
|
Segment::Blog
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut recommended_posts = get_recommended_posts(&segment, &post.metadata.tags).await?;
|
||||||
|
recommended_posts.retain(|post| post.slug != post_id);
|
||||||
|
recommended_posts.sort_by_key(|post| post.slug.to_string());
|
||||||
|
recommended_posts.reverse();
|
||||||
|
let recommended_posts = recommended_posts.into_iter().take(2).collect::<Vec<_>>();
|
||||||
|
|
||||||
let header_props = match segment {
|
let header_props = match segment {
|
||||||
"blog" => HeaderProps::with_back_link(Link {
|
Segment::Blog => HeaderProps::with_back_link(Link {
|
||||||
href: "/blog".to_string(),
|
href: "/blog".to_string(),
|
||||||
label: "All posts".to_string(),
|
label: "All posts".to_string(),
|
||||||
}),
|
}),
|
||||||
"broadcasts" => HeaderProps::with_back_link(Link {
|
Segment::Broadcasts => HeaderProps::with_back_link(Link {
|
||||||
href: "/broadcasts".to_string(),
|
href: "/broadcasts".to_string(),
|
||||||
label: "All broadcasts".to_string(),
|
label: "All broadcasts".to_string(),
|
||||||
}),
|
}),
|
||||||
@ -52,13 +60,40 @@ pub async fn render_blog_post(
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(BlogPostTemplate {
|
Ok(BlogPostTemplate {
|
||||||
title: parsed.metadata.title,
|
title: post.metadata.title,
|
||||||
date: parsed.metadata.date,
|
date: post.metadata.date,
|
||||||
tags: parsed.metadata.tags,
|
tags: post.metadata.tags,
|
||||||
body: parsed.body,
|
body: post.body,
|
||||||
slug: parsed.slug,
|
slug: post.slug,
|
||||||
segment: segment.to_string(),
|
segment,
|
||||||
thumbnail: parsed.metadata.thumbnail,
|
thumbnail: post.metadata.thumbnail,
|
||||||
header_props,
|
header_props,
|
||||||
|
recommended_posts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_recommended_posts(
|
||||||
|
segment: &Segment,
|
||||||
|
tags: &[String],
|
||||||
|
) -> Result<Vec<ParseResult<BlogPostMetadata>>, StatusCode> {
|
||||||
|
let posts = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
|
||||||
|
|
||||||
|
let recommended_posts = posts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|post| {
|
||||||
|
let is_same_segment = post
|
||||||
|
.metadata
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.any(|post_segment| post_segment == segment);
|
||||||
|
let has_same_tags = post
|
||||||
|
.metadata
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.any(|post_tag| tags.contains(post_tag));
|
||||||
|
is_same_segment && has_same_tags
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(recommended_posts)
|
||||||
|
}
|
||||||
|
@ -756,6 +756,10 @@ video {
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-10 {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -804,6 +808,10 @@ video {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.max-h-\[236px\] {
|
.max-h-\[236px\] {
|
||||||
max-height: 236px;
|
max-height: 236px;
|
||||||
}
|
}
|
||||||
@ -832,6 +840,10 @@ video {
|
|||||||
width: 320px;
|
width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-0 {
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-\[24rem\] {
|
.max-w-\[24rem\] {
|
||||||
max-width: 24rem;
|
max-width: 24rem;
|
||||||
}
|
}
|
||||||
@ -962,6 +974,10 @@ video {
|
|||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-l {
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.border-blue-300 {
|
.border-blue-300 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(130 195 247 / var(--tw-border-opacity, 1));
|
border-color: rgb(130 195 247 / var(--tw-border-opacity, 1));
|
||||||
@ -982,6 +998,11 @@ video {
|
|||||||
border-color: rgb(203 213 225 / var(--tw-border-opacity, 1));
|
border-color: rgb(203 213 225 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-l-slate-300 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-left-color: rgb(203 213 225 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-blue-100 {
|
.bg-blue-100 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(225 239 253 / var(--tw-bg-opacity, 1));
|
background-color: rgb(225 239 253 / var(--tw-bg-opacity, 1));
|
||||||
@ -1993,6 +2014,10 @@ article a:visited {
|
|||||||
|
|
||||||
/* } */
|
/* } */
|
||||||
|
|
||||||
|
.first\:border-l-0:first-child {
|
||||||
|
border-left-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.visited\:text-blue-950:visited {
|
.visited\:text-blue-950:visited {
|
||||||
color: rgb(11 39 70 );
|
color: rgb(11 39 70 );
|
||||||
}
|
}
|
||||||
@ -2186,6 +2211,14 @@ article a:visited {
|
|||||||
margin-top: 5rem;
|
margin-top: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lg\:mt-8 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg\:mb-10 {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.lg\:block {
|
.lg\:block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -2228,6 +2261,11 @@ article a:visited {
|
|||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lg\:text-4xl {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
@media (min-width: 1280px) {
|
||||||
@ -2259,10 +2297,30 @@ article a:visited {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:grid {
|
.xl\:grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xl\:auto-cols-auto {
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xl\:auto-cols-fr {
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.xl\:grid-flow-col {
|
||||||
|
grid-auto-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:grid-cols-3 {
|
.xl\:grid-cols-3 {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@ -2271,15 +2329,36 @@ article a:visited {
|
|||||||
grid-template-columns: 1fr 2fr;
|
grid-template-columns: 1fr 2fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:justify-start {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:gap-8 {
|
.xl\:gap-8 {
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:gap-5 {
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xl\:gap-10 {
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:gap-x-32 {
|
.xl\:gap-x-32 {
|
||||||
-moz-column-gap: 8rem;
|
-moz-column-gap: 8rem;
|
||||||
column-gap: 8rem;
|
column-gap: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:border-l {
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xl\:border-slate-300 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(203 213 225 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:text-4xl {
|
.xl\:text-4xl {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
@ -2293,6 +2372,10 @@ article a:visited {
|
|||||||
.xl\:font-medium {
|
.xl\:font-medium {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:first\:border-l-0:first-child {
|
||||||
|
border-left-width: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
@ -33,7 +33,32 @@
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- TODO: Next recommendations for reading -->
|
<!-- TODO: Next recommendations for reading -->
|
||||||
<!-- TODO: Bact to all posts -->
|
<!-- TODO: Back to all posts -->
|
||||||
|
|
||||||
{# footer #}
|
<footer class="max-w-maxindex mx-auto">
|
||||||
|
|
||||||
|
{% if recommended_posts.len() > 0 %}
|
||||||
|
<section id="recommended-articles">
|
||||||
|
<hr class="border-slate-300 m-5 md:my-8">
|
||||||
|
|
||||||
|
<h2 class="m-5 text-2xl md:text-2xl lg:text-4xl lg:mt-8 text-blue-900 lg:mb-10 font-bold">Further reading</h2>
|
||||||
|
<ul class="mx-5 xl:flex xl:justify-start xl:gap-10">
|
||||||
|
{% for post in recommended_posts %}
|
||||||
|
<li class="flex-1">
|
||||||
|
{% include "components/blog_post_preview.html" %}
|
||||||
|
<hr class="border-slate-300 my-5 md:my-8 xl:hidden">
|
||||||
|
</li>
|
||||||
|
{% if !loop.last %}
|
||||||
|
<div class="h-auto w-0 border-l border-slate-300 hidden xl:block"></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<section class="text-center my-3 md:text-lg">
|
||||||
|
<a href="/blog">see all blog posts</a>
|
||||||
|
</section>
|
||||||
|
</footer>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
<!-- xl:border-l xl:border-slate-300 xl:first:border-l-0 xl: -->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user