diff --git a/Cargo.lock b/Cargo.lock index 03dfff4..08f173c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,9 +247,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ar_archive_writer" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" dependencies = [ "object", ] @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "gpui_util", "indexmap", @@ -1891,7 +1891,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "proc-macro2", "quote", @@ -2865,7 +2865,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "accesskit", "anyhow", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "accesskit", "accesskit_unix", @@ -2998,7 +2998,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "accesskit", "accesskit_macos", @@ -3043,7 +3043,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3054,7 +3054,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "console_error_panic_hook", "gpui", @@ -3067,7 +3067,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "schemars", "serde", @@ -3077,7 +3077,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "gpui", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "log", @@ -3097,7 +3097,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "bytemuck", @@ -3150,7 +3150,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "accesskit", "accesskit_windows", @@ -3439,7 +3439,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "async-compression", @@ -3464,7 +3464,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "rustls", "rustls-platform-verifier", @@ -4322,7 +4322,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "bindgen", @@ -5225,7 +5225,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "collections", "serde", @@ -6018,16 +6018,16 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "derive_refineable", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -6048,9 +6048,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relay_auth" @@ -6118,7 +6118,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "bytes", @@ -6433,7 +6433,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "async-task", "backtrace", @@ -7077,7 +7077,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "heapless 0.9.3", "log", @@ -8071,7 +8071,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "async-fs", @@ -8110,7 +8110,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "perf", "quote", @@ -9875,18 +9875,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -9970,7 +9970,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "anyhow", "chrono", @@ -9987,7 +9987,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" dependencies = [ "tracing", "tracing-subscriber", @@ -9998,7 +9998,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106" +source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c" [[package]] name = "zune-core" diff --git a/crates/chat/src/message.rs b/crates/chat/src/message.rs index 10a8ba6..1d9217c 100644 --- a/crates/chat/src/message.rs +++ b/crates/chat/src/message.rs @@ -5,6 +5,106 @@ use common::{EventExt, NostrParser, extract_and_remove_media_urls}; use gpui::{SharedString, SharedUri}; use nostr_sdk::prelude::*; +/// Rendered message. +#[derive(Debug, Clone)] +pub struct Message { + pub id: EventId, + /// Author's public key + pub author: PublicKey, + /// The content/text of the message + pub content: String, + /// List of media URLs in the message + pub media: Vec, + /// Message created time as unix timestamp + pub created_at: Timestamp, + /// List of mentioned public keys in the message + pub mentions: Vec, + /// List of event of the message this message is a reply to + pub replies_to: Vec, +} + +impl From<&Event> for Message { + fn from(val: &Event) -> Self { + let mentions = extract_mentions(&val.content); + let replies_to = extract_reply_ids(&val.tags); + let (media, string) = extract_and_remove_media_urls(&val.content); + + Self { + id: val.id, + author: val.pubkey, + content: string, + media, + created_at: val.created_at, + mentions, + replies_to, + } + } +} + +impl From<&UnsignedEvent> for Message { + fn from(val: &UnsignedEvent) -> Self { + let mentions = extract_mentions(&val.content); + let replies_to = extract_reply_ids(&val.tags); + let (media, string) = extract_and_remove_media_urls(&val.content); + + Self { + // Event ID must be known + id: val.id.unwrap(), + author: val.pubkey, + content: string, + media, + created_at: val.created_at, + mentions, + replies_to, + } + } +} + +impl From<&NewMessage> for Message { + fn from(val: &NewMessage) -> Self { + let mentions = extract_mentions(&val.rumor.content); + let replies_to = extract_reply_ids(&val.rumor.tags); + let (media, string) = extract_and_remove_media_urls(&val.rumor.content); + + Self { + // Event ID must be known + id: val.rumor.id.unwrap(), + author: val.rumor.pubkey, + content: string, + media, + created_at: val.rumor.created_at, + mentions, + replies_to, + } + } +} + +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); + } +} + /// New message. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct NewMessage { @@ -44,74 +144,6 @@ impl FailedMessage { } } -/// Message. -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum Message { - User(RenderedMessage), - Warning(String, Timestamp), - System(Timestamp), -} - -impl Message { - pub fn user(user: I) -> Self - where - I: Into, - { - Self::User(user.into()) - } - - pub fn warning(content: I) -> Self - where - I: Into, - { - Self::Warning(content.into(), Timestamp::now()) - } - - pub fn system() -> Self { - Self::System(Timestamp::default()) - } - - fn timestamp(&self) -> &Timestamp { - match self { - Message::User(msg) => &msg.created_at, - Message::Warning(_, ts) => ts, - Message::System(ts) => ts, - } - } -} - -impl From<&NewMessage> for Message { - fn from(val: &NewMessage) -> Self { - Self::User(val.into()) - } -} - -impl From<&UnsignedEvent> for Message { - fn from(val: &UnsignedEvent) -> Self { - Self::User(val.into()) - } -} - -impl Ord for Message { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match (self, other) { - // System always comes first - (Message::System(_), Message::System(_)) => self.timestamp().cmp(other.timestamp()), - (Message::System(_), _) => std::cmp::Ordering::Less, - (_, Message::System(_)) => std::cmp::Ordering::Greater, - - // For non-system messages, compare by timestamp - _ => self.timestamp().cmp(other.timestamp()), - } - } -} - -impl PartialOrd for Message { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - #[derive(Debug, Clone)] pub struct Mention { pub public_key: PublicKey, @@ -124,106 +156,6 @@ impl Mention { } } -/// Rendered message. -#[derive(Debug, Clone)] -pub struct RenderedMessage { - pub id: EventId, - /// Author's public key - pub author: PublicKey, - /// The content/text of the message - pub content: String, - /// List of media URLs in the message - pub media: Vec, - /// Message created time as unix timestamp - pub created_at: Timestamp, - /// List of mentioned public keys in the message - pub mentions: Vec, - /// List of event of the message this message is a reply to - pub replies_to: Vec, -} - -impl From<&Event> for RenderedMessage { - fn from(val: &Event) -> Self { - let mentions = extract_mentions(&val.content); - let replies_to = extract_reply_ids(&val.tags); - let (media, string) = extract_and_remove_media_urls(&val.content); - - Self { - id: val.id, - author: val.pubkey, - content: string, - media, - created_at: val.created_at, - mentions, - replies_to, - } - } -} - -impl From<&UnsignedEvent> for RenderedMessage { - fn from(val: &UnsignedEvent) -> Self { - let mentions = extract_mentions(&val.content); - let replies_to = extract_reply_ids(&val.tags); - let (media, string) = extract_and_remove_media_urls(&val.content); - - Self { - // Event ID must be known - id: val.id.unwrap(), - author: val.pubkey, - content: string, - media, - created_at: val.created_at, - mentions, - replies_to, - } - } -} - -impl From<&NewMessage> for RenderedMessage { - fn from(val: &NewMessage) -> Self { - let mentions = extract_mentions(&val.rumor.content); - let replies_to = extract_reply_ids(&val.rumor.tags); - let (media, string) = extract_and_remove_media_urls(&val.rumor.content); - - Self { - // Event ID must be known - id: val.rumor.id.unwrap(), - author: val.rumor.pubkey, - content: string, - media, - created_at: val.rumor.created_at, - mentions, - replies_to, - } - } -} - -impl Eq for RenderedMessage {} - -impl PartialEq for RenderedMessage { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Ord for RenderedMessage { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.created_at.cmp(&other.created_at) - } -} - -impl PartialOrd for RenderedMessage { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Hash for RenderedMessage { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } -} - /// Extracts all mentions (public keys) from a content string. fn extract_mentions(content: &str) -> Vec { let parser = NostrParser::new(); diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index cdc69eb..624c6f5 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -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, window: &mut Window, cx: &mut App) -> Entity { 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) -> AnyElement { + fn render_announcement(&self, cx: &Context) -> 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, ) -> 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, ) -> 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(