Compare commits

...

3 commits

Author SHA1 Message Date
48768b0e1a
fix: oops forgot extra argument here
this is really ugly and unintuitive but im working on builder patterns
directly in apb so this should get refactored away too soon™️
2024-11-09 13:33:34 +01:00
5e32959c61
feat(web): replies filter changes urls
pretty ugly way to do this but ehh im going to refactor this anyway
soon™️
2024-11-09 13:32:53 +01:00
aaed94d2f8
fix: include replies choice in next page urls 2024-11-09 13:18:19 +01:00
11 changed files with 49 additions and 27 deletions

View file

@ -119,6 +119,7 @@ pub async fn page<const OUTGOING: bool>(
crate::builders::collection_page( crate::builders::collection_page(
&upub::url!(ctx, "/actors/{id}/{follow___}/page"), &upub::url!(ctx, "/actors/{id}/{follow___}/page"),
offset, limit, offset, limit,
following.into_iter().map(serde_json::Value::String).collect() following.into_iter().map(serde_json::Value::String).collect(),
true
) )
} }

View file

@ -52,6 +52,6 @@ pub async fn page(
.map(|x| x.ap()) .map(|x| x.ap())
.collect(); .collect();
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/notifications/page"), offset, limit, activities) crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/notifications/page"), offset, limit, activities, true)
} }

View file

@ -51,5 +51,5 @@ pub async fn page(
.map(|item| item.ap()) .map(|item| item.ap())
.collect(); .collect();
crate::builders::collection_page(&id, offset, limit, items) crate::builders::collection_page(&id, offset, limit, items, true)
} }

View file

@ -47,6 +47,7 @@ pub async fn page(
offset, offset,
limit, limit,
objects, objects,
true,
) )
} }

View file

@ -21,7 +21,9 @@ pub async fn paginate_feed(
.add(filter); .add(filter);
// by default we want replies because servers don't know about our api and want everything // by default we want replies because servers don't know about our api and want everything
if !page.replies.unwrap_or(true) { let replies = page.replies.unwrap_or(true);
if !replies {
conditions = conditions.add(upub::model::object::Column::InReplyTo.is_null()); conditions = conditions.add(upub::model::object::Column::InReplyTo.is_null());
} }
@ -52,18 +54,19 @@ pub async fn paginate_feed(
.map(|item| item.ap()) .map(|item| item.ap())
.collect(); .collect();
collection_page(&id, offset, limit, items) collection_page(&id, offset, limit, items, replies)
} }
pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> crate::ApiResult<JsonLD<serde_json::Value>> { pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>, replies: bool) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let replies = if replies { "" } else { "&replies=false" };
let next = if items.len() < limit as usize { let next = if items.len() < limit as usize {
apb::Node::Empty apb::Node::Empty
} else { } else {
apb::Node::link(format!("{id}?offset={}", offset+limit)) apb::Node::link(format!("{id}?offset={}{replies}", offset+limit))
}; };
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(&format!("{id}?offset={offset}"))) .set_id(Some(&format!("{id}?offset={offset}{replies}")))
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage)) .set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
.set_part_of(apb::Node::link(id.replace("/page", ""))) .set_part_of(apb::Node::link(id.replace("/page", "")))
.set_ordered_items(apb::Node::array(items)) .set_ordered_items(apb::Node::array(items))

View file

@ -193,6 +193,7 @@ fn Scrollable() -> impl IntoView {
let location = use_location(); let location = use_location();
let feeds = use_context::<Feeds>().expect("missing feeds context"); let feeds = use_context::<Feeds>().expect("missing feeds context");
let auth = use_context::<Auth>().expect("missing auth context"); let auth = use_context::<Auth>().expect("missing auth context");
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let relevant_timeline = Signal::derive(move || { let relevant_timeline = Signal::derive(move || {
let path = location.pathname.get(); let path = location.pathname.get();
if path.contains("/web/home") { if path.contains("/web/home") {
@ -246,7 +247,7 @@ fn Scrollable() -> impl IntoView {
<a class="breadcrumb mr-1" href="javascript:history.back()" ><b>"<<"</b></a> <a class="breadcrumb mr-1" href="javascript:history.back()" ><b>"<<"</b></a>
<b>{crate::NAME}</b>" :: "{breadcrumb} <b>{crate::NAME}</b>" :: "{breadcrumb}
{move || relevant_timeline.get().map(|tl| view! { {move || relevant_timeline.get().map(|tl| view! {
<a class="breadcrumb ml-1" href="#" on:click=move|_| tl.refresh(auth) ><b>""</b></a> <a class="breadcrumb ml-1" href="#" on:click=move|_| tl.refresh(auth, config) ><b>""</b></a>
})} })}
</div> </div>
<Outlet /> <Outlet />

View file

@ -7,6 +7,7 @@ pub fn LoginBox(
userid_tx: WriteSignal<Option<String>>, userid_tx: WriteSignal<Option<String>>,
) -> impl IntoView { ) -> impl IntoView {
let auth = use_context::<Auth>().expect("missing auth context"); let auth = use_context::<Auth>().expect("missing auth context");
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let username_ref: NodeRef<html::Input> = create_node_ref(); let username_ref: NodeRef<html::Input> = create_node_ref();
let password_ref: NodeRef<html::Input> = create_node_ref(); let password_ref: NodeRef<html::Input> = create_node_ref();
let feeds = use_context::<Feeds>().expect("missing feeds context"); let feeds = use_context::<Feeds>().expect("missing feeds context");
@ -17,8 +18,8 @@ pub fn LoginBox(
<input style="float:right" type="submit" value="logout" on:click=move |_| { <input style="float:right" type="submit" value="logout" on:click=move |_| {
token_tx.set(None); token_tx.set(None);
feeds.reset(); feeds.reset();
feeds.global.spawn_more(auth); feeds.global.spawn_more(auth, config);
feeds.server.spawn_more(auth); feeds.server.spawn_more(auth, config);
} /> } />
</div> </div>
<div class:hidden=move || auth.present() > <div class:hidden=move || auth.present() >
@ -45,14 +46,14 @@ pub fn LoginBox(
token_tx.set(Some(auth_response.token)); token_tx.set(Some(auth_response.token));
// reset home feed and point it to our user's inbox // reset home feed and point it to our user's inbox
feeds.home.reset(Some(format!("{URL_BASE}/actors/{username}/inbox/page"))); feeds.home.reset(Some(format!("{URL_BASE}/actors/{username}/inbox/page")));
feeds.home.spawn_more(auth); feeds.home.spawn_more(auth, config);
feeds.notifications.reset(Some(format!("{URL_BASE}/actors/{username}/notifications/page"))); feeds.notifications.reset(Some(format!("{URL_BASE}/actors/{username}/notifications/page")));
feeds.notifications.spawn_more(auth); feeds.notifications.spawn_more(auth, config);
// reset server feed: there may be more content now that we're authed // reset server feed: there may be more content now that we're authed
feeds.global.reset(Some(format!("{URL_BASE}/inbox/page"))); feeds.global.reset(Some(format!("{URL_BASE}/inbox/page")));
feeds.global.spawn_more(auth); feeds.global.spawn_more(auth, config);
feeds.server.reset(Some(format!("{URL_BASE}/outbox/page"))); feeds.server.reset(Some(format!("{URL_BASE}/outbox/page")));
feeds.server.spawn_more(auth); feeds.server.spawn_more(auth, config);
}); });
} > } >
<table class="w-100 align"> <table class="w-100 align">

View file

@ -6,6 +6,7 @@ use crate::{prelude::*, DEFAULT_COLOR};
pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView { pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
let config = use_context::<Signal<crate::Config>>().expect("missing config context"); let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let auth = use_context::<Auth>().expect("missing auth context"); let auth = use_context::<Auth>().expect("missing auth context");
let feeds = use_context::<Feeds>().expect("missing feeds context");
let (color, set_color) = leptos_use::use_css_var("--accent"); let (color, set_color) = leptos_use::use_css_var("--accent");
let (_color_rgb, set_color_rgb) = leptos_use::use_css_var("--accent-rgb"); let (_color_rgb, set_color_rgb) = leptos_use::use_css_var("--accent-rgb");
@ -97,7 +98,12 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
<hr /> <hr />
<p><code title="unchecked elements won't show in timelines">filters</code></p> <p><code title="unchecked elements won't show in timelines">filters</code></p>
<ul> <ul>
<li><span title="replies to other posts"><input type="checkbox" prop:checked=get_cfg!(filter replies) on:input=set_cfg!(filter replies) />" replies"</span></li> <li><span title="replies to other posts"><input type="checkbox" prop:checked=get_cfg!(filter replies) on:input=move |ev| {
let mut mock = config.get();
mock.filters.replies = event_target_checked(&ev);
setter.set(mock);
feeds.reset();
}/>" replies"</span></li>
<li><span title="like activities"><input type="checkbox" prop:checked=get_cfg!(filter likes) on:input=set_cfg!(filter likes) />" likes"</span></li> <li><span title="like activities"><input type="checkbox" prop:checked=get_cfg!(filter likes) on:input=set_cfg!(filter likes) />" likes"</span></li>
<li><span title="create activities with object"><input type="checkbox" prop:checked=get_cfg!(filter creates) on:input=set_cfg!(filter creates)/>" creates"</span></li> <li><span title="create activities with object"><input type="checkbox" prop:checked=get_cfg!(filter creates) on:input=set_cfg!(filter creates)/>" creates"</span></li>
<li><span title="update activities, to objects or actors"><input type="checkbox" prop:checked=get_cfg!(filter updates) on:input=set_cfg!(filter updates)/>" updates"</span></li> <li><span title="update activities, to objects or actors"><input type="checkbox" prop:checked=get_cfg!(filter updates) on:input=set_cfg!(filter updates)/>" updates"</span></li>

View file

@ -10,10 +10,11 @@ pub fn Feed(
ignore_filters: bool, ignore_filters: bool,
) -> impl IntoView { ) -> impl IntoView {
let auth = use_context::<Auth>().expect("missing auth context"); let auth = use_context::<Auth>().expect("missing auth context");
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
if let Some(auto_scroll) = use_context::<Signal<bool>>() { if let Some(auto_scroll) = use_context::<Signal<bool>>() {
let _ = leptos::watch( let _ = leptos::watch(
move || auto_scroll.get(), move || auto_scroll.get(),
move |at_end, _, _| if *at_end { tl.spawn_more(auth) }, move |at_end, _, _| if *at_end { tl.spawn_more(auth, config) },
true, true,
); );
} }

View file

@ -40,7 +40,7 @@ impl Timeline {
} }
} }
pub fn refresh(&self, auth: Auth) { pub fn refresh(&self, auth: Auth, config: Signal<crate::Config>) {
self.reset( self.reset(
self.next self.next
.get_untracked() .get_untracked()
@ -48,13 +48,13 @@ impl Timeline {
.next() .next()
.map(|x| x.to_string()) .map(|x| x.to_string())
); );
self.spawn_more(auth); self.spawn_more(auth, config);
} }
pub fn spawn_more(&self, auth: Auth) { pub fn spawn_more(&self, auth: Auth, config: Signal<crate::Config>) {
let _self = *self; let _self = *self;
spawn_local(async move { spawn_local(async move {
_self.more(auth).await _self.more(auth, config).await
}); });
} }
@ -62,21 +62,28 @@ impl Timeline {
self.loading.get_untracked() self.loading.get_untracked()
} }
pub async fn more(&self, auth: Auth) { pub async fn more(&self, auth: Auth, config: Signal<crate::Config>) {
if self.loading.get_untracked() { return } if self.loading.get_untracked() { return }
if self.over.get_untracked() { return } if self.over.get_untracked() { return }
self.loading.set(true); self.loading.set(true);
let res = self.load_more(auth).await; let res = self.load_more(auth, config).await;
self.loading.set(false); self.loading.set(false);
if let Err(e) = res { if let Err(e) = res {
tracing::error!("failed loading posts for timeline: {e}"); tracing::error!("failed loading posts for timeline: {e}");
} }
} }
pub async fn load_more(&self, auth: Auth) -> reqwest::Result<()> { pub async fn load_more(&self, auth: Auth, config: Signal<crate::Config>) -> reqwest::Result<()> {
use apb::{Collection, CollectionPage}; use apb::{Collection, CollectionPage};
let feed_url = self.next.get_untracked(); let mut feed_url = self.next.get_untracked();
if !config.get_untracked().filters.replies {
feed_url = if feed_url.contains('?') {
feed_url + "&replies=false"
} else {
feed_url + "?replies=false"
};
}
let collection : serde_json::Value = Http::fetch(&feed_url, auth).await?; let collection : serde_json::Value = Http::fetch(&feed_url, auth).await?;
let activities : Vec<serde_json::Value> = collection let activities : Vec<serde_json::Value> = collection
.ordered_items() .ordered_items()

View file

@ -6,14 +6,15 @@ use super::Timeline;
#[component] #[component]
pub fn Thread(tl: Timeline, root: String) -> impl IntoView { pub fn Thread(tl: Timeline, root: String) -> impl IntoView {
let auth = use_context::<Auth>().expect("missing auth context"); let auth = use_context::<Auth>().expect("missing auth context");
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
if let Some(auto_scroll) = use_context::<Signal<bool>>() { if let Some(auto_scroll) = use_context::<Signal<bool>>() {
let _ = leptos::watch( let _ = leptos::watch(
move || auto_scroll.get(), move || auto_scroll.get(),
move |new, old, _| { move |new, old, _| {
match old { match old {
None => tl.spawn_more(auth), // always do it first time None => tl.spawn_more(auth, config), // always do it first time
Some(old) => if *new && new != old { Some(old) => if *new && new != old {
tl.spawn_more(auth); tl.spawn_more(auth, config);
}, },
} }
}, },