forked from alemi/upub
feat: show attachments, initial work on threads
This commit is contained in:
parent
e9a19b3cb4
commit
1171d4cd06
5 changed files with 82 additions and 56 deletions
|
@ -152,6 +152,11 @@
|
|||
img.inline-avatar {
|
||||
max-height: 2em;
|
||||
}
|
||||
img.attachment {
|
||||
max-height: 15em;
|
||||
border: 5px solid #bf616a;
|
||||
padding: 5px;
|
||||
}
|
||||
div.tl-header {
|
||||
background-color: #bf616a55;
|
||||
color: #bf616a;
|
||||
|
|
|
@ -2,13 +2,13 @@ mod activity;
|
|||
pub use activity::ActivityLine;
|
||||
|
||||
mod object;
|
||||
pub use object::{Object, ObjectInline};
|
||||
pub use object::Object;
|
||||
|
||||
mod user;
|
||||
pub use user::ActorBanner;
|
||||
|
||||
mod timeline;
|
||||
pub use timeline::{TimelineFeed, Timeline};
|
||||
pub use timeline::{TimelineFeed, TimelineReplies, Timeline};
|
||||
|
||||
use leptos::*;
|
||||
|
||||
|
|
|
@ -6,64 +6,28 @@ use apb::{target::Addressed, Base, Object};
|
|||
|
||||
#[component]
|
||||
pub fn Object(object: serde_json::Value) -> impl IntoView {
|
||||
let oid = object.id().unwrap_or_default().to_string();
|
||||
let in_reply_to = object.in_reply_to().id().unwrap_or_default();
|
||||
let summary = object.summary().unwrap_or_default().to_string();
|
||||
let content = dissolve::strip_html_tags(object.content().unwrap_or_default());
|
||||
let author_id = object.attributed_to().id().unwrap_or_default();
|
||||
let author = CACHE.get_or(&author_id, serde_json::Value::String(author_id.clone()));
|
||||
view! {
|
||||
<div>
|
||||
<table class="w-100 post-table pa-1 mb-s" >
|
||||
{move || if !in_reply_to.is_empty() {
|
||||
Some(view! {
|
||||
<tr class="post-table" >
|
||||
<td class="post-table pa-1" colspan="2" >
|
||||
"in reply to "<small><a class="clean hover" href={Uri::web(FetchKind::Object, &in_reply_to)}>{Uri::pretty(&in_reply_to)}</a></small>
|
||||
</td>
|
||||
</tr>
|
||||
})
|
||||
} else { None }}
|
||||
{move || if !summary.is_empty() {
|
||||
Some(view! {
|
||||
<tr class="post-table" >
|
||||
<td class="post-table pa-1" colspan="2" >{summary.clone()}</td>
|
||||
</tr>
|
||||
})
|
||||
} else { None }}
|
||||
<tr class="post-table" >
|
||||
<td class="post-table pa-1" colspan="2" >{
|
||||
content.into_iter().map(|x| view! { <p>{x}</p> }).collect_view()
|
||||
}</td>
|
||||
</tr>
|
||||
<tr class="post-table" >
|
||||
<td class="post-table pa-1" ><ActorBanner object=author /></td>
|
||||
<td class="post-table pa-1 center" >
|
||||
<a class="clean hover" href={oid} target="_blank">
|
||||
<DateTime t=object.published() />
|
||||
<PrivacyMarker addressed=object.addressed() />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ObjectInline(object: serde_json::Value) -> impl IntoView {
|
||||
let uid = object.id().unwrap_or_default().to_string();
|
||||
let summary = object.summary().unwrap_or_default().to_string();
|
||||
let content = dissolve::strip_html_tags(object.content().unwrap_or_default());
|
||||
let author_id = object.attributed_to().id().unwrap_or_default();
|
||||
let author = CACHE.get_or(&author_id, serde_json::Value::String(author_id.clone()));
|
||||
let attachments = object.attachment()
|
||||
.map(|x| view! {
|
||||
<p><img class="attachment" src={x.url().id().unwrap_or_default()} /></p>
|
||||
})
|
||||
.collect_view();
|
||||
view! {
|
||||
<table class="align w-100">
|
||||
<tr>
|
||||
<td><ActorBanner object=author /></td>
|
||||
<td class="rev" >
|
||||
{object.in_reply_to().id().map(|reply| view! {
|
||||
<small><i><a class="clean mr-1" href={Uri::web(FetchKind::Object, &reply)} title={reply}>reply</a></i></small>
|
||||
})}
|
||||
<a class="clean hover" href={Uri::web(FetchKind::Object, object.id().unwrap_or_default())}>
|
||||
<DateTime t=object.published() />
|
||||
</a>
|
||||
<sup><small><a class="clean" href={uid} target="_blank">"↗"</a></small></sup>
|
||||
<PrivacyMarker addressed=object.addressed() />
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -72,6 +36,7 @@ pub fn ObjectInline(object: serde_json::Value) -> impl IntoView {
|
|||
{if summary.is_empty() { None } else { Some(view! { <code class="color ml-1">{summary}</code> })}}
|
||||
{content.into_iter().map(|x| view! { <p>{x}</p> }).collect_view()}
|
||||
</blockquote>
|
||||
{attachments}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,52 @@ impl Timeline {
|
|||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TimelineRepliesRecursive(tl: Timeline, root: String) -> impl IntoView {
|
||||
let root_values = move || tl.feed
|
||||
.get()
|
||||
.into_iter()
|
||||
.filter_map(|x| CACHE.get(&x))
|
||||
.filter(|x| x.object().get().map(|o| o.in_reply_to().id().map(|r| r == root).unwrap_or(false)).unwrap_or(false))
|
||||
.collect::<Vec<serde_json::Value>>();
|
||||
|
||||
view! {
|
||||
<For
|
||||
each=move || root_values()
|
||||
key=|k| k.id().unwrap_or_default().to_string()
|
||||
children=move |object: serde_json::Value| {
|
||||
let oid = object.id().unwrap_or_default().to_string();
|
||||
view! {
|
||||
<Object object=object />
|
||||
<TimelineRepliesRecursive tl=tl root=oid />
|
||||
}
|
||||
}
|
||||
/ >
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TimelineReplies(tl: Timeline, root: String) -> impl IntoView {
|
||||
let auth = use_context::<Auth>().expect("missing auth context");
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<TimelineRepliesRecursive tl=tl root=root />
|
||||
</div>
|
||||
<div class="center mt-1 mb-1" >
|
||||
<button type="button"
|
||||
on:click=move |_| {
|
||||
spawn_local(async move {
|
||||
if let Err(e) = tl.more(auth).await {
|
||||
tracing::error!("error fetching more items for timeline: {e}");
|
||||
}
|
||||
})
|
||||
}
|
||||
>more</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
||||
let auth = use_context::<Auth>().expect("missing auth context");
|
||||
|
@ -57,7 +103,7 @@ pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
|||
Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => {
|
||||
let object_id = item.object().id().unwrap_or_default();
|
||||
let object = CACHE.get(&object_id).map(|obj| {
|
||||
view! { <ObjectInline object=obj /> }
|
||||
view! { <Object object=obj /> }
|
||||
});
|
||||
view! {
|
||||
<ActivityLine activity=item />
|
||||
|
|
|
@ -162,7 +162,7 @@ pub fn ObjectPage(tl: Timeline) -> impl IntoView {
|
|||
view!{
|
||||
<Object object=object />
|
||||
<div class="ml-1 mr-1 mt-2">
|
||||
<TimelineFeed tl=tl />
|
||||
<TimelineReplies tl=tl root=o.id().unwrap_or_default().to_string() />
|
||||
</div>
|
||||
}.into_view()
|
||||
},
|
||||
|
@ -203,6 +203,7 @@ pub fn DebugPage() -> impl IntoView {
|
|||
let (object, set_object) = create_signal(serde_json::Value::String(
|
||||
"use this view to fetch remote AP objects and inspect their content".into())
|
||||
);
|
||||
let cached_ref: NodeRef<html::Input> = create_node_ref();
|
||||
let auth = use_context::<Auth>().expect("missing auth context");
|
||||
view! {
|
||||
<div>
|
||||
|
@ -210,19 +211,28 @@ pub fn DebugPage() -> impl IntoView {
|
|||
<div class="mt-1" >
|
||||
<form on:submit=move|ev| {
|
||||
ev.prevent_default();
|
||||
let cached = cached_ref.get().map(|x| x.checked()).unwrap_or_default();
|
||||
let fetch_url = url_ref.get().map(|x| x.value()).unwrap_or("".into());
|
||||
let url = format!("{URL_BASE}/dbg?id={fetch_url}");
|
||||
spawn_local(async move {
|
||||
match Http::fetch::<serde_json::Value>(&url, auth).await {
|
||||
Ok(x) => set_object.set(x),
|
||||
Err(e) => set_object.set(serde_json::Value::String(e.to_string())),
|
||||
if cached {
|
||||
match CACHE.get(&fetch_url) {
|
||||
Some(x) => set_object.set(x),
|
||||
None => set_object.set(serde_json::Value::String("not in cache!".into())),
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let url = format!("{URL_BASE}/dbg?id={fetch_url}");
|
||||
spawn_local(async move {
|
||||
match Http::fetch::<serde_json::Value>(&url, auth).await {
|
||||
Ok(x) => set_object.set(x),
|
||||
Err(e) => set_object.set(serde_json::Value::String(e.to_string())),
|
||||
}
|
||||
});
|
||||
}
|
||||
} >
|
||||
<table class="align w-100" >
|
||||
<tr>
|
||||
<td><input class="w-100" type="text" node_ref=url_ref placeholder="AP id" /></td>
|
||||
<td><input type="submit" class="w-100" value="fetch" /></td>
|
||||
<td><input type="checkbox" title="cached" value="cached" node_ref=cached_ref /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue