This commit is contained in:
		| @@ -6,8 +6,7 @@ edition = "2021" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| askama = { version = "0.14", features = ["with-axum", "mime", "mime_guess"] } | ||||
| askama_axum = "0.5.0" | ||||
| askama = { version = "0.14" } | ||||
| axum = "0.8.0" | ||||
| chrono = { version = "0.4.31", features = ["serde"] } | ||||
| pulldown-cmark = { version = "0.13" } | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/feed.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/feed.rs
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | ||||
| use askama::Values; | ||||
| use axum::http::{header, StatusCode}; | ||||
| use axum::response::IntoResponse; | ||||
| use chrono::Utc; | ||||
| @@ -7,6 +8,14 @@ use crate::blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}; | ||||
| use crate::filters::{parse_markdown, truncate_md}; | ||||
| use crate::post_utils::post_listing::get_post_list; | ||||
|  | ||||
| struct EmptyValues; | ||||
|  | ||||
| impl Values for EmptyValues { | ||||
|     fn get_value<'a>(&'a self, _key: &str) -> Option<&'a dyn std::any::Any> { | ||||
|         return None; | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn render_rss_feed() -> Result<impl IntoResponse, StatusCode> { | ||||
|     let mut post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH) | ||||
|         .await | ||||
| @@ -27,14 +36,14 @@ pub async fn render_rss_feed() -> Result<impl IntoResponse, StatusCode> { | ||||
|                 .title(Some(post.metadata.title)) | ||||
|                 .link(Some(format!("https://michalvanko.dev/blog/{}", post.slug))) | ||||
|                 .description({ | ||||
|                     let truncated = | ||||
|                         truncate_md(&post.body, 2).unwrap_or("Can't parse post body".to_string()); | ||||
|                     let parsed_md = parse_markdown(&truncated) | ||||
|                     let truncated = truncate_md(&post.body, &EmptyValues, 2) | ||||
|                         .unwrap_or("Can't parse post body".to_string()); | ||||
|                     let parsed_md = parse_markdown(&truncated, &EmptyValues) | ||||
|                         .unwrap_or("Can't process truncated post body".to_string()); | ||||
|                     Some(parsed_md) | ||||
|                 }) | ||||
|                 .content({ | ||||
|                     let parsed_md = parse_markdown(&post.body) | ||||
|                     let parsed_md = parse_markdown(&post.body, &EmptyValues) | ||||
|                         .unwrap_or("Can't process full post body".to_string()); | ||||
|                     Some(parsed_md) | ||||
|                 }) | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| use core::fmt; | ||||
| use std::path::Path; | ||||
|  | ||||
| use image::image_dimensions; | ||||
| @@ -19,7 +20,10 @@ enum TextKind { | ||||
| } | ||||
|  | ||||
| // pub fn parse_markdown(markdown: &str) -> ::askama::Result<String> | ||||
| pub fn parse_markdown(markdown: &str) -> ::askama::Result<String> { | ||||
| pub fn parse_markdown<T: fmt::Display>( | ||||
|     markdown: T, | ||||
|     _: &dyn askama::Values, | ||||
| ) -> ::askama::Result<String> { | ||||
|     let mut options = Options::empty(); | ||||
|     options.insert(Options::ENABLE_TABLES); | ||||
|     options.insert(Options::ENABLE_FOOTNOTES); | ||||
| @@ -34,7 +38,8 @@ pub fn parse_markdown(markdown: &str) -> ::askama::Result<String> { | ||||
|     let theme = theme_set.themes.get("InspiredGitHub").unwrap(); | ||||
|     let mut heading_ended: Option<bool> = None; | ||||
|  | ||||
|     let parser = Parser::new_ext(markdown, options).map(|event| match event { | ||||
|     let mds = markdown.to_string(); | ||||
|     let parser = Parser::new_ext(&mds, options).map(|event| match event { | ||||
|         /* | ||||
|         Parsing images considers `alt` attribute as inner `Text` event | ||||
|         Therefore the `[alt]` is rendered in html as subtitle | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use chrono::{DateTime, Utc}; | ||||
|  | ||||
| // This filter does not have extra arguments | ||||
| pub fn pretty_date(date_time: &DateTime<Utc>) -> ::askama::Result<String> { | ||||
| pub fn pretty_date(date_time: &DateTime<Utc>, _: &dyn askama::Values) -> ::askama::Result<String> { | ||||
|     let formatted = format!("{}", date_time.format("%e %B %Y")); | ||||
|     Ok(formatted) | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| const FORBIDDEN_LINES: [&str; 5] = [" ", "#", "-", "!", "<"]; | ||||
|  | ||||
| pub fn truncate_md(body: &str, rows: usize) -> ::askama::Result<String> { | ||||
| pub fn truncate_md(body: &str, _: &dyn askama::Values, rows: usize) -> ::askama::Result<String> { | ||||
|     let description = body | ||||
|         .lines() | ||||
|         .filter(|line| { | ||||
|   | ||||
| @@ -1,9 +1,17 @@ | ||||
| use askama::Template; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
|  | ||||
| #[derive(Template)] | ||||
| #[template(path = "admin.html")] | ||||
| pub struct AdminPageTemplate {} | ||||
|  | ||||
| pub async fn render_admin() -> AdminPageTemplate { | ||||
|     AdminPageTemplate {} | ||||
| pub async fn render_admin() -> Result<impl IntoResponse, StatusCode> { | ||||
|     Ok(Html( | ||||
|         AdminPageTemplate {} | ||||
|             .render() | ||||
|             .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
|  | ||||
| #[derive(Template)] | ||||
| #[template(path = "assets/animated_logo.html")] | ||||
| pub struct AnimatedLogoTemplate {} | ||||
|  | ||||
| pub async fn render_animated_logo() -> Result<AnimatedLogoTemplate, StatusCode> { | ||||
|     Ok(AnimatedLogoTemplate {}) | ||||
| pub async fn render_animated_logo() -> Result<impl IntoResponse, StatusCode> { | ||||
|     Ok(Html(AnimatedLogoTemplate {}.render().unwrap())) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::{ | ||||
|     extract::{OriginalUri, Path}, | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
| use tokio::try_join; | ||||
| use tracing::debug; | ||||
| @@ -22,7 +24,7 @@ use super::post_list::PostListTemplate; | ||||
| pub async fn render_blog_post_list( | ||||
|     tag: Option<Path<String>>, | ||||
|     OriginalUri(original_uri): OriginalUri, | ||||
| ) -> Result<PostListTemplate, StatusCode> { | ||||
| ) -> Result<impl IntoResponse, StatusCode> { | ||||
|     // I will forget what happens here in a week. But essentially it's pattern matching and shadowing | ||||
|     let tag = tag.map(|Path(tag)| tag); | ||||
|  | ||||
| @@ -50,14 +52,18 @@ pub async fn render_blog_post_list( | ||||
|         ("Blog posts".to_string(), "Blog posts".to_string()) | ||||
|     }; | ||||
|  | ||||
|     Ok(PostListTemplate { | ||||
|         title, | ||||
|         og_title, | ||||
|         segment: Segment::Blog, | ||||
|         posts, | ||||
|         header_props, | ||||
|         tags: blog_tags, | ||||
|         featured_projects, | ||||
|         current_url: original_uri.to_string(), | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         PostListTemplate { | ||||
|             title, | ||||
|             og_title, | ||||
|             segment: Segment::Blog, | ||||
|             posts, | ||||
|             header_props, | ||||
|             tags: blog_tags, | ||||
|             featured_projects, | ||||
|             current_url: original_uri.to_string(), | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| use askama::Template; | ||||
| use axum::extract::OriginalUri; | ||||
| use axum::response::{Html, IntoResponse}; | ||||
| use axum::{extract::Path, http::StatusCode}; | ||||
| use chrono::{DateTime, Utc}; | ||||
|  | ||||
| @@ -30,7 +31,7 @@ pub struct BlogPostTemplate { | ||||
| pub async fn render_blog_post( | ||||
|     Path(post_id): Path<String>, | ||||
|     OriginalUri(original_uri): OriginalUri, | ||||
| ) -> Result<BlogPostTemplate, StatusCode> { | ||||
| ) -> Result<impl IntoResponse, StatusCode> { | ||||
|     let path = format!("{}/{}.md", BLOG_POST_PATH, post_id); | ||||
|     let post = parse_post::<BlogPostMetadata>(&path).await?; | ||||
|     let segment = if original_uri.to_string().starts_with("/blog") { | ||||
| @@ -59,17 +60,21 @@ pub async fn render_blog_post( | ||||
|         _ => HeaderProps::default(), | ||||
|     }; | ||||
|  | ||||
|     Ok(BlogPostTemplate { | ||||
|         title: post.metadata.title, | ||||
|         date: post.metadata.date, | ||||
|         tags: post.metadata.tags, | ||||
|         body: post.body, | ||||
|         slug: post.slug, | ||||
|         segment, | ||||
|         thumbnail: post.metadata.thumbnail, | ||||
|         header_props, | ||||
|         recommended_posts, | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         BlogPostTemplate { | ||||
|             title: post.metadata.title, | ||||
|             date: post.metadata.date, | ||||
|             tags: post.metadata.tags, | ||||
|             body: post.body, | ||||
|             slug: post.slug, | ||||
|             segment, | ||||
|             thumbnail: post.metadata.thumbnail, | ||||
|             header_props, | ||||
|             recommended_posts, | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|  | ||||
| async fn get_recommended_posts( | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::{ | ||||
|     extract::{OriginalUri, Path}, | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
| use tokio::try_join; | ||||
| use tracing::debug; | ||||
| @@ -21,7 +23,7 @@ use super::post_list::PostListTemplate; | ||||
| pub async fn render_broadcast_post_list( | ||||
|     tag: Option<Path<String>>, | ||||
|     OriginalUri(original_uri): OriginalUri, | ||||
| ) -> Result<PostListTemplate, StatusCode> { | ||||
| ) -> Result<impl IntoResponse, StatusCode> { | ||||
|     // I will forget what happens here in a week. But essentially it's pattern matching and shadowing | ||||
|     let tag = tag.map(|Path(tag)| tag); | ||||
|  | ||||
| @@ -50,14 +52,18 @@ pub async fn render_broadcast_post_list( | ||||
|         "Broadcasts".to_string() | ||||
|     }; | ||||
|  | ||||
|     Ok(PostListTemplate { | ||||
|         title: title.clone(), | ||||
|         og_title: title, | ||||
|         segment: Segment::Broadcasts, | ||||
|         posts, | ||||
|         header_props, | ||||
|         tags: popular_tags, | ||||
|         featured_projects, | ||||
|         current_url: original_uri.to_string(), | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         PostListTemplate { | ||||
|             title: title.clone(), | ||||
|             og_title: title, | ||||
|             segment: Segment::Broadcasts, | ||||
|             posts, | ||||
|             header_props, | ||||
|             tags: popular_tags, | ||||
|             featured_projects, | ||||
|             current_url: original_uri.to_string(), | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
|  | ||||
| use crate::components::site_header::HeaderProps; | ||||
|  | ||||
| @@ -18,7 +21,7 @@ pub struct ContactPageTemplate { | ||||
|     pub links: Vec<ContactLink>, | ||||
| } | ||||
|  | ||||
| pub async fn render_contact() -> Result<ContactPageTemplate, StatusCode> { | ||||
| pub async fn render_contact() -> Result<impl IntoResponse, StatusCode> { | ||||
|     let links = vec![ | ||||
|         ContactLink { | ||||
|             href: "mailto:michalvankosk@gmail.com".to_string(), | ||||
| @@ -76,9 +79,13 @@ pub async fn render_contact() -> Result<ContactPageTemplate, StatusCode> { | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     Ok(ContactPageTemplate { | ||||
|         title: "Contact".to_owned(), | ||||
|         header_props: HeaderProps::default(), | ||||
|         links, | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         ContactPageTemplate { | ||||
|             title: "Contact".to_owned(), | ||||
|             header_props: HeaderProps::default(), | ||||
|             links, | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| use std::rc::Rc; | ||||
|  | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
| use tokio::try_join; | ||||
|  | ||||
| use crate::{ | ||||
| @@ -26,7 +29,7 @@ pub struct IndexTemplate { | ||||
|     featured_broadcasts: Vec<Rc<ParseResult<BlogPostMetadata>>>, | ||||
| } | ||||
|  | ||||
| pub async fn render_index() -> Result<IndexTemplate, StatusCode> { | ||||
| pub async fn render_index() -> Result<impl IntoResponse, StatusCode> { | ||||
|     let (blog_tags, broadcasts_tags, all_posts, featured_projects) = try_join!( | ||||
|         get_popular_tags(Some(Segment::Blog)), | ||||
|         get_popular_tags(Some(Segment::Broadcasts)), | ||||
| @@ -44,12 +47,16 @@ pub async fn render_index() -> Result<IndexTemplate, StatusCode> { | ||||
|     let featured_broadcasts = | ||||
|         ref_get_posts_by_segment(&all_posts_rc, &[Segment::Broadcasts, Segment::Featured]); | ||||
|  | ||||
|     Ok(IndexTemplate { | ||||
|         header_props: HeaderProps::default(), | ||||
|         blog_tags, | ||||
|         broadcasts_tags, | ||||
|         featured_blog_posts, | ||||
|         featured_projects, | ||||
|         featured_broadcasts, | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         IndexTemplate { | ||||
|             header_props: HeaderProps::default(), | ||||
|             blog_tags, | ||||
|             broadcasts_tags, | ||||
|             featured_blog_posts, | ||||
|             featured_projects, | ||||
|             featured_broadcasts, | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| use askama::Template; | ||||
| use axum::{extract::OriginalUri, http::StatusCode}; | ||||
| use axum::{ | ||||
|     extract::OriginalUri, | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
| use tracing::info; | ||||
|  | ||||
| use crate::components::site_header::HeaderProps; | ||||
| @@ -13,13 +17,17 @@ pub struct NotFoundPage { | ||||
|  | ||||
| pub async fn render_not_found( | ||||
|     OriginalUri(original_uri): OriginalUri, | ||||
| ) -> Result<(StatusCode, NotFoundPage), StatusCode> { | ||||
| ) -> Result<(StatusCode, impl IntoResponse), StatusCode> { | ||||
|     info!("{original_uri} not found"); | ||||
|     Ok(( | ||||
|         StatusCode::NOT_FOUND, | ||||
|         NotFoundPage { | ||||
|             title: "This page does not exists".to_owned(), | ||||
|             header_props: HeaderProps::default(), | ||||
|         }, | ||||
|         Html( | ||||
|             NotFoundPage { | ||||
|                 title: "This page does not exists".to_owned(), | ||||
|                 header_props: HeaderProps::default(), | ||||
|             } | ||||
|             .render() | ||||
|             .unwrap(), | ||||
|         ), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
| use serde::Deserialize; | ||||
|  | ||||
| use crate::{ | ||||
| @@ -50,7 +53,7 @@ pub struct PortfolioTemplate { | ||||
|     pub technology_list: Vec<String>, | ||||
| } | ||||
|  | ||||
| pub async fn render_portfolio() -> Result<PortfolioTemplate, StatusCode> { | ||||
| pub async fn render_portfolio() -> Result<impl IntoResponse, StatusCode> { | ||||
|     let portfolio = parse_post::<PortfolioPageModel>("_pages/portfolio.md").await?; | ||||
|  | ||||
|     let mut project_list = get_post_list::<ProjectMetadata>("_projects").await?; | ||||
| @@ -126,14 +129,18 @@ pub async fn render_portfolio() -> Result<PortfolioTemplate, StatusCode> { | ||||
|     .map(|str| str.to_owned()) | ||||
|     .collect(); | ||||
|  | ||||
|     Ok(PortfolioTemplate { | ||||
|         title: "Portfolio".to_owned(), | ||||
|         body: portfolio.body, | ||||
|         header_props: HeaderProps::default(), | ||||
|         project_list, | ||||
|         workplace_list, | ||||
|         education_list, | ||||
|         contact_links, | ||||
|         technology_list, | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         PortfolioTemplate { | ||||
|             title: "Portfolio".to_owned(), | ||||
|             body: portfolio.body, | ||||
|             header_props: HeaderProps::default(), | ||||
|             project_list, | ||||
|             workplace_list, | ||||
|             education_list, | ||||
|             contact_links, | ||||
|             technology_list, | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     components::site_header::HeaderProps, | ||||
| @@ -16,16 +19,20 @@ pub struct ProjectListTemplate { | ||||
|     pub header_props: HeaderProps, | ||||
| } | ||||
|  | ||||
| pub async fn render_projects_list() -> Result<ProjectListTemplate, StatusCode> { | ||||
| pub async fn render_projects_list() -> Result<impl IntoResponse, StatusCode> { | ||||
|     let mut project_list = get_post_list::<ProjectMetadata>("_projects").await?; | ||||
|  | ||||
|     project_list.sort_by_key(|post| post.slug.to_string()); | ||||
|     project_list.retain(|project| project.metadata.displayed); | ||||
|     project_list.reverse(); | ||||
|  | ||||
|     Ok(ProjectListTemplate { | ||||
|         title: "Showcase".to_owned(), | ||||
|         header_props: HeaderProps::default(), | ||||
|         project_list, | ||||
|     }) | ||||
|     Ok(Html( | ||||
|         ProjectListTemplate { | ||||
|             title: "Showcase".to_owned(), | ||||
|             header_props: HeaderProps::default(), | ||||
|             project_list, | ||||
|         } | ||||
|         .render() | ||||
|         .unwrap(), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| use askama::Template; | ||||
| use axum::http::StatusCode; | ||||
| use axum::{ | ||||
|     http::StatusCode, | ||||
|     response::{Html, IntoResponse}, | ||||
| }; | ||||
|  | ||||
| use crate::components::site_header::HeaderProps; | ||||
|  | ||||
| @@ -9,8 +12,12 @@ pub struct EggFetcherShowcaseTemplate { | ||||
|     header_props: HeaderProps, | ||||
| } | ||||
|  | ||||
| pub async fn render_egg_fetcher() -> Result<EggFetcherShowcaseTemplate, StatusCode> { | ||||
|     Ok(EggFetcherShowcaseTemplate { | ||||
|         header_props: HeaderProps::default(), | ||||
|     }) | ||||
| pub async fn render_egg_fetcher() -> Result<impl IntoResponse, StatusCode> { | ||||
|     Ok(Html( | ||||
|         EggFetcherShowcaseTemplate { | ||||
|             header_props: HeaderProps::default(), | ||||
|         } | ||||
|         .render() | ||||
|         .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR), | ||||
|     )) | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,14 @@ use crate::{ | ||||
|         project_list::render_projects_list, showcase::egg_fetcher::render_egg_fetcher, | ||||
|     }, | ||||
| }; | ||||
| use axum::{extract::MatchedPath, http::Request, routing::get, Router}; | ||||
| use askama::Template; | ||||
| use axum::{ | ||||
|     extract::MatchedPath, | ||||
|     http::{Request, StatusCode}, | ||||
|     response::IntoResponse, | ||||
|     routing::get, | ||||
|     Router, | ||||
| }; | ||||
| use tower_http::trace::TraceLayer; | ||||
| use tracing::info_span; | ||||
|  | ||||
| @@ -16,15 +23,15 @@ pub fn get_router() -> Router { | ||||
|     Router::new() | ||||
|         .route("/", get(render_index)) | ||||
|         .route("/blog", get(render_blog_post_list)) | ||||
|         .route("/blog/tags/:tag", get(render_blog_post_list)) | ||||
|         .route("/blog/:post_id", get(render_blog_post)) | ||||
|         .route("/blog/tags/{tag}", get(render_blog_post_list)) | ||||
|         .route("/blog/{post_id}", get(render_blog_post)) | ||||
|         .route("/broadcasts", get(render_broadcast_post_list)) | ||||
|         .route("/broadcasts/tags/:tag", get(render_broadcast_post_list)) | ||||
|         .route("/broadcasts/:post_id", get(render_blog_post)) | ||||
|         .route("/broadcasts/tags/{tag}", get(render_broadcast_post_list)) | ||||
|         .route("/broadcasts/{post_id}", get(render_blog_post)) | ||||
|         .route("/contact", get(render_contact)) | ||||
|         .route("/showcase", get(render_projects_list)) | ||||
|         .route("/showcase/m-logo-svg", get(render_animated_logo)) | ||||
|         .route("/showcase/:project_slug", get(render_egg_fetcher)) | ||||
|         .route("/showcase/{project_slug}", get(render_egg_fetcher)) | ||||
|         .route("/portfolio", get(render_portfolio)) | ||||
|         .route("/admin", get(render_admin)) | ||||
|         .route("/feed.xml", get(render_rss_feed)) | ||||
|   | ||||
| @@ -335,9 +335,6 @@ | ||||
|   .mt-8 { | ||||
|     margin-top: calc(var(--spacing) * 8); | ||||
|   } | ||||
|   .mt-12 { | ||||
|     margin-top: calc(var(--spacing) * 12); | ||||
|   } | ||||
|   .mr-3 { | ||||
|     margin-right: calc(var(--spacing) * 3); | ||||
|   } | ||||
| @@ -936,11 +933,6 @@ | ||||
|       margin-block: calc(var(--spacing) * 6); | ||||
|     } | ||||
|   } | ||||
|   .lg\:mt-8 { | ||||
|     @media (width >= 64rem) { | ||||
|       margin-top: calc(var(--spacing) * 8); | ||||
|     } | ||||
|   } | ||||
|   .lg\:mt-10 { | ||||
|     @media (width >= 64rem) { | ||||
|       margin-top: calc(var(--spacing) * 10); | ||||
|   | ||||
| @@ -2,8 +2,7 @@ | ||||
| 	<aside class="flex justify-center items-center pr-3 shrink-0"> | ||||
|   {% match skill.thumbnail %} | ||||
|   {% when Some with (source) %} | ||||
| 	{% let skill_name = skill.name.clone() %} | ||||
|   {% let picture = crate::picture_generator::picture_markup_generator::generate_picture_markup(source, 64, 64, format!("{skill_name} cover"), Some("h-12 w-12 md:h-16 md:w-16")).unwrap_or("cover not found".to_string()) %} | ||||
|   {% let picture = crate::picture_generator::picture_markup_generator::generate_picture_markup(source, 64, 64, &format!("{} cover", skill.name), Some("h-12 w-12 md:h-16 md:w-16")).unwrap_or("cover not found".to_string()) %} | ||||
|   <figure class="mx-4 my-2"> | ||||
|      {{picture|safe}} | ||||
|   </figure> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user