From c3d677ca81198fe8807b5106b5290bfe0975bf41 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 31 Jul 2026 08:45:56 +0700 Subject: [PATCH] add send reaction --- crates/chat/src/room.rs | 17 ++++++-- crates/chat_ui/src/lib.rs | 87 +++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index d207c05..8f09b4b 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -1,11 +1,11 @@ use std::cmp::Ordering; use std::hash::{Hash, Hasher}; -use instant::Duration; use anyhow::{Error, anyhow}; use common::EventExt; use device::DeviceRegistry; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; +use instant::Duration; use itertools::Itertools; use nostr_sdk::prelude::*; use person::{Person, PersonRegistry}; @@ -419,12 +419,23 @@ impl Room { } // Construct a rumor event for direct message - pub fn rumor(&self, content: S, replies: I, cx: &App) -> Option + pub fn rumor( + &self, + content: S, + replies: I, + reaction: bool, + cx: &App, + ) -> Option where S: Into, I: IntoIterator, { - let kind = Kind::PrivateDirectMessage; + let kind = if reaction { + Kind::Reaction + } else { + Kind::PrivateDirectMessage + }; + let content: String = content.into(); let replies: Vec = replies.into_iter().collect(); diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 27d5380..9ab7ed2 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -35,6 +35,9 @@ use ui::{ use crate::text::RenderedText; +const REACTION_EMOJIS: &[&str] = &["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"]; +const COMPACT_REACTION_EMOJIS: &[&str] = &["👍", "❤️", "👀"]; + mod actions; mod text; @@ -331,24 +334,49 @@ impl ChatPanel { // Get the message which includes all attachments let content = self.get_input_value(cx); + // Get the replies to this message + let replies: Vec = self.replies_to.read(cx).iter().copied().collect(); + // Return if message is empty if content.trim().is_empty() { window.push_notification("Cannot send an empty message", cx); return; } - self.send_message(&content, window, cx); + self.send_message(&content, replies, false, window, cx); + } + + fn send_reaction( + &mut self, + emoji: &str, + target: &EventId, + window: &mut Window, + cx: &mut Context, + ) { + // Return if emoji is empty + if emoji.trim().is_empty() { + window.push_notification("Cannot send an empty reaction", cx); + return; + } + + self.send_message(emoji, vec![*target], true, window, cx); } /// Send a message to all members of the chat - fn send_message(&mut self, value: &str, window: &mut Window, cx: &mut Context) { + fn send_message( + &mut self, + value: &str, + replies: Vec, + reaction: bool, + window: &mut Window, + cx: &mut Context, + ) { if value.trim().is_empty() { window.push_notification("Cannot send an empty message", cx); return; } let room = self.room.clone(); - let replies: Vec = self.replies_to.read(cx).iter().copied().collect(); let content = value.to_string(); let sent_ids = self.sent_ids.clone(); @@ -357,7 +385,7 @@ impl ChatPanel { return; }; let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| { - let rumor = room.rumor(content.clone(), replies, cx)?; + let rumor = room.rumor(content.clone(), replies, reaction, cx)?; let send_task = room.send(rumor.clone(), cx)?; Some((rumor, send_task)) }) { @@ -575,7 +603,10 @@ impl ChatPanel { fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context) { match command { Command::Insert(content) => { - self.send_message(content, window, cx); + self.input.update(cx, |this, cx| { + let new_value = format!("{} {}", this.value(), content); + this.set_value(new_value, window, cx); + }); } Command::ChangeSubject(subject) => { if self @@ -990,13 +1021,9 @@ impl ChatPanel { .w_full() .px_2() .border_l_2() - .border_color(cx.theme().element_selected) + .border_color(cx.theme().element_active) .text_sm() - .child( - div() - .text_color(cx.theme().text_accent) - .child(author.name()), - ) + .child(div().child(author.name())) .child( div() .w_full() @@ -1192,6 +1219,29 @@ impl ChatPanel { .border_1() .border_color(cx.theme().border) .bg(cx.theme().background) + .children({ + let mut items = vec![]; + + for emoji in COMPACT_REACTION_EMOJIS { + items.push( + Button::new(*emoji) + .label(*emoji) + .tooltip(*emoji) + .small() + .ghost() + .on_click({ + let emoji = *emoji; + let id = *id; + cx.listener(move |this, _event, window, cx| { + this.send_reaction(emoji, &id, window, cx); + }) + }), + ); + } + + items + }) + .child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border)) .child( Button::new("reply") .icon(IconName::Reply) @@ -1305,7 +1355,7 @@ impl ChatPanel { .gap_1() .text_xs() .text_color(cx.theme().text_muted) - .child(SharedString::from("Replying to:")) + .child("Replying to:") .child( div() .text_color(cx.theme().text_accent) @@ -1402,15 +1452,10 @@ impl ChatPanel { .ghost() .large() .dropdown_menu_with_anchor(gpui::Anchor::BottomLeft, move |this, _window, _cx| { - this.horizontal() - .menu("👍", Box::new(Command::Insert("👍"))) - .menu("👎", Box::new(Command::Insert("👎"))) - .menu("😄", Box::new(Command::Insert("😄"))) - .menu("🎉", Box::new(Command::Insert("🎉"))) - .menu("😕", Box::new(Command::Insert("😕"))) - .menu("❤️", Box::new(Command::Insert("❤️"))) - .menu("🚀", Box::new(Command::Insert("🚀"))) - .menu("👀", Box::new(Command::Insert("👀"))) + let menu = this.horizontal(); + REACTION_EMOJIS.iter().fold(menu, |this, emoji| { + this.menu(*emoji, Box::new(Command::Insert(emoji))) + }) }) } }