Add WIP blog
This commit is contained in:
parent
d2702ae39f
commit
0065528f30
8 changed files with 193 additions and 10 deletions
|
@ -5,7 +5,7 @@ partial_translations: (partial translations)
|
|||
resume: Resume
|
||||
|
||||
blog: Blog
|
||||
welcome_blog: Welcome to my blog!
|
||||
blog_latest_posts: Latests posts
|
||||
|
||||
gallery: Gallery
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ partial_translations: (keskeneräisiä käännöksiä)
|
|||
resume: Ansioluettelo
|
||||
|
||||
blog: Blogi
|
||||
welcome_blog: Tervetuloa blogiini!
|
||||
blog_latest_posts: Viimeisimmät postaukset
|
||||
|
||||
gallery: Galleria
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ partial_translations: (traductions partielles)
|
|||
resume: CV
|
||||
|
||||
blog: Blog
|
||||
welcome_blog: Bienvenue sur mon blog!
|
||||
blog_latest_posts: Dernier billets
|
||||
|
||||
gallery: Gallerie
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ pub fn App() -> impl IntoView {
|
|||
<I18nRoute<Locale, _, _> view=Outlet>
|
||||
<Route path=path!("/") view=HomePage/>
|
||||
<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>
|
||||
<Route path=path!(":slug") view=crate::gallery::GalleryEntry/>
|
||||
<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>
|
||||
<div class="links">
|
||||
<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="/resume">{t!(i18n, resume)}</A>
|
||||
<A href="/resume">{t!(i18n, resume)}</A>
|
||||
</div>
|
||||
</div>
|
||||
<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-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-wrapper">
|
||||
<div class="markdown" inner_html=html/>
|
||||
</main>
|
||||
</div>
|
||||
}.into()
|
||||
})
|
||||
}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ pub mod app;
|
|||
|
||||
pub mod services;
|
||||
|
||||
pub mod blog;
|
||||
pub mod gallery;
|
||||
|
||||
#[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>> {
|
||||
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))
|
||||
.send().await?;
|
||||
let json = &response.json::<serde_json::Value>().await?;
|
||||
|
|
|
@ -233,7 +233,7 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.gallery-entries {
|
||||
.gallery-entries, .blog-entries {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -243,10 +243,76 @@ body {
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
.blog-open-entry {
|
||||
display: flex;
|
||||
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 {
|
||||
img {
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
object-fit: contain;
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
|
|
Loading…
Add table
Reference in a new issue