diff --git a/locales/en.yaml b/locales/en.yaml index d49f87d..4123b5b 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -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 diff --git a/locales/fi.yaml b/locales/fi.yaml index 9424e93..ee15b4b 100644 --- a/locales/fi.yaml +++ b/locales/fi.yaml @@ -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 diff --git a/locales/fr.yaml b/locales/fr.yaml index 7bff598..084c634 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -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 diff --git a/src/app.rs b/src/app.rs index 44c554a..89a4493 100644 --- a/src/app.rs +++ b/src/app.rs @@ -50,6 +50,10 @@ pub fn App() -> impl IntoView { view=Outlet> + + + + @@ -92,10 +96,9 @@ pub fn Header() -> impl IntoView {

tanguy.gerome.fi

diff --git a/src/blog.rs b/src/blog.rs new file mode 100644 index 0000000..51141cd --- /dev/null +++ b/src/blog.rs @@ -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, + illustration: Asset, +} + +#[server(GetBlog, "/api", "GetJson")] +pub async fn get_blog_posts(locale: String) -> Result, ServerFnError> { + crate::services::directus::get_client() + .get_all_items_in_collection::(&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! { + +
+

{t!(i18n, blog_latest_posts)}:

+ "Loading..." }> + {move || blog_posts.get() + .and_then(|blog_posts| blog_posts.ok()) + .and_then(|blog_posts| { + view! { +
+ { + blog_posts.into_iter().map(|post| { + view! { + + +

{post.translations.unwrap_or_default().title}

+
+ } + }).collect::>() + } +
+ }.into() + }) + } +
+
+ } +} + +#[derive(Params, PartialEq)] +struct BlogPostParams { + slug: Option, +} + +#[component] +pub fn BlogPost() -> impl IntoView { + let i18n = use_i18n(); + let params = use_params::(); + 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! { +
"Loading..."
}> + {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! { +
+
+ +

{translations.title}

+
+
+
+
+
+ }.into() + }) + } +
+ } +} diff --git a/src/lib.rs b/src/lib.rs index 6758df1..9a9cdc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod app; pub mod services; +pub mod blog; pub mod gallery; #[cfg(feature = "hydrate")] diff --git a/src/services/directus.rs b/src/services/directus.rs index 30ba30d..dd6eb33 100644 --- a/src/services/directus.rs +++ b/src/services/directus.rs @@ -58,7 +58,7 @@ impl DirectusClient { } pub async fn get_all_items_in_collection Deserialize<'a>>(&self, locale: &str, collection: &str) -> Result> { - 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::().await?; diff --git a/style/main.scss b/style/main.scss index 2d51a3b..25b69e3 100644 --- a/style/main.scss +++ b/style/main.scss @@ -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) {