use std::sync::Arc; use leptos::*; use crate::prelude::*; use apb::{field::OptionalString, target::Addressed, ActivityMut, Base, Collection, CollectionMut, Object, ObjectMut}; #[component] pub fn Object( object: crate::Object, #[prop(optional)] reply: bool, ) -> impl IntoView { let oid = object.id().unwrap_or_default().to_string(); let author_id = object.attributed_to().id().str().unwrap_or_default(); let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into()); let sensitive = object.sensitive().unwrap_or_default(); let addressed = object.addressed(); let public = addressed.iter().any(|x| x.as_str() == apb::target::PUBLIC); let external_url = object.url().id().str().unwrap_or_else(|| oid.clone()); let attachments = object.attachment() .map(|x| view! { }) .collect_view(); let comments = object.replies().get() .map_or(0, |x| x.total_items().unwrap_or(0)); let shares = object.shares().get() .map_or(0, |x| x.total_items().unwrap_or(0)); let likes = object.likes().get() .map_or(0, |x| x.total_items().unwrap_or(0)); let already_liked = object.liked_by_me().unwrap_or(false); let attachments_padding = if object.attachment().is_empty() { None } else { Some(view! {
}) }; let content = mdhtml::safe_html(object.content().unwrap_or_default()); let audience_badge = object.audience().id().str() .map(|x| { // TODO this isn't guaranteed to work every time... let name = x.split('/').last().unwrap_or_default().to_string(); view! { & {name} } }); let quote_badge = object.quote_url() .id() .ok() .map(|x| { let href = Uri::web(U::Object, x); view! { ">" RE " " } }); let tag_badges = object.tag() .flat() .into_iter() .filter_map(|node| match node { apb::Node::Link(x) => Some(x), _ => None, }) .map(|link| { match apb::Link::link_type(link.as_ref()) { Ok(apb::LinkType::Hashtag) => { let name = apb::Link::name(link.as_ref()).unwrap_or_default().replace('#', ""); let href = Uri::web(U::Hashtag, &name); Some(view! { # {name} " " }) }, Ok(apb::LinkType::Mention) => { let uid = apb::Link::href(link.as_ref()).unwrap_or_default(); let mention = apb::Link::name(link.as_ref()).unwrap_or_default().replacen('@', "", 1); let (username, domain) = if let Some((username, server)) = mention.split_once('@') { (username.to_string(), server.to_string()) } else { ( mention.to_string(), uid.replace("https://", "").replace("http://", "").split('/').next().unwrap_or_default().to_string(), ) }; let href = Uri::web(U::Actor, uid); Some(view! { @ {username} " " }) }, _ => None, } }).collect_view(); let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| { let (expand, set_expand) = create_signal(false); view! { } }); let post_inner = view! {

{attachments_padding} {attachments}
}; let post = match object.object_type() { // mastodon, pleroma, misskey Ok(apb::ObjectType::Note) => view! {
{post_inner}
}.into_view(), // lemmy with Page, peertube with Video Ok(apb::ObjectType::Document(t)) => view! {
{post_image}

{object.name().unwrap_or_default().to_string()}

{post_inner}
}.into_view(), // wordpress, ... ? Ok(apb::ObjectType::Article) => view! {

{object.name().unwrap_or_default().to_string()}


{post_inner}
}.into_view(), // everything else Ok(t) => view! {

{t.as_ref().to_string()}

{post_inner} }.into_view(), // object without type? Err(_) => view! { missing object type }.into_view(), }; view! {
{object.in_reply_to().id().str().map(|reply| view! { reply })} "↗"
{post}
{quote_badge} {tag_badges} {audience_badge}
} } #[component] pub fn Summary(summary: Option, children: Children) -> impl IntoView { let config = use_context::>().expect("missing config context"); match summary.filter(|x| !x.is_empty()) { None => children().into_view(), Some(summary) => view! {
{summary} {children()}
}.into_view(), } } #[component] pub fn LikeButton( n: u64, target: String, liked: bool, author: String, #[prop(optional)] private: bool, ) -> impl IntoView { let (count, set_count) = create_signal(n); let (clicked, set_clicked) = create_signal(!liked); let auth = use_context::().expect("missing auth context"); view! { { set_clicked.set(false); set_count.set(count.get() + 1); if let Some(cached) = cache::OBJECTS.get(&target) { let mut new = (*cached).clone().set_liked_by_me(Some(true)); if let Some(likes) = new.likes().get() { if let Ok(count) = likes.total_items() { new = new.set_likes(apb::Node::object(likes.clone().set_total_items(Some(count + 1)))); } } cache::OBJECTS.store(&target, Arc::new(new)); } }, Err(e) => tracing::error!("failed sending like: {e}"), } }); } > {move || if count.get() > 0 { Some(view! { {count} })} else { None }} " ⭐" } } #[component] pub fn ReplyButton(n: u64, target: String) -> impl IntoView { let reply = use_context::().expect("missing reply controls context"); let auth = use_context::().expect("missing auth context"); let comments = if n > 0 { Some(view! { {n} }) } else { None }; let _target = target.clone(); // TODO ughhhh useless clones view! { {comments} " 📨" } } #[component] pub fn RepostButton(n: u64, target: String) -> impl IntoView { let (count, set_count) = create_signal(n); let (clicked, set_clicked) = create_signal(true); let auth = use_context::().expect("missing auth context"); view! { set_count.set(count.get() + 1), Err(e) => tracing::error!("failed sending like: {e}"), } set_clicked.set(true); }); } > {move || if count.get() > 0 { Some(view! { {count} })} else { None }} " 🚀" } }