feat: add group discovery and group preview #59
+61
-78
@@ -90,7 +90,6 @@ fn is_own_subscription(id: &SubscriptionId) -> bool {
|
|||||||
|| id.starts_with(PAGE_PREFIX)
|
|| id.starts_with(PAGE_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The group id, member count and timestamp of a group members event.
|
|
||||||
fn parse_members(event: &Event) -> Option<(GroupId, Timestamp, usize)> {
|
fn parse_members(event: &Event) -> Option<(GroupId, Timestamp, usize)> {
|
||||||
if event.kind != Kind::GroupMembers {
|
if event.kind != Kind::GroupMembers {
|
||||||
return None;
|
return None;
|
||||||
@@ -102,8 +101,6 @@ fn parse_members(event: &Event) -> Option<(GroupId, Timestamp, usize)> {
|
|||||||
Some((id, event.created_at, members))
|
Some((id, event.created_at, members))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconcile a filter with the relays via negentropy, returning the events
|
|
||||||
/// that were downloaded to the local database.
|
|
||||||
async fn reconcile(
|
async fn reconcile(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
@@ -112,22 +109,19 @@ async fn reconcile(
|
|||||||
let output = match client.sync(filter).with(urls).await {
|
let output = match client.sync(filter).with(urls).await {
|
||||||
Ok(output) => output,
|
Ok(output) => output,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: the reconciliation failed: {error}");
|
log::warn!("the reconciliation failed: {error}");
|
||||||
return HashMap::new();
|
return HashMap::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (relay, error) in &output.failed {
|
for (relay, error) in &output.failed {
|
||||||
log::debug!("nip29: syncing with {relay} failed: {error}");
|
log::debug!("syncing with {relay} failed: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
output.value.received
|
output.value.received
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pair the downloaded events with the relay that served them.
|
/// Pair the downloaded events with the relay that served them.
|
||||||
///
|
|
||||||
/// The database does not keep relay associations, so the pairing has to
|
|
||||||
/// happen while the reconciliation still knows them.
|
|
||||||
async fn pull_received(
|
async fn pull_received(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
received: HashMap<EventId, HashSet<RelayUrl>>,
|
received: HashMap<EventId, HashSet<RelayUrl>>,
|
||||||
@@ -202,16 +196,15 @@ impl GroupsRegistry {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
cx.defer(move |cx| {
|
cx.defer(move |cx| {
|
||||||
if let Err(error) = this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.handle_notifications(cx);
|
this.handle_notifications(cx);
|
||||||
this.load_cached(cx);
|
this.load_cached(cx);
|
||||||
|
|
||||||
if nostr.read(cx).current_user().is_some() {
|
if nostr.read(cx).current_user().is_some() {
|
||||||
this.load(cx);
|
this.load(cx);
|
||||||
}
|
}
|
||||||
}) {
|
})
|
||||||
log::warn!("nip29: initializing the registry failed: {error}");
|
.ok();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -278,7 +271,7 @@ impl GroupsRegistry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: marking the refused join failed: {error}");
|
log::warn!("marking the refused join failed: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -337,11 +330,11 @@ impl GroupsRegistry {
|
|||||||
.close_on(opts)
|
.close_on(opts)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::warn!("nip29: browsing {relay} failed: {error}");
|
log::warn!("browsing {relay} failed: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(error) = tx.send_async(Signal::BrowseSettled(id)).await {
|
if let Err(error) = tx.send_async(Signal::BrowseSettled(id)).await {
|
||||||
log::warn!("nip29: the browse settle signal was dropped: {error}");
|
log::warn!("the browse settle signal was dropped: {error}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -405,7 +398,7 @@ impl GroupsRegistry {
|
|||||||
let (authors, lists) = match task.await {
|
let (authors, lists) = match task.await {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: loading the cached groups failed: {error}");
|
log::warn!("loading the cached groups failed: {error}");
|
||||||
(HashSet::new(), Vec::new())
|
(HashSet::new(), Vec::new())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -413,21 +406,20 @@ impl GroupsRegistry {
|
|||||||
if let Err(error) = this.update(cx, |this, cx| {
|
if let Err(error) = this.update(cx, |this, cx| {
|
||||||
this.absorb_lists(lists, cx);
|
this.absorb_lists(lists, cx);
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: applying the cached group lists failed: {error}");
|
log::warn!("applying the cached group lists failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the cached metadata before the relays answer.
|
|
||||||
let previews = match this.update(cx, |this, cx| this.load_cached_previews(cx)) {
|
let previews = match this.update(cx, |this, cx| this.load_cached_previews(cx)) {
|
||||||
Ok(task) => match task.await {
|
Ok(task) => match task.await {
|
||||||
Ok(previews) => previews,
|
Ok(previews) => previews,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: loading the cached group metadata failed: {error}");
|
log::warn!("loading the cached group metadata failed: {error}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -437,12 +429,12 @@ impl GroupsRegistry {
|
|||||||
Ok(task) => match task.await {
|
Ok(task) => match task.await {
|
||||||
Ok(members) => members,
|
Ok(members) => members,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: loading the cached group members failed: {error}");
|
log::warn!("loading the cached group members failed: {error}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -451,16 +443,14 @@ impl GroupsRegistry {
|
|||||||
this.absorb_cached_previews(previews, cx);
|
this.absorb_cached_previews(previews, cx);
|
||||||
this.absorb_cached_members(members, cx);
|
this.absorb_cached_members(members, cx);
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconcile the contacts' group lists with the pool relays: only
|
|
||||||
// the events missing locally are downloaded and stored.
|
|
||||||
let lists = match this.update(cx, |this, cx| this.sync_lists(authors, cx)) {
|
let lists = match this.update(cx, |this, cx| this.sync_lists(authors, cx)) {
|
||||||
Ok(task) => task,
|
Ok(task) => task,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -469,7 +459,7 @@ impl GroupsRegistry {
|
|||||||
let sweep = match this.update(cx, |this, cx| this.sync_sweep(cx)) {
|
let sweep = match this.update(cx, |this, cx| this.sync_sweep(cx)) {
|
||||||
Ok(task) => task,
|
Ok(task) => task,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -477,7 +467,7 @@ impl GroupsRegistry {
|
|||||||
let lists = match lists.await {
|
let lists = match lists.await {
|
||||||
Ok(lists) => lists,
|
Ok(lists) => lists,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: syncing the contacts group lists failed: {error}");
|
log::warn!("syncing the contacts group lists failed: {error}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -485,13 +475,13 @@ impl GroupsRegistry {
|
|||||||
if let Err(error) = this.update(cx, |this, cx| {
|
if let Err(error) = this.update(cx, |this, cx| {
|
||||||
this.absorb_lists(lists, cx);
|
this.absorb_lists(lists, cx);
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: applying the contacts group lists failed: {error}");
|
log::warn!("applying the contacts group lists failed: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let sweep = match sweep.await {
|
let sweep = match sweep.await {
|
||||||
Ok(events) => events,
|
Ok(events) => events,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: syncing the discovered groups failed: {error}");
|
log::warn!("syncing the discovered groups failed: {error}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -501,7 +491,7 @@ impl GroupsRegistry {
|
|||||||
this.absorb_discovered(relay, &event, cx);
|
this.absorb_discovered(relay, &event, cx);
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: applying the discovered groups failed: {error}");
|
log::warn!("applying the discovered groups failed: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The previews cover the relays the lists just referenced.
|
// The previews cover the relays the lists just referenced.
|
||||||
@@ -509,12 +499,12 @@ impl GroupsRegistry {
|
|||||||
Ok(task) => match task.await {
|
Ok(task) => match task.await {
|
||||||
Ok(previews) => previews,
|
Ok(previews) => previews,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: syncing the group previews failed: {error}");
|
log::warn!("syncing the group previews failed: {error}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -527,7 +517,7 @@ impl GroupsRegistry {
|
|||||||
this.discovery
|
this.discovery
|
||||||
.update(cx, |discovery, cx| discovery.settle(cx));
|
.update(cx, |discovery, cx| discovery.settle(cx));
|
||||||
}) {
|
}) {
|
||||||
log::warn!("nip29: discovering the groups failed: {error}");
|
log::warn!("discovering the groups failed: {error}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -560,7 +550,7 @@ impl GroupsRegistry {
|
|||||||
let metadata = match GroupMetadata::parse(&event) {
|
let metadata = match GroupMetadata::parse(&event) {
|
||||||
Ok(metadata) => metadata,
|
Ok(metadata) => metadata,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: ignoring malformed cached group metadata: {error}");
|
log::warn!("ignoring malformed cached group metadata: {error}");
|
||||||
return newest;
|
return newest;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -717,7 +707,7 @@ impl GroupsRegistry {
|
|||||||
let metadata = match GroupMetadata::parse(event) {
|
let metadata = match GroupMetadata::parse(event) {
|
||||||
Ok(metadata) => metadata,
|
Ok(metadata) => metadata,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: ignoring malformed group metadata from {relay}: {error}");
|
log::warn!("ignoring malformed group metadata from {relay}: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -762,7 +752,7 @@ impl GroupsRegistry {
|
|||||||
|
|
||||||
async move {
|
async move {
|
||||||
if let Err(error) = client.add_relay(relay.clone()).and_connect().await {
|
if let Err(error) = client.add_relay(relay.clone()).and_connect().await {
|
||||||
log::warn!("nip29: connecting to {relay} failed: {error}");
|
log::warn!("connecting to {relay} failed: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -811,10 +801,7 @@ impl GroupsRegistry {
|
|||||||
|
|
||||||
fn absorb_group_list(&mut self, event: &Event, cx: &mut Context<Self>) {
|
fn absorb_group_list(&mut self, event: &Event, cx: &mut Context<Self>) {
|
||||||
let Ok(list) = GroupList::parse(event) else {
|
let Ok(list) = GroupList::parse(event) else {
|
||||||
log::warn!(
|
log::warn!("ignoring a malformed group list from {}", event.pubkey);
|
||||||
"nip29: ignoring a malformed group list from {}",
|
|
||||||
event.pubkey
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -861,7 +848,7 @@ impl GroupsRegistry {
|
|||||||
let metadata = match GroupMetadata::parse(event) {
|
let metadata = match GroupMetadata::parse(event) {
|
||||||
Ok(metadata) => metadata,
|
Ok(metadata) => metadata,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: ignoring malformed group metadata from {relay}: {error}");
|
log::warn!("ignoring malformed group metadata from {relay}: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -925,7 +912,7 @@ impl GroupsRegistry {
|
|||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if let Err(error) = sent.await {
|
if let Err(error) = sent.await {
|
||||||
log::warn!("nip29: leaving {} failed: {error}", key.id());
|
log::warn!("leaving {} failed: {error}", key.id());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
@@ -963,7 +950,7 @@ impl GroupsRegistry {
|
|||||||
self.tasks.push(cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if let Err(error) = client.unsubscribe(&id).await {
|
if let Err(error) = client.unsubscribe(&id).await {
|
||||||
log::warn!("nip29: unsubscribing {id} failed: {error}");
|
log::warn!("unsubscribing {id} failed: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -995,11 +982,14 @@ impl GroupsRegistry {
|
|||||||
self.tasks
|
self.tasks
|
||||||
.push(cx.spawn(async move |this, cx| match task.await {
|
.push(cx.spawn(async move |this, cx| match task.await {
|
||||||
Ok(list) => {
|
Ok(list) => {
|
||||||
if let Err(error) = this.update(cx, |this, cx| this.merge(list, cx)) {
|
this.update(cx, |this, cx| {
|
||||||
log::warn!("nip29: applying the cached group list failed: {error}");
|
this.merge(list, cx);
|
||||||
}
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("loading the cached group list failed: {error}");
|
||||||
}
|
}
|
||||||
Err(error) => log::warn!("nip29: loading the cached group list failed: {error}"),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,7 +998,7 @@ impl GroupsRegistry {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
let updates: Task<Result<(), Error>> = cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let me = signer.get_public_key_async().await?;
|
let me = signer.get_public_key_async().await?;
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let filter = Filter::new().author(me).kind(Kind::SimpleGroups).limit(1);
|
let filter = Filter::new().author(me).kind(Kind::SimpleGroups).limit(1);
|
||||||
@@ -1019,14 +1009,9 @@ impl GroupsRegistry {
|
|||||||
.close_on(opts)
|
.close_on(opts)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok::<(), anyhow::Error>(())
|
||||||
});
|
})
|
||||||
|
.detach();
|
||||||
self.tasks.push(cx.spawn(async move |_this, _cx| {
|
|
||||||
if let Err(error) = updates.await {
|
|
||||||
log::warn!("nip29: subscribing to the group list failed: {error}");
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.watch_forks(cx);
|
self.watch_forks(cx);
|
||||||
}
|
}
|
||||||
@@ -1050,7 +1035,6 @@ impl GroupsRegistry {
|
|||||||
self.tasks.push(cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
executor.timer(FORK_INTERVAL).await;
|
executor.timer(FORK_INTERVAL).await;
|
||||||
|
|
||||||
if tx.send_async(Signal::Forks).await.is_err() {
|
if tx.send_async(Signal::Forks).await.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1091,14 +1075,14 @@ impl GroupsRegistry {
|
|||||||
.with_id(fork_subscription_id())
|
.with_id(fork_subscription_id())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::warn!("nip29: subscribing to the admins' group lists failed: {error}");
|
log::warn!("subscribing to the admins' group lists failed: {error}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn absorb_fork_list(&mut self, event: &Event, cx: &mut Context<Self>) {
|
fn absorb_fork_list(&mut self, event: &Event, cx: &mut Context<Self>) {
|
||||||
let Ok(list) = GroupList::parse(event) else {
|
let Ok(list) = GroupList::parse(event) else {
|
||||||
log::warn!("nip29: ignoring a malformed admin group list");
|
log::warn!("ignoring a malformed admin group list");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1201,7 +1185,7 @@ impl GroupsRegistry {
|
|||||||
self.tasks.push(cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
for id in [id, page] {
|
for id in [id, page] {
|
||||||
if let Err(error) = client.unsubscribe(&id).await {
|
if let Err(error) = client.unsubscribe(&id).await {
|
||||||
log::warn!("nip29: unsubscribing {id} failed: {error}");
|
log::warn!("unsubscribing {id} failed: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,7 +1194,7 @@ impl GroupsRegistry {
|
|||||||
.custom_tag(SingleLetterTag::LOWERCASE_R, cache_tag);
|
.custom_tag(SingleLetterTag::LOWERCASE_R, cache_tag);
|
||||||
|
|
||||||
if let Err(error) = client.database().delete(filter).await {
|
if let Err(error) = client.database().delete(filter).await {
|
||||||
log::warn!("nip29: clearing the group cache failed: {error}");
|
log::warn!("clearing the group cache failed: {error}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -1271,18 +1255,17 @@ impl GroupsRegistry {
|
|||||||
let event = match builder.finalize_async(&signer).await {
|
let event = match builder.finalize_async(&signer).await {
|
||||||
Ok(event) => event,
|
Ok(event) => event,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: signing the group list failed: {error}");
|
log::warn!("signing the group list failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match client.send_event(&event).to_nip65().await {
|
match client.send_event(&event).to_nip65().await {
|
||||||
Ok(output) if output.failed.is_empty() => {}
|
Ok(output) if output.failed.is_empty() => {}
|
||||||
Ok(output) => log::warn!(
|
Ok(output) => {
|
||||||
"nip29: {} relay(s) rejected the group list",
|
log::warn!("{} relay(s) rejected the group list", output.failed.len())
|
||||||
output.failed.len()
|
}
|
||||||
),
|
Err(error) => log::warn!("publishing the group list failed: {error}"),
|
||||||
Err(error) => log::warn!("nip29: publishing the group list failed: {error}"),
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1307,7 +1290,7 @@ impl GroupsRegistry {
|
|||||||
.read_with(cx, |group, cx| group.save_envelope(event, cx))
|
.read_with(cx, |group, cx| group.save_envelope(event, cx))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::warn!("nip29: caching failed: {error}");
|
log::warn!("caching failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1318,7 +1301,7 @@ impl GroupsRegistry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: reloading {} failed: {error}", logged.id());
|
log::warn!("reloading {} failed: {error}", logged.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -1345,7 +1328,7 @@ impl GroupsRegistry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("nip29: loading {} state failed: {error}", logged.id());
|
log::warn!("loading {} state failed: {error}", logged.id());
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1365,7 +1348,7 @@ impl GroupsRegistry {
|
|||||||
.read_with(cx, |group, cx| group.save_envelope(event.clone(), cx))
|
.read_with(cx, |group, cx| group.save_envelope(event.clone(), cx))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::warn!("nip29: caching failed: {error}");
|
log::warn!("caching failed: {error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1411,7 +1394,7 @@ impl GroupsRegistry {
|
|||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if let Err(error) = purge.await {
|
if let Err(error) = purge.await {
|
||||||
log::warn!("nip29: dropping the deleted envelope failed: {error}");
|
log::warn!("dropping the deleted envelope failed: {error}");
|
||||||
}
|
}
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
group.update(cx, |group, cx| {
|
group.update(cx, |group, cx| {
|
||||||
@@ -1427,7 +1410,7 @@ impl GroupsRegistry {
|
|||||||
if id == list_subscription_id() {
|
if id == list_subscription_id() {
|
||||||
match GroupList::parse(&event) {
|
match GroupList::parse(&event) {
|
||||||
Ok(list) => self.merge(list, cx),
|
Ok(list) => self.merge(list, cx),
|
||||||
Err(error) => log::warn!("nip29: ignoring a malformed group list: {error}"),
|
Err(error) => log::warn!("ignoring a malformed group list: {error}"),
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1470,7 +1453,7 @@ impl GroupsRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Ok(metadata) = GroupMetadata::parse(event) else {
|
let Ok(metadata) = GroupMetadata::parse(event) else {
|
||||||
log::warn!("nip29: ignoring malformed group metadata from {relay}");
|
log::warn!("ignoring malformed group metadata from {relay}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1515,12 +1498,12 @@ impl GroupsRegistry {
|
|||||||
|
|
||||||
fn handle_closed(&mut self, id: SubscriptionId, reason: String, cx: &mut Context<Self>) {
|
fn handle_closed(&mut self, id: SubscriptionId, reason: String, cx: &mut Context<Self>) {
|
||||||
if let Some(relay) = self.browse.get(&id) {
|
if let Some(relay) = self.browse.get(&id) {
|
||||||
log::warn!("nip29: browsing {relay} was refused: {reason}");
|
log::warn!("browsing {relay} was refused: {reason}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == fork_subscription_id() {
|
if id == fork_subscription_id() {
|
||||||
log::debug!("nip29: the admins' group lists were refused: {reason}");
|
log::debug!("the admins' group lists were refused: {reason}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1531,7 +1514,7 @@ impl GroupsRegistry {
|
|||||||
if is_page(&id) {
|
if is_page(&id) {
|
||||||
self.routes.remove(&id);
|
self.routes.remove(&id);
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"nip29: loading older messages in {} was refused: {reason}",
|
"loading older messages in {} was refused: {reason}",
|
||||||
key.id()
|
key.id()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use theme::ActiveTheme;
|
|||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
||||||
use ui::indicator::Indicator;
|
|
||||||
use ui::input::{Input, InputEvent, InputState};
|
use ui::input::{Input, InputEvent, InputState};
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::scroll::Scrollbar;
|
use ui::scroll::Scrollbar;
|
||||||
@@ -214,10 +213,6 @@ impl BrowsePanel {
|
|||||||
self.render_card_rows(cards, cx)
|
self.render_card_rows(cards, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A row holds two group cards side by side.
|
|
||||||
///
|
|
||||||
/// The cards are the relative slice of the visible rows, so they pair up
|
|
||||||
/// in order, two per row.
|
|
||||||
fn render_card_rows(
|
fn render_card_rows(
|
||||||
&self,
|
&self,
|
||||||
cards: &[GroupCandidate],
|
cards: &[GroupCandidate],
|
||||||
@@ -235,8 +230,6 @@ impl BrowsePanel {
|
|||||||
.p_2()
|
.p_2()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.children(first.map(|card| self.render_card(card, cx)))
|
.children(first.map(|card| self.render_card(card, cx)))
|
||||||
// Keep an odd last card in one column: an empty spacer
|
|
||||||
// takes the second slot so the card cannot stretch.
|
|
||||||
.child(match second {
|
.child(match second {
|
||||||
Some(card) => self.render_card(card, cx),
|
Some(card) => self.render_card(card, cx),
|
||||||
None => div().flex_1().into_any_element(),
|
None => div().flex_1().into_any_element(),
|
||||||
@@ -339,7 +332,7 @@ impl BrowsePanel {
|
|||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.line_clamp(3)
|
.line_clamp(2)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if let Some(about) = about {
|
if let Some(about) = about {
|
||||||
this.child(about)
|
this.child(about)
|
||||||
@@ -458,28 +451,31 @@ impl Render for BrowsePanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.gap_2()
|
.h_32()
|
||||||
.px_2()
|
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.child(Input::new(&self.input).cleanable(true).flex_1())
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.bg(cx.theme().surface_background)
|
||||||
.child(
|
.child(
|
||||||
Button::new("browse-go")
|
h_flex()
|
||||||
.label("Go")
|
.w_full()
|
||||||
.primary()
|
.max_w_56()
|
||||||
.on_click(cx.listener(|this, _event, window, cx| {
|
.gap_1()
|
||||||
this.browse(window, cx);
|
.child(Input::new(&self.input).cleanable(true).flex_1())
|
||||||
})),
|
.child(Button::new("browse-go").label("Go").primary().on_click(
|
||||||
)
|
cx.listener(|this, _event, window, cx| {
|
||||||
.when(self.loading || (discover && discovering), |this| {
|
this.browse(window, cx);
|
||||||
this.child(Indicator::new().small().color(cx.theme().icon_accent))
|
}),
|
||||||
}),
|
)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.min_h_0()
|
.min_h_0()
|
||||||
.relative()
|
.relative()
|
||||||
|
.px_2()
|
||||||
.child(body)
|
.child(body)
|
||||||
.child(Scrollbar::vertical(&self.scroll_handle)),
|
.child(Scrollbar::vertical(&self.scroll_handle)),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user