Add WIP blog
This commit is contained in:
parent
d2702ae39f
commit
f2357759f1
8 changed files with 195 additions and 11 deletions
|
@ -5,7 +5,7 @@ partial_translations: (partial translations)
|
||||||
resume: Resume
|
resume: Resume
|
||||||
|
|
||||||
blog: Blog
|
blog: Blog
|
||||||
welcome_blog: Welcome to my blog!
|
blog_latest_posts: Latests posts
|
||||||
|
|
||||||
gallery: Gallery
|
gallery: Gallery
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ partial_translations: (keskeneräisiä käännöksiä)
|
||||||
resume: Ansioluettelo
|
resume: Ansioluettelo
|
||||||
|
|
||||||
blog: Blogi
|
blog: Blogi
|
||||||
welcome_blog: Tervetuloa blogiini!
|
blog_latest_posts: Viimeisimmät postaukset
|
||||||
|
|
||||||
gallery: Galleria
|
gallery: Galleria
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ partial_translations: (traductions partielles)
|
||||||
resume: CV
|
resume: CV
|
||||||
|
|
||||||
blog: Blog
|
blog: Blog
|
||||||
welcome_blog: Bienvenue sur mon blog!
|
blog_latest_posts: Dernier billets
|
||||||
|
|
||||||
gallery: Gallerie
|
gallery: Gallerie
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,10 @@ pub fn App() -> impl IntoView {
|
||||||
<I18nRoute<Locale, _, _> view=Outlet>
|
<I18nRoute<Locale, _, _> view=Outlet>
|
||||||
<Route path=path!("/") view=HomePage/>
|
<Route path=path!("/") view=HomePage/>
|
||||||
<Route path=path!("/resume") view=Resume/>
|
<Route path=path!("/resume") view=Resume/>
|
||||||
|
<ParentRoute path=path!("/blog") view=crate::blog::Blog>
|
||||||
|
<Route path=path!(":slug") view=crate::blog::BlogPost/>
|
||||||
|
<Route path=path!("") view=|| view! {}/>
|
||||||
|
</ParentRoute>
|
||||||
<ParentRoute path=path!("/gallery") view=crate::gallery::Gallery>
|
<ParentRoute path=path!("/gallery") view=crate::gallery::Gallery>
|
||||||
<Route path=path!(":slug") view=crate::gallery::GalleryEntry/>
|
<Route path=path!(":slug") view=crate::gallery::GalleryEntry/>
|
||||||
<Route path=path!("") view=|| view! {}/>
|
<Route path=path!("") view=|| view! {}/>
|
||||||
|
@ -92,10 +96,9 @@ pub fn Header() -> impl IntoView {
|
||||||
<h1><span>tanguy</span><span>.gerome</span><span>.fi</span></h1>
|
<h1><span>tanguy</span><span>.gerome</span><span>.fi</span></h1>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<A href="/">{t!(i18n, home)}</A>
|
<A href="/">{t!(i18n, home)}</A>
|
||||||
<A href="/resume">{t!(i18n, resume)}</A>
|
<A href="/blog">{t!(i18n, blog)}</A>
|
||||||
// <A href="/blog">{t!(i18n, blog)}</A>
|
|
||||||
<A href="/gallery">{t!(i18n, gallery)}</A>
|
<A href="/gallery">{t!(i18n, gallery)}</A>
|
||||||
// <A href="/resume">{t!(i18n, resume)}</A>
|
<A href="/resume">{t!(i18n, resume)}</A>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<picture>
|
<picture>
|
||||||
|
|
113
src/blog.rs
Normal file
113
src/blog.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use leptos::{
|
||||||
|
Params,
|
||||||
|
prelude::*
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use leptos_router::{
|
||||||
|
components::Outlet, hooks::use_params, params::Params
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{i18n::*, services::directus::Asset};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct BlogPostTranslations {
|
||||||
|
title: String,
|
||||||
|
content: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BlogPost {
|
||||||
|
slug: String,
|
||||||
|
translations: Option<BlogPostTranslations>,
|
||||||
|
illustration: Asset,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(GetBlog, "/api", "GetJson")]
|
||||||
|
pub async fn get_blog_posts(locale: String) -> Result<Vec<BlogPost>, ServerFnError> {
|
||||||
|
crate::services::directus::get_client()
|
||||||
|
.get_all_items_in_collection::<BlogPost>(&locale, "blog_posts").await
|
||||||
|
.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Blog() -> impl IntoView {
|
||||||
|
let i18n = use_i18n();
|
||||||
|
|
||||||
|
let blog_posts = Resource::new(move || i18n.get_locale(), move |locale| get_blog_posts(locale.to_string()));
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Outlet/>
|
||||||
|
<main class="main-width blog-wrapper">
|
||||||
|
<h2>{t!(i18n, blog_latest_posts)}:</h2>
|
||||||
|
<Suspense fallback=move || view! { <div>"Loading..."</div> }>
|
||||||
|
{move || blog_posts.get()
|
||||||
|
.and_then(|blog_posts| blog_posts.ok())
|
||||||
|
.and_then(|blog_posts| {
|
||||||
|
view! {
|
||||||
|
<div class="blog-entries">
|
||||||
|
{
|
||||||
|
blog_posts.into_iter().map(|post| {
|
||||||
|
view! {
|
||||||
|
<a class="blog-thumbnail" href={format!("/blog/{}", post.slug)}>
|
||||||
|
<img src=format!("https://directus.gerome.fi/assets/{}?width=600&height=400&fit=cover&format=auto&quality=80&withoutEnlargement=true", post.illustration.id)/>
|
||||||
|
<p class="blog-title">{post.translations.unwrap_or_default().title}</p>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Suspense>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params, PartialEq)]
|
||||||
|
struct BlogPostParams {
|
||||||
|
slug: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BlogPost() -> impl IntoView {
|
||||||
|
let i18n = use_i18n();
|
||||||
|
let params = use_params::<BlogPostParams>();
|
||||||
|
let slug = move || {
|
||||||
|
params .read()
|
||||||
|
.as_ref()
|
||||||
|
.ok()
|
||||||
|
.and_then(|params| params.slug.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let blog_posts = Resource::new(move || i18n.get_locale(), move |locale| get_blog_posts(locale.to_string()));
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Suspense fallback=move || view! { <main class="main-width blog-entry-wrapper"><div>"Loading..."</div></main> }>
|
||||||
|
{move || blog_posts.get()
|
||||||
|
.and_then(|blog_posts| blog_posts.ok())
|
||||||
|
.and_then(|blog_posts| blog_posts.iter()
|
||||||
|
.find(|post| post.slug == slug())
|
||||||
|
.map(|post| post.clone()))
|
||||||
|
.and_then(|post| {
|
||||||
|
let translations = post.translations.unwrap_or_default();
|
||||||
|
let html = markdown::to_html(&translations.content);
|
||||||
|
view! {
|
||||||
|
<div class="blog-open-entry">
|
||||||
|
<div class="blog-illustration">
|
||||||
|
<img src=format!("https://directus.gerome.fi/assets/{}?width=1600&height=1000&fit=inside&format=auto&quality=90&withoutEnlargement=true", post.illustration.id)/>
|
||||||
|
<h2 class="blog-title">{translations.title}</h2>
|
||||||
|
</div>
|
||||||
|
<main class="main-width blog-entry-wrapper">
|
||||||
|
<div class="markdown" inner_html=html/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
}.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ pub mod app;
|
||||||
|
|
||||||
pub mod services;
|
pub mod services;
|
||||||
|
|
||||||
|
pub mod blog;
|
||||||
pub mod gallery;
|
pub mod gallery;
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl DirectusClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_items_in_collection<T: for<'a> Deserialize<'a>>(&self, locale: &str, collection: &str) -> Result<Vec<T>> {
|
pub async fn get_all_items_in_collection<T: for<'a> Deserialize<'a>>(&self, locale: &str, collection: &str) -> Result<Vec<T>> {
|
||||||
let response = self.reqwest_client.get(format!("{}/items/{}?fields=*,*.*,translations.*", self.base_url, collection))
|
let response = self.reqwest_client.get(format!("{}/items/{}?fields=*,*.*,translations.*&sort=-date_created", self.base_url, collection))
|
||||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||||
.send().await?;
|
.send().await?;
|
||||||
let json = &response.json::<serde_json::Value>().await?;
|
let json = &response.json::<serde_json::Value>().await?;
|
||||||
|
|
|
@ -233,8 +233,8 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-entries {
|
.gallery-entries, .blog-entries {
|
||||||
width: fit-content;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -243,10 +243,77 @@ body {
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blog-open-entry {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.blog-illustration {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 60vh;
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-title {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 64px;
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 1.0));
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-wrapper {
|
||||||
|
max-width: 1250px!important;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-title {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 64px;
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 1.0));
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-wrapper {
|
||||||
|
max-width: 1000px!important;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
.gallery-thumbnail {
|
.gallery-thumbnail {
|
||||||
img {
|
img {
|
||||||
height: 400px;
|
height: 300px;
|
||||||
width: 400px;
|
width: 300px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
@media all and (max-width: 1000px) {
|
@media all and (max-width: 1000px) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue