feat(web): mentions, dynamically resolved and shown

This commit is contained in:
əlemi 2024-05-23 16:41:45 +02:00
parent 17f77c1769
commit b9a25bc3d7
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 48 additions and 8 deletions

View file

@ -34,4 +34,5 @@ lazy_static = "1.4"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
web-sys = { version = "0.3", features = ["Screen"] } web-sys = { version = "0.3", features = ["Screen"] }
mdhtml = { path = "../mdhtml/" } mdhtml = { path = "../mdhtml/" }
jrd = "0.1.0" jrd = "0.1"
tld = "2.35"

View file

@ -1,7 +1,8 @@
use apb::{ActivityMut, Base, BaseMut, Object, ObjectMut}; use apb::{ActivityMut, Base, BaseMut, Object, ObjectMut};
use leptos::*; use leptos::*;
use crate::prelude::*; use leptos_use::DebounceOptions;
use crate::{prelude::*, WEBFINGER};
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct ReplyControls { pub struct ReplyControls {
@ -34,11 +35,34 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
let reply = use_context::<ReplyControls>().expect("missing reply controls"); let reply = use_context::<ReplyControls>().expect("missing reply controls");
let (posting, set_posting) = create_signal(false); let (posting, set_posting) = create_signal(false);
let (error, set_error) = create_signal(None); let (error, set_error) = create_signal(None);
let (content, set_content) = create_signal("".to_string());
let summary_ref: NodeRef<html::Input> = create_node_ref(); let summary_ref: NodeRef<html::Input> = create_node_ref();
let content_ref: NodeRef<html::Textarea> = create_node_ref();
let public_ref: NodeRef<html::Input> = create_node_ref(); let public_ref: NodeRef<html::Input> = create_node_ref();
let followers_ref: NodeRef<html::Input> = create_node_ref(); let followers_ref: NodeRef<html::Input> = create_node_ref();
let private_ref: NodeRef<html::Input> = create_node_ref(); let private_ref: NodeRef<html::Input> = create_node_ref();
// TODO is this too abusive with resources? im even checking if TLD exists...
let mentions = create_local_resource(
move || content.get(),
move |c| async move {
let mut out = Vec::new();
for word in c.split(' ') {
if !word.starts_with('@') { break };
let stripped = word.replacen('@', "", 1);
if let Some((user, domain)) = stripped.split_once('@') {
if let Some(tld) = domain.split('.').last() {
if tld::exist(tld) {
if let Some(uid) = WEBFINGER.blocking_resolve(user, domain).await {
out.push(uid);
}
}
}
}
}
out
},
);
view! { view! {
<div> <div>
{move || {move ||
@ -51,13 +75,22 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
on:click=move|_| reply.clear() on:click=move|_| reply.clear()
title={format!("> {r} | ctx: {}", reply.context.get().unwrap_or_default())} title={format!("> {r} | ctx: {}", reply.context.get().unwrap_or_default())}
> >
"📨" "✒️"
</span> </span>
<small>{actor_strip}</small> {actor_strip}
<small class="tiny ml-1">"["<a class="clean" title="remove reply" href="#" on:click=move |_| reply.clear() >reply</a>"]"</small>
</span> </span>
} }
}) })
} }
{move ||
mentions.get()
.map(|x| x.into_iter().map(|u| match CACHE.get(&u) {
Some(u) => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><ActorStrip object=u /></span> }.into_view(),
None => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><a href={Uri::web(U::User, &u)}>{u}</a></span> }.into_view(),
})
.collect_view())
}
<table class="align w-100"> <table class="align w-100">
<tr> <tr>
<td><input type="checkbox" on:input=move |ev| advanced.set(event_target_checked(&ev)) title="toggle advanced controls" /></td> <td><input type="checkbox" on:input=move |ev| advanced.set(event_target_checked(&ev)) title="toggle advanced controls" /></td>
@ -65,7 +98,10 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
</tr> </tr>
</table> </table>
<textarea rows="6" class="w-100" node_ref=content_ref title="content" placeholder="\n look at nothing\n what do you see?" ></textarea> <textarea rows="6" class="w-100" title="content" placeholder="\n look at nothing\n what do you see?"
prop:value=content
on:input=move |ev| set_content.set(event_target_value(&ev))
></textarea>
<table class="align rev w-100"> <table class="align rev w-100">
<tr> <tr>
@ -76,7 +112,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
set_posting.set(true); set_posting.set(true);
spawn_local(async move { spawn_local(async move {
let summary = get_if_some(summary_ref); let summary = get_if_some(summary_ref);
let content = content_ref.get().map(|x| x.value()).unwrap_or_default(); let content = content.get();
let mut cc_vec = Vec::new(); let mut cc_vec = Vec::new();
let mut to_vec = Vec::new(); let mut to_vec = Vec::new();
if get_checked(followers_ref) { if get_checked(followers_ref) {
@ -92,6 +128,9 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
} }
} }
} }
for mention in mentions.get().as_deref().unwrap_or(&[]) {
to_vec.push(mention.to_string());
}
let payload = serde_json::Value::Object(serde_json::Map::default()) let payload = serde_json::Value::Object(serde_json::Map::default())
.set_object_type(Some(apb::ObjectType::Note)) .set_object_type(Some(apb::ObjectType::Note))
.set_summary(summary.as_deref()) .set_summary(summary.as_deref())
@ -105,7 +144,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
Ok(()) => { Ok(()) => {
set_error.set(None); set_error.set(None);
if let Some(x) = summary_ref.get() { x.set_value("") } if let Some(x) = summary_ref.get() { x.set_value("") }
if let Some(x) = content_ref.get() { x.set_value("") } set_content.set("".to_string());
}, },
} }
set_posting.set(false); set_posting.set(false);