forked from alemi/upub
feat(web): show posts inline, better activity ui
This commit is contained in:
parent
3fde41eb97
commit
764f810ff9
3 changed files with 80 additions and 53 deletions
|
@ -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%;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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! {
|
||||||
|
<div class="tl-header w-100 center mb-s ml-1" >view::object</div>
|
||||||
|
<div class="ma-2" >
|
||||||
{move || match object.get() {
|
{move || match object.get() {
|
||||||
Some(Some(o)) => view!{ <Object object=o /> }.into_view(),
|
Some(Some(o)) => view!{ <Object object=o /> }.into_view(),
|
||||||
Some(None) => view! { <p><code>loading failed</code></p> }.into_view(),
|
Some(None) => view! { <p><code>loading failed</code></p> }.into_view(),
|
||||||
None => view! { <p> loading ... </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" >{
|
||||||
|
content.into_iter().map(|x| view! { <p>{x}</p> }).collect_view()
|
||||||
|
}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="post-table" >
|
||||||
<td>{date}</td>
|
<td class="post-table pa-1" ><ActorBanner object=author /></td>
|
||||||
|
<td class="post-table pa-1" >{date}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</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()
|
||||||
|
|
Loading…
Reference in a new issue