1
0
Fork 0
forked from alemi/upub

feat(web): show posts inline, better activity ui

This commit is contained in:
əlemi 2024-04-16 07:34:16 +02:00
parent 3fde41eb97
commit 764f810ff9
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 80 additions and 53 deletions

View file

@ -25,6 +25,9 @@
a.upub-title:hover { a.upub-title:hover {
text-decoration: underline; text-decoration: underline;
} }
a.hover:hover {
text-decoration: underline;
}
img.avatar-circle { img.avatar-circle {
display: inline; display: inline;
max-height: 2em; max-height: 2em;
@ -38,6 +41,15 @@
background-color: #bf616a55; background-color: #bf616a55;
color: #bf616a; color: #bf616a;
} }
table.post-table {
border-collapse: collapse;
}
tr.post-table {
border: 1px dashed #bf616a;
}
td.post-table {
border: 1px dashed #bf616a;
}
@media screen and (max-width: 786px) { @media screen and (max-width: 786px) {
div.boxscroll { div.boxscroll {
max-height: 100%; max-height: 100%;

View file

@ -35,6 +35,14 @@ impl Uri {
} }
} }
pub fn pretty(url: &str) -> String {
if url.len() < 50 {
url.replace("https://", "")
} else {
format!("{}..", url.replace("https://", "").get(..50).unwrap_or_default().to_string())
}.replace('/', "/")
}
pub fn short(url: &str) -> String { pub fn short(url: &str) -> String {
if url.starts_with(URL_BASE) { if url.starts_with(URL_BASE) {
url.split('/').last().unwrap_or_default().to_string() url.split('/').last().unwrap_or_default().to_string()

View file

@ -213,6 +213,7 @@ pub fn UserPage() -> impl IntoView {
} }
}); });
view! { view! {
<div class="tl-header w-100 center mb-s ml-1" >view::user</div>
{move || match actor.get() { {move || match actor.get() {
None => view! { <p>loading...</p> }.into_view(), None => view! { <p>loading...</p> }.into_view(),
Some(None) => view! { <p><code>error loading</code></p> }.into_view(), Some(None) => view! { <p><code>error loading</code></p> }.into_view(),
@ -257,47 +258,55 @@ pub fn ObjectPage() -> impl IntoView {
} }
}); });
view! { view! {
{move || match object.get() { <div class="tl-header w-100 center mb-s ml-1" >view::object</div>
Some(Some(o)) => view!{ <Object object=o /> }.into_view(), <div class="ma-2" >
Some(None) => view! { <p><code>loading failed</code></p> }.into_view(), {move || match object.get() {
None => view! { <p> loading ... </p> }.into_view(), Some(Some(o)) => view!{ <Object object=o /> }.into_view(),
}} Some(None) => view! { <p><code>loading failed</code></p> }.into_view(),
None => view! { <p> loading ... </p> }.into_view(),
}}
</div>
} }
} }
#[component] #[component]
pub fn Object(object: serde_json::Value) -> impl IntoView { pub fn Object(object: serde_json::Value) -> impl IntoView {
let summary = object.summary().unwrap_or_default().to_string(); let summary = object.summary().unwrap_or_default().to_string();
let content = object.content().unwrap_or_default().to_string(); let content = dissolve::strip_html_tags(object.content().unwrap_or_default());
let date = object.published().map(|x| x.to_rfc3339()).unwrap_or_default(); let date = object.published().map(|x| x.to_rfc2822()).unwrap_or_default();
let author_id = object.attributed_to().id().unwrap_or_default(); let author_id = object.attributed_to().id().unwrap_or_default();
let author = CACHE.get(&author_id).map(|x| view! { <ActorBanner object=x.clone() /> }); let author = CACHE.get(&author_id).unwrap_or(serde_json::Value::String(author_id.clone()));
view! { view! {
{author} <div>
<table> <table class="post-table pa-1 mb-s" >
<tr> <tr class="post-table" >
<td>{summary}</td> <td class="post-table pa-1" colspan="2" >{summary}</td>
</tr> </tr>
<tr> <tr class="post-table" >
<td>{content}</td> <td class="post-table pa-1" colspan="2" >{
</tr> content.into_iter().map(|x| view! { <p>{x}</p> }).collect_view()
<tr> }</td>
<td>{date}</td> </tr>
</tr> <tr class="post-table" >
</table> <td class="post-table pa-1" ><ActorBanner object=author /></td>
<td class="post-table pa-1" >{date}</td>
</tr>
</table>
</div>
} }
} }
#[component] #[component]
pub fn InlineActivity(activity: serde_json::Value) -> impl IntoView { pub fn InlineActivity(activity: serde_json::Value) -> impl IntoView {
let object = activity.clone().object().extract().unwrap_or_else(|| let object_id = activity.object().id().unwrap_or_default();
serde_json::Value::String(activity.object().id().unwrap_or_default()) let object = CACHE.get(&object_id).unwrap_or(serde_json::Value::String(object_id.clone()));
);
let object_id = object.id().unwrap_or_default().to_string();
let object_uri = Uri::web("objects", &object_id);
let content = dissolve::strip_html_tags(object.content().unwrap_or_default());
let addressed = activity.addressed(); let addressed = activity.addressed();
let audience = format!("[ {} ]", addressed.join(", ")); let audience = format!("[ {} ]", addressed.join(", "));
let actor_id = activity.actor().id().unwrap_or_default();
let actor = match CACHE.get(&actor_id) {
Some(a) => a,
None => serde_json::Value::String(actor_id.clone()),
};
let privacy = if addressed.iter().any(|x| x == apb::target::PUBLIC) { let privacy = if addressed.iter().any(|x| x == apb::target::PUBLIC) {
"[public]" "[public]"
} else if addressed.iter().any(|x| x.ends_with("/followers")) { } else if addressed.iter().any(|x| x.ends_with("/followers")) {
@ -305,32 +314,36 @@ pub fn InlineActivity(activity: serde_json::Value) -> impl IntoView {
} else { } else {
"[private]" "[private]"
}; };
let title = object.summary().unwrap_or_default().to_string(); let date = object.published().map(|x| x.to_rfc2822()).unwrap_or_else(||
let date = object.published().map(|x| x.to_rfc3339()).unwrap_or_else(|| activity.published().map(|x| x.to_rfc2822()).unwrap_or_default()
activity.published().map(|x| x.to_rfc3339()).unwrap_or_default()
); );
let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity); let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity);
view! { view! {
<div>
<table class="align w-100" >
<tr>
<td rowspan="2" >
<ActorBanner object=actor />
</td>
<td class="rev" >
<code class="color" >{kind.as_ref().to_string()}</code>
</td>
</tr>
<tr>
<td class="rev">
<a class="clean hover" href={Uri::web("objects", &object_id)} >
<small>{Uri::pretty(&object_id)}</small>
</a>
</td>
</tr>
</table>
</div>
{match kind { {match kind {
// post // post
apb::ActivityType::Create => view! { apb::ActivityType::Create => view! { <Object object=object /> }.into_view(),
<div> _ => view! {}.into_view(),
<p><i>{title}</i></p>
{
content
.into_iter()
.map(|x| view! { <p>{x}</p> }.into_view())
.collect::<Vec<View>>()
}
</div>
},
kind => view! {
<div>
<b>{kind.as_ref().to_string()}</b>" >> "<a href={object_uri}>{object_id}</a>
</div>
},
}} }}
<small><u title={audience} >{privacy}</u>" "{date}</small> <small>{date}" "<u class="moreinfo" style="float: right" title={audience} >{privacy}</u></small>
} }
} }
@ -358,15 +371,9 @@ pub fn TimelineFeed(name: &'static str, tl: Timeline) -> impl IntoView {
children=move |id: String| { children=move |id: String| {
match CACHE.get(&id) { match CACHE.get(&id) {
Some(object) => { Some(object) => {
let actor_id = object.actor().id().unwrap_or_default();
let actor = match CACHE.get(&actor_id) {
Some(a) => a,
None => serde_json::Value::String(id),
};
view! { view! {
<div class="ml-1 mr-1 mt-1"> <div class="ml-1 mr-1 mt-1">
<ActorBanner object=actor /> <InlineActivity activity=object />
<InlineActivity activity=object.clone() />
</div> </div>
<hr/ > <hr/ >
}.into_view() }.into_view()