chore: fix duplicate messages (#108)

* prevent duplicate message on load

* refactor
This commit is contained in:
reya
2025-08-05 21:15:10 +07:00
committed by GitHub
parent d6edc8b546
commit bd2b72a57a
2 changed files with 45 additions and 46 deletions

View File

@@ -1,5 +1,4 @@
use std::cell::RefCell; use std::collections::{BTreeSet, HashMap};
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@@ -57,7 +56,7 @@ pub struct Chat {
focus_handle: FocusHandle, focus_handle: FocusHandle,
// Chat Room // Chat Room
room: Entity<Room>, room: Entity<Room>,
messages: Entity<Vec<Rc<RefCell<Message>>>>, messages: Entity<BTreeSet<Message>>,
text_data: HashMap<EventId, RichText>, text_data: HashMap<EventId, RichText>,
list_state: ListState, list_state: ListState,
// New Message // New Message
@@ -76,7 +75,7 @@ impl Chat {
pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<Self> { pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<Self> {
let attaches = cx.new(|_| None); let attaches = cx.new(|_| None);
let replies_to = 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| { let input = cx.new(|cx| {
InputState::new(window, cx) InputState::new(window, cx)
@@ -120,10 +119,10 @@ impl Chat {
} }
let old_len = this.messages.read(cx).len(); 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| { cx.update_entity(&this.messages, |this, cx| {
this.extend(vec![message]); this.insert(message);
cx.notify(); cx.notify();
}); });
@@ -179,7 +178,7 @@ impl Chat {
// Extend the messages list with the new events // Extend the messages list with the new events
this.messages.update(cx, |this, cx| { this.messages.update(cx, |this, cx| {
this.extend(messages.into_iter().map(|e| e.into_rc())); this.extend(messages);
cx.notify(); cx.notify();
}); });
@@ -242,9 +241,8 @@ impl Chat {
self.messages self.messages
.read(cx) .read(cx)
.iter() .iter()
.filter(|m| m.borrow().author == identity) .filter(|m| m.author == identity)
.any(|existing| { .any(|existing| {
let existing = existing.borrow();
// Check if messages are within the time window // Check if messages are within the time window
(existing.created_at.as_u64() >= min_timestamp) && (existing.created_at.as_u64() >= min_timestamp) &&
// Compare content and author // Compare content and author
@@ -316,10 +314,9 @@ impl Chat {
}); });
this.messages.update(cx, |this, cx| { this.messages.update(cx, |this, cx| {
if let Some(msg) = if let Some(mut msg) = this.iter().find(|msg| msg.id == id).cloned()
this.iter().find(|msg| msg.borrow().id == id).cloned()
{ {
msg.borrow_mut().errors = Some(reports); msg.errors = Some(reports);
cx.notify(); cx.notify();
} }
}); });
@@ -334,10 +331,9 @@ impl Chat {
fn insert_message(&self, message: Message, cx: &mut Context<Self>) { fn insert_message(&self, message: Message, cx: &mut Context<Self>) {
let old_len = self.messages.read(cx).len(); let old_len = self.messages.read(cx).len();
let message = message.into_rc();
cx.update_entity(&self.messages, |this, cx| { cx.update_entity(&self.messages, |this, cx| {
this.extend(vec![message]); this.insert(message);
cx.notify(); cx.notify();
}); });
@@ -345,12 +341,7 @@ impl Chat {
} }
fn scroll_to(&self, id: EventId, cx: &Context<Self>) { fn scroll_to(&self, id: EventId, cx: &Context<Self>) {
if let Some(ix) = self if let Some(ix) = self.messages.read(cx).iter().position(|m| m.id == id) {
.messages
.read(cx)
.iter()
.position(|m| m.borrow().id == id)
{
self.list_state.scroll_to_reveal_item(ix); self.list_state.scroll_to_reveal_item(ix);
} }
} }
@@ -359,8 +350,9 @@ impl Chat {
let Some(item) = self let Some(item) = self
.messages .messages
.read(cx) .read(cx)
.get(ix) .iter()
.map(|m| ClipboardItem::new_string(m.borrow().content.to_string())) .nth(ix)
.map(|m| ClipboardItem::new_string(m.content.to_string()))
else { else {
return; return;
}; };
@@ -369,7 +361,7 @@ impl Chat {
} }
fn reply_to(&mut self, ix: usize, cx: &mut Context<Self>) { fn reply_to(&mut self, ix: usize, cx: &mut Context<Self>) {
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; return;
}; };
@@ -583,7 +575,7 @@ impl Chat {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> 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); return div().id(ix);
}; };
@@ -642,10 +634,7 @@ impl Chat {
let messages = self.messages.read(cx); let messages = self.messages.read(cx);
for (ix, id) in replies.iter().cloned().enumerate() { for (ix, id) in replies.iter().cloned().enumerate() {
let Some(message) = messages let Some(message) = messages.iter().find(|m| m.id == id)
.iter()
.map(|m| m.borrow())
.find(|m| m.id == id)
else { else {
continue; continue;
}; };

View File

@@ -1,6 +1,5 @@
use std::cell::RefCell; use std::hash::Hash;
use std::iter::IntoIterator; use std::iter::IntoIterator;
use std::rc::Rc;
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use gpui::SharedString; use gpui::SharedString;
@@ -12,7 +11,7 @@ use crate::room::SendError;
/// ///
/// Contains information about the message content, author, creation time, /// Contains information about the message content, author, creation time,
/// mentions, replies, and any errors that occurred during sending. /// mentions, replies, and any errors that occurred during sending.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone)]
pub struct Message { pub struct Message {
/// Unique identifier of the message (EventId from nostr_sdk) /// Unique identifier of the message (EventId from nostr_sdk)
pub id: EventId, pub id: EventId,
@@ -30,6 +29,32 @@ pub struct Message {
pub errors: Option<Vec<SendError>>, pub errors: Option<Vec<SendError>>,
} }
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<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Hash for Message {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// Builder pattern implementation for constructing Message objects. /// Builder pattern implementation for constructing Message objects.
#[derive(Debug)] #[derive(Debug)]
pub struct MessageBuilder { pub struct MessageBuilder {
@@ -110,11 +135,6 @@ impl MessageBuilder {
self self
} }
/// Builds the message wrapped in an Rc<RefCell<Message>>
pub fn build_rc(self) -> Result<Rc<RefCell<Message>>, String> {
self.build().map(|m| Rc::new(RefCell::new(m)))
}
/// Builds the message /// Builds the message
pub fn build(self) -> Result<Message, String> { pub fn build(self) -> Result<Message, String> {
Ok(Message { Ok(Message {
@@ -135,16 +155,6 @@ impl Message {
MessageBuilder::new(id, author) MessageBuilder::new(id, author)
} }
/// Converts the message into an Rc<RefCell<Message>>
pub fn into_rc(self) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(self))
}
/// Builds a message from a builder and wraps it in Rc<RefCell>
pub fn build_rc(builder: MessageBuilder) -> Result<Rc<RefCell<Self>>, String> {
builder.build().map(|m| Rc::new(RefCell::new(m)))
}
/// Returns a human-readable string representing how long ago the message was created /// Returns a human-readable string representing how long ago the message was created
pub fn ago(&self) -> SharedString { pub fn ago(&self) -> SharedString {
let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) { let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {