add send reaction

This commit is contained in:
2026-07-31 08:45:56 +07:00
parent 9dbac5308d
commit c3d677ca81
2 changed files with 80 additions and 24 deletions

View File

@@ -1,11 +1,11 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use instant::Duration;
use anyhow::{Error, anyhow}; use anyhow::{Error, anyhow};
use common::EventExt; use common::EventExt;
use device::DeviceRegistry; use device::DeviceRegistry;
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
use instant::Duration;
use itertools::Itertools; use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry}; use person::{Person, PersonRegistry};
@@ -419,12 +419,23 @@ impl Room {
} }
// Construct a rumor event for direct message // Construct a rumor event for direct message
pub fn rumor<S, I>(&self, content: S, replies: I, cx: &App) -> Option<UnsignedEvent> pub fn rumor<S, I>(
&self,
content: S,
replies: I,
reaction: bool,
cx: &App,
) -> Option<UnsignedEvent>
where where
S: Into<String>, S: Into<String>,
I: IntoIterator<Item = EventId>, I: IntoIterator<Item = EventId>,
{ {
let kind = Kind::PrivateDirectMessage; let kind = if reaction {
Kind::Reaction
} else {
Kind::PrivateDirectMessage
};
let content: String = content.into(); let content: String = content.into();
let replies: Vec<EventId> = replies.into_iter().collect(); let replies: Vec<EventId> = replies.into_iter().collect();

View File

@@ -35,6 +35,9 @@ use ui::{
use crate::text::RenderedText; use crate::text::RenderedText;
const REACTION_EMOJIS: &[&str] = &["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"];
const COMPACT_REACTION_EMOJIS: &[&str] = &["👍", "❤️", "👀"];
mod actions; mod actions;
mod text; mod text;
@@ -331,24 +334,49 @@ impl ChatPanel {
// Get the message which includes all attachments // Get the message which includes all attachments
let content = self.get_input_value(cx); let content = self.get_input_value(cx);
// Get the replies to this message
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
// Return if message is empty // Return if message is empty
if content.trim().is_empty() { if content.trim().is_empty() {
window.push_notification("Cannot send an empty message", cx); window.push_notification("Cannot send an empty message", cx);
return; 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<Self>,
) {
// 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 /// Send a message to all members of the chat
fn send_message(&mut self, value: &str, window: &mut Window, cx: &mut Context<Self>) { fn send_message(
&mut self,
value: &str,
replies: Vec<EventId>,
reaction: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
if value.trim().is_empty() { if value.trim().is_empty() {
window.push_notification("Cannot send an empty message", cx); window.push_notification("Cannot send an empty message", cx);
return; return;
} }
let room = self.room.clone(); let room = self.room.clone();
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
let content = value.to_string(); let content = value.to_string();
let sent_ids = self.sent_ids.clone(); let sent_ids = self.sent_ids.clone();
@@ -357,7 +385,7 @@ impl ChatPanel {
return; return;
}; };
let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| { 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)?; let send_task = room.send(rumor.clone(), cx)?;
Some((rumor, send_task)) Some((rumor, send_task))
}) { }) {
@@ -575,7 +603,10 @@ impl ChatPanel {
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) { fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
match command { match command {
Command::Insert(content) => { 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) => { Command::ChangeSubject(subject) => {
if self if self
@@ -990,13 +1021,9 @@ impl ChatPanel {
.w_full() .w_full()
.px_2() .px_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().element_selected) .border_color(cx.theme().element_active)
.text_sm() .text_sm()
.child( .child(div().child(author.name()))
div()
.text_color(cx.theme().text_accent)
.child(author.name()),
)
.child( .child(
div() div()
.w_full() .w_full()
@@ -1192,6 +1219,29 @@ impl ChatPanel {
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.bg(cx.theme().background) .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( .child(
Button::new("reply") Button::new("reply")
.icon(IconName::Reply) .icon(IconName::Reply)
@@ -1305,7 +1355,7 @@ impl ChatPanel {
.gap_1() .gap_1()
.text_xs() .text_xs()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("Replying to:")) .child("Replying to:")
.child( .child(
div() div()
.text_color(cx.theme().text_accent) .text_color(cx.theme().text_accent)
@@ -1402,15 +1452,10 @@ impl ChatPanel {
.ghost() .ghost()
.large() .large()
.dropdown_menu_with_anchor(gpui::Anchor::BottomLeft, move |this, _window, _cx| { .dropdown_menu_with_anchor(gpui::Anchor::BottomLeft, move |this, _window, _cx| {
this.horizontal() let menu = this.horizontal();
.menu("👍", Box::new(Command::Insert("👍"))) REACTION_EMOJIS.iter().fold(menu, |this, emoji| {
.menu("👎", Box::new(Command::Insert("👎"))) this.menu(*emoji, Box::new(Command::Insert(emoji)))
.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("👀")))
}) })
} }
} }