From bd2b72a57ae0566a385fca7985b09f5659665d1c Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:15:10 +0700 Subject: [PATCH] chore: fix duplicate messages (#108) * prevent duplicate message on load * refactor --- crates/coop/src/views/chat/mod.rs | 45 ++++++++++++------------------ crates/registry/src/message.rs | 46 +++++++++++++++++++------------ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/crates/coop/src/views/chat/mod.rs b/crates/coop/src/views/chat/mod.rs index 70c430c..8aa1351 100644 --- a/crates/coop/src/views/chat/mod.rs +++ b/crates/coop/src/views/chat/mod.rs @@ -1,5 +1,4 @@ -use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::rc::Rc; use std::sync::Arc; @@ -57,7 +56,7 @@ pub struct Chat { focus_handle: FocusHandle, // Chat Room room: Entity, - messages: Entity>>>, + messages: Entity>, text_data: HashMap, list_state: ListState, // New Message @@ -76,7 +75,7 @@ impl Chat { pub fn new(room: Entity, window: &mut Window, cx: &mut App) -> Entity { let attaches = cx.new(|_| None); let replies_to = cx.new(|_| None); - let messages = cx.new(|_| vec![]); + let messages = cx.new(|_| BTreeSet::new()); let input = cx.new(|cx| { InputState::new(window, cx) @@ -120,10 +119,10 @@ impl Chat { } let old_len = this.messages.read(cx).len(); - let message = event.clone().into_rc(); + let message = event.to_owned(); cx.update_entity(&this.messages, |this, cx| { - this.extend(vec![message]); + this.insert(message); cx.notify(); }); @@ -179,7 +178,7 @@ impl Chat { // Extend the messages list with the new events this.messages.update(cx, |this, cx| { - this.extend(messages.into_iter().map(|e| e.into_rc())); + this.extend(messages); cx.notify(); }); @@ -242,9 +241,8 @@ impl Chat { self.messages .read(cx) .iter() - .filter(|m| m.borrow().author == identity) + .filter(|m| m.author == identity) .any(|existing| { - let existing = existing.borrow(); // Check if messages are within the time window (existing.created_at.as_u64() >= min_timestamp) && // Compare content and author @@ -316,10 +314,9 @@ impl Chat { }); this.messages.update(cx, |this, cx| { - if let Some(msg) = - this.iter().find(|msg| msg.borrow().id == id).cloned() + if let Some(mut msg) = this.iter().find(|msg| msg.id == id).cloned() { - msg.borrow_mut().errors = Some(reports); + msg.errors = Some(reports); cx.notify(); } }); @@ -334,10 +331,9 @@ impl Chat { fn insert_message(&self, message: Message, cx: &mut Context) { let old_len = self.messages.read(cx).len(); - let message = message.into_rc(); cx.update_entity(&self.messages, |this, cx| { - this.extend(vec![message]); + this.insert(message); cx.notify(); }); @@ -345,12 +341,7 @@ impl Chat { } fn scroll_to(&self, id: EventId, cx: &Context) { - if let Some(ix) = self - .messages - .read(cx) - .iter() - .position(|m| m.borrow().id == id) - { + if let Some(ix) = self.messages.read(cx).iter().position(|m| m.id == id) { self.list_state.scroll_to_reveal_item(ix); } } @@ -359,8 +350,9 @@ impl Chat { let Some(item) = self .messages .read(cx) - .get(ix) - .map(|m| ClipboardItem::new_string(m.borrow().content.to_string())) + .iter() + .nth(ix) + .map(|m| ClipboardItem::new_string(m.content.to_string())) else { return; }; @@ -369,7 +361,7 @@ impl Chat { } fn reply_to(&mut self, ix: usize, cx: &mut Context) { - let Some(message) = self.messages.read(cx).get(ix).map(|m| m.borrow().clone()) else { + let Some(message) = self.messages.read(cx).iter().nth(ix).map(|m| m.to_owned()) else { return; }; @@ -583,7 +575,7 @@ impl Chat { window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let Some(message) = self.messages.read(cx).get(ix).map(|m| m.borrow()) else { + let Some(message) = self.messages.read(cx).iter().nth(ix) else { return div().id(ix); }; @@ -642,10 +634,7 @@ impl Chat { let messages = self.messages.read(cx); for (ix, id) in replies.iter().cloned().enumerate() { - let Some(message) = messages - .iter() - .map(|m| m.borrow()) - .find(|m| m.id == id) + let Some(message) = messages.iter().find(|m| m.id == id) else { continue; }; diff --git a/crates/registry/src/message.rs b/crates/registry/src/message.rs index 5a5bd6e..9c5caba 100644 --- a/crates/registry/src/message.rs +++ b/crates/registry/src/message.rs @@ -1,6 +1,5 @@ -use std::cell::RefCell; +use std::hash::Hash; use std::iter::IntoIterator; -use std::rc::Rc; use chrono::{Local, TimeZone}; use gpui::SharedString; @@ -12,7 +11,7 @@ use crate::room::SendError; /// /// Contains information about the message content, author, creation time, /// mentions, replies, and any errors that occurred during sending. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Message { /// Unique identifier of the message (EventId from nostr_sdk) pub id: EventId, @@ -30,6 +29,32 @@ pub struct Message { pub errors: Option>, } +impl Eq for Message {} + +impl PartialEq for Message { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Ord for Message { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.created_at.cmp(&other.created_at) + } +} + +impl PartialOrd for Message { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Hash for Message { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + /// Builder pattern implementation for constructing Message objects. #[derive(Debug)] pub struct MessageBuilder { @@ -110,11 +135,6 @@ impl MessageBuilder { self } - /// Builds the message wrapped in an Rc> - pub fn build_rc(self) -> Result>, String> { - self.build().map(|m| Rc::new(RefCell::new(m))) - } - /// Builds the message pub fn build(self) -> Result { Ok(Message { @@ -135,16 +155,6 @@ impl Message { MessageBuilder::new(id, author) } - /// Converts the message into an Rc> - pub fn into_rc(self) -> Rc> { - Rc::new(RefCell::new(self)) - } - - /// Builds a message from a builder and wraps it in Rc - pub fn build_rc(builder: MessageBuilder) -> Result>, String> { - builder.build().map(|m| Rc::new(RefCell::new(m))) - } - /// Returns a human-readable string representing how long ago the message was created pub fn ago(&self) -> SharedString { let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {