group message
This commit is contained in:
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
pub use actions::*;
|
||||
use anyhow::{Context as AnyhowContext, Error};
|
||||
use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
|
||||
use chat::{ChatRegistry, Message, Room, RoomEvent, SendReport, SendStatus};
|
||||
use common::{TimestampExt, coop_cache};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -38,9 +38,6 @@ use crate::text::RenderedText;
|
||||
mod actions;
|
||||
mod text;
|
||||
|
||||
const ANNOUNCEMENT: &str =
|
||||
"This conversation is private. Only members can see each other's messages.";
|
||||
|
||||
pub fn init(room: WeakEntity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
||||
cx.new(|cx| ChatPanel::new(room, window, cx))
|
||||
}
|
||||
@@ -101,7 +98,7 @@ impl ChatPanel {
|
||||
let reports_by_id = cx.new(|_| BTreeMap::new());
|
||||
|
||||
// Define list of messages
|
||||
let messages = BTreeSet::from([Message::system()]);
|
||||
let messages = BTreeSet::default();
|
||||
let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.));
|
||||
|
||||
// Get room id and name
|
||||
@@ -476,25 +473,13 @@ impl ChatPanel {
|
||||
}
|
||||
|
||||
/// Get a message by its ID
|
||||
fn message(&self, id: &EventId) -> Option<&RenderedMessage> {
|
||||
self.messages.iter().find_map(|msg| {
|
||||
if let Message::User(rendered) = msg
|
||||
&& &rendered.id == id
|
||||
{
|
||||
return Some(rendered);
|
||||
}
|
||||
None
|
||||
})
|
||||
fn message(&self, id: &EventId) -> Option<&Message> {
|
||||
self.messages.iter().find(|msg| &msg.id == id)
|
||||
}
|
||||
|
||||
fn scroll_to(&self, id: EventId) {
|
||||
if let Some(ix) = self.messages.iter().position(|m| {
|
||||
if let Message::User(msg) = m {
|
||||
msg.id == id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
/// Scroll to a message by its ID
|
||||
fn scroll_to(&self, id: &EventId) {
|
||||
if let Some(ix) = self.messages.iter().position(|msg| &msg.id == id) {
|
||||
self.list_state.scroll_to_reveal_item(ix);
|
||||
}
|
||||
}
|
||||
@@ -742,9 +727,11 @@ impl ChatPanel {
|
||||
cx.open_url(&content);
|
||||
}
|
||||
|
||||
fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
||||
fn render_announcement(&self, cx: &Context<Self>) -> AnyElement {
|
||||
const MSG: &str =
|
||||
"This conversation is private. Only members can see each other's messages.";
|
||||
|
||||
v_flex()
|
||||
.id(ix)
|
||||
.h_40()
|
||||
.w_full()
|
||||
.gap_3()
|
||||
@@ -761,7 +748,7 @@ impl ChatPanel {
|
||||
.size_12()
|
||||
.text_color(cx.theme().ghost_element_active),
|
||||
)
|
||||
.child(SharedString::from(ANNOUNCEMENT))
|
||||
.child(MSG)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -798,6 +785,34 @@ impl ChatPanel {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn is_group_start(&self, ix: usize) -> bool {
|
||||
// 5 minutes
|
||||
const GROUP_WINDOW: u64 = 300;
|
||||
|
||||
if ix == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut iter = self.messages.iter();
|
||||
|
||||
if let Some(previous) = iter.nth(ix - 1)
|
||||
&& let Some(current) = iter.next()
|
||||
{
|
||||
if current.author != previous.author {
|
||||
return true;
|
||||
}
|
||||
|
||||
let gap = current
|
||||
.created_at
|
||||
.as_secs()
|
||||
.saturating_sub(previous.created_at.as_secs());
|
||||
|
||||
return gap > GROUP_WINDOW;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn render_message(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
@@ -805,24 +820,17 @@ impl ChatPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
if let Some(message) = self.messages.iter().nth(ix) {
|
||||
match message {
|
||||
Message::User(rendered) => {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let text = self
|
||||
.rendered_texts_by_id
|
||||
.entry(rendered.id)
|
||||
.or_insert_with(|| {
|
||||
RenderedText::new(&rendered.content, &rendered.mentions, &persons, cx)
|
||||
})
|
||||
.element(ix.into(), window, cx);
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let show_author = self.is_group_start(ix);
|
||||
let text = self
|
||||
.rendered_texts_by_id
|
||||
.entry(message.id)
|
||||
.or_insert_with(|| {
|
||||
RenderedText::new(&message.content, &message.mentions, &persons, cx)
|
||||
})
|
||||
.element(ix.into(), window, cx);
|
||||
|
||||
self.render_text_message(ix, rendered, text, cx)
|
||||
}
|
||||
Message::Warning(content, _timestamp) => {
|
||||
self.render_warning(ix, SharedString::from(content), cx)
|
||||
}
|
||||
Message::System(_timestamp) => self.render_announcement(ix, cx),
|
||||
}
|
||||
self.render_text_message(ix, message, text, show_author, cx)
|
||||
} else {
|
||||
self.render_warning(ix, SharedString::from("Message not found"), cx)
|
||||
}
|
||||
@@ -831,8 +839,9 @@ impl ChatPanel {
|
||||
fn render_text_message(
|
||||
&self,
|
||||
ix: usize,
|
||||
message: &RenderedMessage,
|
||||
message: &Message,
|
||||
rendered_text: AnyElement,
|
||||
show_author: bool,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let id = message.id;
|
||||
@@ -858,17 +867,21 @@ impl ChatPanel {
|
||||
.flex()
|
||||
.gap_3()
|
||||
.when(!hide_avatar, |this| {
|
||||
this.child(
|
||||
Avatar::new(author.avatar())
|
||||
.flex_shrink_0()
|
||||
.relative()
|
||||
.dropdown_menu(move |this, _window, _cx| {
|
||||
this.menu("Public Key", Box::new(Command::Copy(pk)))
|
||||
.menu("View Relays", Box::new(Command::Relays(pk)))
|
||||
.separator()
|
||||
.menu("View on njump.me", Box::new(Command::Njump(pk)))
|
||||
}),
|
||||
)
|
||||
if show_author {
|
||||
this.child(
|
||||
Avatar::new(author.avatar())
|
||||
.flex_shrink_0()
|
||||
.relative()
|
||||
.dropdown_menu(move |this, _window, _cx| {
|
||||
this.menu("Public Key", Box::new(Command::Copy(pk)))
|
||||
.menu("View Relays", Box::new(Command::Relays(pk)))
|
||||
.separator()
|
||||
.menu("View on njump.me", Box::new(Command::Njump(pk)))
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.child(div().flex_shrink_0().w(px(32.)))
|
||||
}
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -876,22 +889,24 @@ impl ChatPanel {
|
||||
.w_full()
|
||||
.flex_initial()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text)
|
||||
.child(author.name()),
|
||||
)
|
||||
.child(message.created_at.to_human_time())
|
||||
.when(has_reports, |this| {
|
||||
this.child(self.render_sent_reports(&id, cx))
|
||||
}),
|
||||
)
|
||||
.when(show_author, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text)
|
||||
.child(author.name()),
|
||||
)
|
||||
.child(message.created_at.to_human_time())
|
||||
.when(has_reports, |this| {
|
||||
this.child(self.render_sent_reports(&id, cx))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(has_replies, |this| {
|
||||
this.children(self.render_message_replies(replies, cx))
|
||||
})
|
||||
@@ -1009,7 +1024,7 @@ impl ChatPanel {
|
||||
.on_click({
|
||||
let id = *id;
|
||||
cx.listener(move |this, _event, _window, _cx| {
|
||||
this.scroll_to(id);
|
||||
this.scroll_to(&id);
|
||||
})
|
||||
}),
|
||||
);
|
||||
@@ -1500,15 +1515,28 @@ impl Render for ChatPanel {
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.relative()
|
||||
.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, ix, window, cx| {
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
.map(|this| {
|
||||
if self.messages.is_empty() {
|
||||
this.child(
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_end()
|
||||
.child(self.render_announcement(cx)),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, ix, window, cx| {
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(Scrollbar::vertical(&self.list_state)),
|
||||
)
|
||||
.child(
|
||||
|
||||
Reference in New Issue
Block a user