193 lines
No EOL
5.6 KiB
Rust
193 lines
No EOL
5.6 KiB
Rust
use leptos::*;
|
|
use leptos_router::*;
|
|
use crate::app::{
|
|
models::{Post, PostMetadata},
|
|
components::{
|
|
Loading, Nav, ReadingTime, DateTime
|
|
}
|
|
};
|
|
|
|
#[server]
|
|
pub async fn get_posts(
|
|
tag: Option<String>
|
|
) -> Result<Vec<Post>, ServerFnError> {
|
|
leptos_actix::extract(
|
|
|data: actix_web::web::Data<crate::app::models::Data>| async move {
|
|
let data = data.into_inner();
|
|
let default = vec![];
|
|
let posts = match tag {
|
|
Some(tag) => data.posts_by_tag.get(&tag).unwrap_or(&default),
|
|
None => &data.posts
|
|
};
|
|
posts.iter()
|
|
.map(|post| {
|
|
Post {
|
|
metadata: post.metadata.clone(),
|
|
content: post.content.clone(),
|
|
}
|
|
})
|
|
.collect::<Vec<_>, >()
|
|
},
|
|
).await
|
|
}
|
|
|
|
#[server]
|
|
pub async fn get_post(
|
|
slug: String
|
|
) -> Result<Post, ServerFnError> {
|
|
leptos_actix::extract(
|
|
|data: actix_web::web::Data<crate::app::models::Data>| async move {
|
|
let data = data.into_inner();
|
|
data.posts_by_slug.get(&slug)
|
|
.and_then(|post| Some(Post {
|
|
metadata: post.metadata.clone(),
|
|
content: post.content.clone(),
|
|
}))
|
|
},
|
|
)
|
|
.await
|
|
.and_then(|post| post.ok_or_else(|| ServerFnError::ServerError("Post not found".to_string())))
|
|
}
|
|
|
|
#[component]
|
|
pub fn PostHeader(
|
|
metadata: PostMetadata,
|
|
full_element: bool
|
|
) -> impl IntoView {
|
|
let class = if full_element {
|
|
"metadata border-b-2 pb-5"
|
|
} else {
|
|
"metadata"
|
|
};
|
|
|
|
let title = if full_element {
|
|
view! { <>{metadata.title.clone()}</> }.into_view()
|
|
} else {
|
|
view! { <A href=format!("/posts/{}", metadata.slug.clone())>{metadata.title.clone()}</A> }
|
|
};
|
|
|
|
view! {
|
|
<div class=class>
|
|
<h2>{title}</h2>
|
|
<p>{metadata.description.clone()}</p>
|
|
<DateTime datetime=metadata.date />
|
|
<ReadingTime time=metadata.reading_time />
|
|
<PostTags tags=metadata.tags.clone()/>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn PostTags(
|
|
tags: Vec<String>
|
|
) -> impl IntoView {
|
|
view! {
|
|
<div class="tags_list">
|
|
{
|
|
tags.into_iter().map(|tag| view! { <A class="tag" href=format!("/posts?tag={}", tag)>{tag}</A>}).collect_view()
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn PostListCard(
|
|
post: Post
|
|
) -> impl IntoView {
|
|
view! {
|
|
<div>
|
|
<img src={post.metadata.image_path.clone()} alt=format!("Image {}", post.metadata.title)/>
|
|
|
|
{
|
|
if post.metadata.draft {
|
|
Some(view!{
|
|
<div class="warning">
|
|
<leptos_icons::Icon icon=icondata::IoConstruct/>
|
|
</div>
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
<PostHeader metadata=post.metadata.clone() full_element=false />
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn PostList() -> impl IntoView {
|
|
let query = use_query_map();
|
|
let tag = move || query.with(|query| query.get("tag").cloned());
|
|
let posts = create_resource(move || tag(), move |_| get_posts(tag()));
|
|
|
|
let posts_view = move || {
|
|
posts.and_then(|posts| {
|
|
posts.iter()
|
|
.map(|post| view! { <PostListCard post=post.clone() /> })
|
|
.collect_view()
|
|
})
|
|
};
|
|
|
|
let filter_view = move || {
|
|
tag().and_then(|tag| Some(view! {
|
|
<div class="mx-auto max-w-3xl mb-5">
|
|
Tag sélectionné : <A class="tag" href="/posts".to_string()>{tag}<leptos_icons::Icon icon=icondata::IoCloseOutline class="scale-125 ml-1 inline" /></A>
|
|
</div>
|
|
}))
|
|
};
|
|
|
|
view! {
|
|
<Suspense fallback=move || view! { <Loading title="Chargement des posts...".to_string() /> }>
|
|
<Nav/>
|
|
{filter_view}
|
|
<main class="posts">
|
|
<div class="posts__cards">{posts_view}</div>
|
|
</main>
|
|
</Suspense>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn PostElement() -> impl IntoView {
|
|
let params = use_params_map();
|
|
let slug = move || params.with(|params| params.get("slug").cloned().unwrap_or_default());
|
|
|
|
let post = create_resource(|| (), move |_| get_post(slug()));
|
|
|
|
let post_view = move || {
|
|
post.and_then(|post| {
|
|
view! {
|
|
<>
|
|
{
|
|
if post.metadata.draft {
|
|
Some(view!{
|
|
<div class="bg-warning text-on_warning dark:bg-dark_warning dark:text-dart_on_warning rounded-md p-5 mb-5">
|
|
r#"
|
|
L'article est en cours d'écriture. La formulation peut ne pas être exacte et les phrases peuvent contenir des fautes.
|
|
"#
|
|
</div>
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
<PostHeader metadata=post.metadata.clone() full_element=true />
|
|
|
|
<div inner_html={post.content.clone()}></div>
|
|
</>
|
|
}
|
|
})
|
|
};
|
|
|
|
view! {
|
|
<Suspense fallback=move || view! { <Loading title="Chargement du post...".to_string() /> }>
|
|
<Nav/>
|
|
<main class="post">
|
|
{post_view}
|
|
<script>load();</script>
|
|
</main>
|
|
</Suspense>
|
|
}
|
|
} |