feat: add group reaction reaction and deletion

This commit is contained in:
2026-10-11 07:57:04 +07:00
parent f83922df2e
commit 4c279a0686
6 changed files with 328 additions and 5 deletions
+60
View File
@@ -27,6 +27,13 @@ enum Absorbed {
Updated,
}
#[derive(Debug, Clone)]
pub struct Reaction {
pub id: EventId,
pub emoji: String,
pub author: PublicKey,
}
#[derive(Debug, Clone)]
pub enum Row {
Message(Box<Message>),
@@ -78,6 +85,7 @@ pub struct Group {
state_ids: HashMap<Kind, EventId>,
rows: Vec<Row>,
index: HashSet<EventId>,
reactions: HashMap<EventId, Vec<Reaction>>,
leave_requests: HashMap<PublicKey, Timestamp>,
window: TimelineWindow,
oldest: Option<Timestamp>,
@@ -105,6 +113,7 @@ impl Group {
state_ids: HashMap::new(),
rows: Vec::new(),
index: HashSet::new(),
reactions: HashMap::new(),
leave_requests: HashMap::new(),
window: TimelineWindow::new(),
oldest: None,
@@ -180,6 +189,10 @@ impl Group {
})
}
pub fn reactions(&self, id: &EventId) -> &[Reaction] {
self.reactions.get(id).map(Vec::as_slice).unwrap_or(&[])
}
pub fn loading_more(&self) -> bool {
self.loading_more
}
@@ -247,6 +260,10 @@ impl Group {
self.dispatch(move |gid, prev| gid.delete_event(id, prev), cx)
}
pub fn delete_group(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
self.dispatch(GroupId::delete_group, cx)
}
pub fn create_invite(&mut self, code: &str, cx: &mut Context<Self>) -> Task<Result<()>> {
self.dispatch(move |id, prev| id.create_invite(code, prev), cx)
}
@@ -309,6 +326,15 @@ impl Group {
}
}
pub(crate) fn mark_member(&mut self, cx: &mut Context<Self>) {
if self.membership == Membership::Member {
return;
}
self.membership = Membership::Member;
cx.emit(GroupEvent::Updated);
cx.notify();
}
pub(crate) fn mark_refused(&mut self, reason: String, cx: &mut Context<Self>) {
if self.membership == Membership::Member {
return;
@@ -504,6 +530,14 @@ impl Group {
}
self.rows.retain(|row| row.id() != id);
self.reactions.remove(&id);
for reactions in self.reactions.values_mut() {
reactions.retain(|reaction| reaction.id != id);
}
self.reactions.retain(|_, reactions| !reactions.is_empty());
cx.emit(GroupEvent::Updated);
cx.notify();
}
@@ -541,6 +575,14 @@ impl Group {
};
}
if event.kind == Kind::Reaction {
return if self.push_reaction(event) {
Absorbed::Updated
} else {
Absorbed::Ignored
};
}
if RENDER_KINDS.contains(&event.kind) {
return Absorbed::Shown {
fresh: self.push_row(event),
@@ -626,6 +668,24 @@ impl Group {
true
}
fn push_reaction(&mut self, event: &Event) -> bool {
let Some(target) = event.tags.event_ids().next() else {
return false;
};
if !self.index.insert(event.id) {
return false;
}
self.reactions.entry(target).or_default().push(Reaction {
id: event.id,
emoji: event.content.clone(),
author: event.pubkey,
});
true
}
fn push_activity(&mut self, event: &Event) -> bool {
let activity = match Activity::parse(event) {
Ok(activity) => activity,
+31 -2
View File
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::sync::LazyLock;
use std::time::Duration;
use anyhow::{Error, Result};
use anyhow::{Error, Result, anyhow};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec};
@@ -186,11 +186,16 @@ impl GroupsRegistry {
self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(error) = sent.await {
let reason = error.to_string();
let duplicate = reason.starts_with("duplicate");
if let Err(error) = this.update(cx, |this, cx| {
if let Some(group) = this.group(&key, cx) {
group.update(cx, |group, cx| {
group.mark_refused(reason, cx);
if duplicate {
group.mark_member(cx);
} else {
group.mark_refused(reason, cx);
}
});
}
}) {
@@ -412,6 +417,30 @@ impl GroupsRegistry {
}));
}
/// Ask the relay to delete the group (kind 9008).
pub fn delete_group(&mut self, key: &GroupKey, cx: &mut Context<Self>) -> Task<Result<()>> {
let Some(group) = self.group(key, cx) else {
return Task::ready(Err(anyhow!("nip29: group not found")));
};
let sent = group.update(cx, |group, cx| group.delete_group(cx));
let key = key.clone();
cx.spawn(async move |this, cx| {
let result = sent.await;
if result.is_ok()
&& let Err(error) = this.update(cx, |this, cx| {
this.forget(key, cx);
})
{
log::warn!("forgetting the deleted group failed: {error}");
}
result
})
}
pub(crate) fn reset(&mut self, cx: &mut Context<Self>) {
self.notification_listener = None;
self.signal_consumer = None;
+6
View File
@@ -212,6 +212,12 @@ impl GroupId {
.tag_maybe(previous)
}
pub fn delete_group(&self, previous: Option<Tag>) -> EventBuilder {
EventBuilder::new(Kind::GroupDeleteGroup, "")
.tag(self.h_tag())
.tag_maybe(previous)
}
pub fn create_invite(&self, code: &str, previous: Option<Tag>) -> EventBuilder {
EventBuilder::new(Kind::GroupCreateInvite, "")
.tag(self.h_tag())