Responsive design for index and blog listing page

This commit is contained in:
Michal Vanko 2024-09-12 17:03:11 +02:00
parent 255536c681
commit cb61962812
15 changed files with 214 additions and 144 deletions

View File

@ -7,7 +7,7 @@ segments:
- cookbook
published: true
date: 2019-08-09T17:24:13.481Z
thumbnail: /images/uploads/screenshot.gif
#thumbnail: /images/uploads/screenshot.gif
tags:
- dev
---

View File

@ -8,10 +8,10 @@ pub fn pretty_date(date_time: &DateTime<Utc>) -> ::askama::Result<String> {
}
// This filter does not have extra arguments
pub fn description_filter(body: &String) -> ::askama::Result<String> {
pub fn description_filter(body: &str) -> ::askama::Result<String> {
let description = body
.lines()
.filter(|line| line.starts_with("<p"))
.filter(|line| line.starts_with("<p>"))
.take(2)
.collect::<Vec<&str>>()
.join("\n");

View File

@ -49,5 +49,7 @@ async fn main() {
}
// TODO responsive design
// - contact
// TODO Fix header menu link on blog
// TODO Colors
// TODO go live pipeline

View File

@ -1,20 +1,27 @@
use askama::Template;
use axum::{extract::Path, http::StatusCode};
use tokio::try_join;
use crate::{
blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
blog_posts::{
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
tag_list::get_popular_blog_tags,
},
components::site_header::{HeaderProps, Link},
filters,
post_utils::{post_listing::get_post_list, post_parser::ParseResult},
projects::{featured_projects::get_featured_projects, project_model::ProjectMetadata},
};
#[derive(Template)]
#[template(path = "post_list.html")]
#[template(path = "blog_post_list.html")]
pub struct PostListTemplate {
pub title: String,
pub posts: Vec<ParseResult<BlogPostMetadata>>,
pub tag: Option<String>,
pub header_props: HeaderProps,
pub blog_tags: Vec<String>,
pub featured_projects: Vec<ParseResult<ProjectMetadata>>,
}
pub async fn render_blog_post_list(
@ -23,7 +30,12 @@ pub async fn render_blog_post_list(
// I will forget what happens here in a week. But essentially it's pattern matching and shadowing
let tag = tag.map(|Path(tag)| tag);
let mut post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
let (blog_tags, featured_projects, mut post_list) = try_join!(
get_popular_blog_tags(),
get_featured_projects(),
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
)?;
post_list.sort_by_key(|post| post.metadata.date);
post_list.retain(|post| post.metadata.published);
post_list.reverse();
@ -56,5 +68,7 @@ pub async fn render_blog_post_list(
posts,
tag,
header_props,
blog_tags,
featured_projects,
})
}

View File

@ -27,7 +27,7 @@ pub fn generate_picture_markup(
let dev_only_img_path =
Path::new("../static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path));
let orig_img_dimensions = image_dimensions(&dev_only_img_path).unwrap();
let orig_img_dimensions = image_dimensions(&dev_only_img_path)?;
let resolutions = get_resolutions(orig_img_dimensions, width, height);
let path_to_generated_arc = Arc::new(path_to_generated);

View File

@ -163,9 +163,6 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String {
let syntax_reference = syntax_set
.find_syntax_by_token(lang)
.unwrap_or(syntax_set.find_syntax_plain_text());
syntax_set.syntaxes().iter().for_each(|sr| {
debug!("{}", sr.name);
});
let highlighted =
highlighted_html_for_string(&text, &syntax_set, syntax_reference, theme)
.unwrap();

View File

@ -633,11 +633,6 @@ video {
margin-bottom: 1.5rem;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
}
.mb-1 {
margin-bottom: 0.25rem;
}
@ -750,6 +745,14 @@ video {
grid-template-columns: max-content 1fr;
}
.grid-rows-\[max-content_max-content_max-content\] {
grid-template-rows: max-content max-content max-content;
}
.grid-rows-\[max-content_1fr_max-content\] {
grid-template-rows: max-content 1fr max-content;
}
.flex-row {
flex-direction: row;
}
@ -786,24 +789,6 @@ video {
gap: 1rem;
}
.gap-8 {
gap: 2rem;
}
.gap-x-64 {
-moz-column-gap: 16rem;
column-gap: 16rem;
}
.gap-y-8 {
row-gap: 2rem;
}
.gap-x-32 {
-moz-column-gap: 8rem;
column-gap: 8rem;
}
.overflow-hidden {
overflow: hidden;
}
@ -915,6 +900,10 @@ video {
text-align: right;
}
.text-justify {
text-align: justify;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@ -1492,6 +1481,11 @@ a {
}
@media (min-width: 768px) {
.md\:my-8 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.md\:grid {
display: grid;
}
@ -1516,6 +1510,15 @@ a {
justify-content: stretch;
}
.md\:gap-8 {
gap: 2rem;
}
.md\:gap-x-8 {
-moz-column-gap: 2rem;
column-gap: 2rem;
}
.md\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@ -1545,6 +1548,16 @@ a {
font-size: 1.25rem;
line-height: 1.75rem;
}
.md\:text-5xl {
font-size: 3rem;
line-height: 1;
}
.md\:text-6xl {
font-size: 3.75rem;
line-height: 1;
}
}
@media (min-width: 1024px) {
@ -1552,6 +1565,10 @@ a {
grid-column: span 2 / span 2;
}
.lg\:row-span-2 {
grid-row: span 2 / span 2;
}
.lg\:row-start-2 {
grid-row-start: 2;
}
@ -1560,6 +1577,10 @@ a {
margin-top: 5rem;
}
.lg\:block {
display: block;
}
.lg\:grid {
display: grid;
}
@ -1572,6 +1593,18 @@ a {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.lg\:grid-cols-\[1fr_2fr\] {
grid-template-columns: 1fr 2fr;
}
.lg\:grid-cols-\[2fr_1fr\] {
grid-template-columns: 2fr 1fr;
}
.lg\:grid-rows-\[min-content_1fr\] {
grid-template-rows: min-content 1fr;
}
.lg\:gap-x-32 {
-moz-column-gap: 8rem;
column-gap: 8rem;
@ -1585,6 +1618,11 @@ a {
font-size: 3.75rem;
line-height: 1;
}
.lg\:text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
}
@media (min-width: 1280px) {

View File

@ -0,0 +1,56 @@
{%- import "components/social_card.html" as sc -%}
{% extends "base.html" %}
{% block content %}
<section id="blog-container" class="lg:grid lg:grid-cols-[2fr_1fr] lg:grid-rows-[min-content_1fr] lg:gap-x-32 max-w-maxindex mx-auto">
<section id="blog-list" class="lg:row-span-2">
{% if posts.len() == 0 %}
<p class="no-posts">You've found void in the space.</p>
{% else %}
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
{% if let Some(t) = tag %}
<em>{{t}}</em>
{% else %}
Blog posts
{% endif %}
</h1>
<section id="blog-tags">
<ul class="mx-5">
{% for tag in blog_tags %}
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
</li>
{% endfor %}
</ul>
</section>
<hr class="border-blue-950 m-5 md:my-8">
<ul class="mx-5">
{% for post in posts %}
<li>
{% include "components/blog_post_preview.html" %}
<hr class="border-blue-950 my-5 md:my-8">
</li>
{% endfor %}
</ul>
{% endif %}
</section> <!-- /#blog-list -->
<section id="socials" class="hidden lg:block">
{% include "sections/social.html" %}
</section> <!-- /#socials -->
<section id="showcase" class="hidden lg:block">
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Showcase</h2>
<ul class="">
{% for project in featured_projects %}
<li class="my-2">
{% include "components/project_preview_card.html" %}
</li>
{% endfor %}
</ul>
</section> <!-- /#showcase -->
</section> <!-- /#blog-container -->
{% endblock %}

View File

@ -1,17 +1,19 @@
<article class="grid grid-cols-[max-content_1fr] grid-flow-col gap-4">
<article class="grid grid-cols-[max-content_1fr] grid-rows-[max-content_1fr_max-content] grid-flow-col gap-4 md:gap-x-8">
<aside class="row-span-3">
{% match post.metadata.thumbnail %}
{% when Some with (orig_path) %}
{{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail", true).unwrap()|safe }}
{{ crate::picture_generator::picture_markup_generator::generate_picture_markup(orig_path, 180, 240, "Article thumbnail", true).unwrap_or("".to_string())|safe }}
{% when None %}
<div> TODO default obrazok </div>
{% endmatch %}
</aside>
<header>
<h3 class="text-lg font-bold mb-1 md:text-3xl">{{post.metadata.title}}</h3>
<h3 class="text-lg font-bold mb-1 md:text-3xl">
<a rel="prefetch" href="/blog/{{post.slug}}">{{post.metadata.title}}</a>
</h3>
</header>
<section class="text-base leading-5 text-gray-800 md:text-xl">{{post.body|description_filter|safe}}</section>
<footer class="text-sm">
<section class="text-base leading-5 text-gray-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section>
<footer class="text-sm md:text-base lg:text-lg">
<ul class="inline-block">
{% for tag in post.metadata.tags %}
<li class="inline-block">

View File

@ -10,7 +10,7 @@
{{project.metadata.title}}
{% endmatch %}
</h2>
<section class="description text-slate-800 my-2 md:text-xl">
<section class="description text-slate-800 my-2 md:text-xl text-justify">
{{project.metadata.description|safe}}
</section>
</header>

View File

@ -27,7 +27,7 @@
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">About me</h2>
<p class="mx-5 md:text-xl">
<p class="mx-5 md:text-xl text-justify">
Welcome to my personal website. My name is
<strong>Michal&nbsp;Vanko</strong>
and I'm a
@ -42,7 +42,7 @@
</section>
<section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2">
<h2 class="text-blue-950 font-semibold text-2xl md:text-4xl m-5">Blog</h2>
<h2 class="text-blue-950 font-semibold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2>
<section id="blog-tags">
<ul class="mx-5">
{% for tag in blog_tags %}
@ -58,80 +58,20 @@
{% for post in featured_blog_posts %}
<li>
{% include "components/blog_post_preview.html" %}
<hr class="border-blue-950 my-5">
<hr class="border-blue-950 my-5 md:my-8">
</li>
{% endfor %}
</ul>
</section>
<section id="socials">
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Socials</h2>
{% call sc::social_card_start("twitch", "I stream (almost) regularly on <em>twitch.tv</em>") %}
<!-- <script src= "https://player.twitch.tv/js/embed/v1.js"></script> -->
<!-- <div id="twitch-player" class="h-64 aspect-video rounded overflow-hidden"></div> -->
<!-- <script type="text/javascript"> -->
<!-- var options = { -->
<!-- width: "100%", -->
<!-- height: "100%", -->
<!-- channel: "michalvankodev", -->
<!-- parent: ["localhost"] -->
<!-- }; -->
<!-- var player = new Twitch.Player("twitch-player", options); -->
<!-- player.setVolume(0.5); -->
</script>
{% call sc::social_card_end() %}
{% call sc::social_card_start("tiktok", "Highlights can be found on <em>TikTok</em>") %}
<!-- STYLES needed to overwrite tiktok embed css -->
<!-- <blockquote -->
<!-- class="h-64 aspect-video overflow-hidden p-0 m-0 tiktok-embed bg-pink-200" -->
<!-- cite="https://www.tiktok.com/@michalvankodev" -->
<!-- data-unique-id="michalvankodev" -->
<!-- data-embed-from="embed_page" -->
<!-- data-embed-type="creator" -->
<!-- style="max-width:780px; min-width:288px; margin: 0; padding: 0; border-radius: 8px" -->
<!-- > -->
<!-- <section> -->
<!-- <a target="_blank" href="https://www.tiktok.com/@michalvankodev?refer=creator_embed">@michalvankodev</a> -->
<!-- </section> -->
<!-- </blockquote> -->
<!-- <script async src="https://www.tiktok.com/embed.js"></script> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("youtube", "Vlogs and highlights can be found on <em>YouTube</em>") %}
<!-- TODO create our own youtube widget which will populate this window on build -->
<!-- <iframe -->
<!-- class="h-64 aspect-video" -->
<!-- id="ytplayer" -->
<!-- type="text/html" -->
<!-- width="100%" -->
<!-- height="100%" -->
<!-- src="https://www.youtube.com/embed/?listType=playlist&list=PLjUl8tFKyR8rCsckLn93PAwQg6tf0cyBl&enablejsapi=1&color=white" -->
<!-- frameborder="0" -->
<!-- allowfullscreen -->
<!-- ></iframe> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("instagram", "Photos and stories shared on <em>Instagram</em>") %}
<!-- <blockquote class="instagram-media aspect-video h-64" data-instgrm-permalink="https://www.instagram.com/michalvankodev/" data-instgrm-version="12" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; height:256px; max-height:100%;"></blockquote><script async src="https://www.instagram.com/embed.js"></script> -->
{% call sc::social_card_end() %}
{% include "sections/social.html" %}
</section>
<hr class="border-blue-950 m-5 lg:hidden">
<section id="showcase" class="col-span-2">
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Showcase</h2>
<ul class="mx-5 md:grid md:grid-cols-2 md:justify-stretch md:items-stretch xl:grid-cols-3">
{% for project in featured_projects %}
<li class="my-2">
{% include "components/project_preview_card.html" %}
</li>
{% endfor %}
</ul>
{% include "sections/showcase.html" %}
</section>
</section> <!-- /.index-container -->

View File

@ -1,21 +0,0 @@
{% extends "base.html" %} {% block content %} {% if posts.len() == 0 %}
<p class="no-posts">You've found void in the space.</p>
{% else %}
<h1 class="mx-6 mt-3 text-4xl text-blue-950 font-extrabold">
{% if let Some(t) = tag %}
<em>{{t}}</em>
{% else %}
Blog posts
{% endif %}
</h1>
<ul>
{% for post in posts %}
<li class="my-12">
{% include "post_preview_card.html" %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View File

@ -1,19 +0,0 @@
<article>
<header class="px-4 mb-3">
<h2 class="text-3xl font-semibold text-blue-900">
<a rel="prefetch" href="/blog/{{post.slug}}">{{post.metadata.title}}</a>
</h2>
<aside class="flex justify-between">
{% let tags = post.metadata.tags.clone() %}
{% include "post_tag_list.html" %}
<section class="created-at m-1 text-right text-sm text-gray-600">
<span>Published on</span>
<time datetime="{{post.metadata.date}}"> {{post.metadata.date|pretty_date}} </time>
</section>
</aside>
</header>
<p class="px-5 text-gray-800"> TODO: article preview, maybe implement as a filter?,
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Ald </p>
<hr class="my-3 mx-4 h-0 border-blue-200 bg-blue-800 text-blue-800" />
</article>

View File

@ -0,0 +1,9 @@
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Showcase</h2>
<ul class="mx-5 md:grid md:grid-cols-2 md:justify-stretch md:items-stretch xl:grid-cols-3">
{% for project in featured_projects %}
<li class="my-2">
{% include "components/project_preview_card.html" %}
</li>
{% endfor %}
</ul>

View File

@ -0,0 +1,52 @@
<h2 class="text-blue-950 font-semibold text-2xl m-5 md:text-4xl">Socials</h2>
{% call sc::social_card_start("twitch", "I stream (almost) regularly on <em>twitch.tv</em>") %}
<!-- <script src= "https://player.twitch.tv/js/embed/v1.js"></script> -->
<!-- <div id="twitch-player" class="h-64 aspect-video rounded overflow-hidden"></div> -->
<!-- <script type="text/javascript"> -->
<!-- var options = { -->
<!-- width: "100%", -->
<!-- height: "100%", -->
<!-- channel: "michalvankodev", -->
<!-- parent: ["localhost"] -->
<!-- }; -->
<!-- var player = new Twitch.Player("twitch-player", options); -->
<!-- player.setVolume(0.5); -->
</script>
{% call sc::social_card_end() %}
{% call sc::social_card_start("tiktok", "Highlights can be found on <em>TikTok</em>") %}
<!-- STYLES needed to overwrite tiktok embed css -->
<!-- <blockquote -->
<!-- class="h-64 aspect-video overflow-hidden p-0 m-0 tiktok-embed bg-pink-200" -->
<!-- cite="https://www.tiktok.com/@michalvankodev" -->
<!-- data-unique-id="michalvankodev" -->
<!-- data-embed-from="embed_page" -->
<!-- data-embed-type="creator" -->
<!-- style="max-width:780px; min-width:288px; margin: 0; padding: 0; border-radius: 8px" -->
<!-- > -->
<!-- <section> -->
<!-- <a target="_blank" href="https://www.tiktok.com/@michalvankodev?refer=creator_embed">@michalvankodev</a> -->
<!-- </section> -->
<!-- </blockquote> -->
<!-- <script async src="https://www.tiktok.com/embed.js"></script> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("youtube", "Vlogs and highlights can be found on <em>YouTube</em>") %}
<!-- TODO create our own youtube widget which will populate this window on build -->
<!-- <iframe -->
<!-- class="h-64 aspect-video" -->
<!-- id="ytplayer" -->
<!-- type="text/html" -->
<!-- width="100%" -->
<!-- height="100%" -->
<!-- src="https://www.youtube.com/embed/?listType=playlist&list=PLjUl8tFKyR8rCsckLn93PAwQg6tf0cyBl&enablejsapi=1&color=white" -->
<!-- frameborder="0" -->
<!-- allowfullscreen -->
<!-- ></iframe> -->
{% call sc::social_card_end() %}
{% call sc::social_card_start("instagram", "Photos and stories shared on <em>Instagram</em>") %}
<!-- <blockquote class="instagram-media aspect-video h-64" data-instgrm-permalink="https://www.instagram.com/michalvankodev/" data-instgrm-version="12" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; height:256px; max-height:100%;"></blockquote><script async src="https://www.instagram.com/embed.js"></script> -->
{% call sc::social_card_end() %}