diff --git a/Cargo.lock b/Cargo.lock index 42e658c..8f68c5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2088,6 +2088,7 @@ name = "matui" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "config", "crossterm 0.26.1", "dirs 5.0.0", diff --git a/Cargo.toml b/Cargo.toml index 22d5044..575c931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = { version = "1.0", features = ["backtrace"] } +chrono = "0.4" crossterm = "0.26" config = { version = "0.13", features = ["toml"] } dirs = "5.0" diff --git a/src/spawn.rs b/src/spawn.rs index 3f96235..4bd5c79 100644 --- a/src/spawn.rs +++ b/src/spawn.rs @@ -7,11 +7,11 @@ use notify_rust::Hint; use std::env::var; use std::io::{Cursor, Read}; use std::process::{Command, Stdio}; -use tempfile::NamedTempFile; +use tempfile::Builder; pub fn get_text(existing: Option<&str>) -> anyhow::Result> { let editor = &var("EDITOR")?; - let mut tmpfile = NamedTempFile::new()?; + let mut tmpfile = Builder::new().suffix(".md").tempfile()?; if let Some(str) = existing { std::fs::write(&tmpfile, str)?; diff --git a/src/widgets/chat.rs b/src/widgets/chat.rs index cb6e97e..56837a6 100644 --- a/src/widgets/chat.rs +++ b/src/widgets/chat.rs @@ -248,6 +248,19 @@ impl Chat { bail!("Couldn't read from editor.") } } + KeyCode::Char('O') => { + let message = match self.selected_message() { + Some(m) => m, + None => return Ok(EventResult::Ignored), + }; + + handler.park(); + get_text(Some(&message.display_full()))?; + handler.unpark(); + + App::get_sender().send(Event::Redraw)?; + return Ok(EventResult::Consumed(Action::Typing)); + } KeyCode::Char('r') => { self.react = Some(React::new( self.selected_reactions() diff --git a/src/widgets/message.rs b/src/widgets/message.rs index 10893c2..d7e31ff 100644 --- a/src/widgets/message.rs +++ b/src/widgets/message.rs @@ -1,8 +1,10 @@ +use chrono::TimeZone; use std::time::{Duration, SystemTime}; use crate::matrix::matrix::{pad_emoji, Matrix}; use crate::pretty_list; use crate::spawn::view_text; +use chrono::offset::Local; use matrix_sdk::room::RoomMember; use once_cell::unsync::OnceCell; use ruma::events::relation::Replacement; @@ -32,12 +34,13 @@ pub struct Message { pub body: MessageType, pub history: Vec, pub sender: String, + pub sender_id: String, pub reactions: Vec, } impl Message { - pub fn display(&self) -> &str { - match &self.body { + pub fn display_body(body: &MessageType) -> &str { + match body { Text(TextMessageEventContent { body, .. }) => body, Image(ImageMessageEventContent { body, .. }) => body, Video(VideoMessageEventContent { body, .. }) => body, @@ -45,6 +48,59 @@ impl Message { } } + pub fn display(&self) -> &str { + Message::display_body(&self.body) + } + + pub fn display_full(&self) -> String { + let date = Local.timestamp_opt(self.sent.as_secs().into(), 0).unwrap(); + + let mut ret = format!( + "Sent {} by {} ({})\n\n", + date.format("%Y-%m-%d at %I:%M:%S %p"), + self.sender_id, + self.sender_id + ); + + ret.push_str(self.display()); + ret.push_str("\n"); + + if !self.reactions.is_empty() { + ret.push_str("### Reactions\n\n"); + + for r in &self.reactions { + for re in &r.events { + ret.push_str( + format!( + "* {} by {} ({})\n", + r.display(), + re.sender_name, + re.sender_id + ) + .as_str(), + ); + } + } + + ret.push_str("\n"); + } + + if !self.history.is_empty() { + let mut reversed_history = self.history.clone(); + reversed_history.reverse(); + + ret.push_str("### History\n\n"); + + for h in reversed_history.into_iter() { + ret.push_str("* "); + ret.push_str(Message::display_body(&h)); + ret.push_str("\n"); + } + } + + ret + } + pub fn style(&self) -> Style { match &self.body { Text(_) => Style::default(), @@ -88,6 +144,7 @@ impl Message { body, history: vec![], sender: c.sender.to_string(), + sender_id: c.sender.to_string(), reactions: Vec::new(), }); } @@ -214,24 +271,21 @@ impl Message { let wrapped = textwrap::wrap(&self.display(), width); - for l in wrapped { - lines.extend(Text::styled(l, self.style())) + for l in wrapped.iter().take(10) { + lines.extend(Text::styled(l.to_string(), self.style())) } - // reactions - for r in &self.reactions { - let line = if let Some(emoji) = emojis::get(&r.body) { - if let Some(shortcode) = emoji.shortcode() { - format!("{} ({})", pad_emoji(&r.body), shortcode) - } else { - pad_emoji(&r.body) - } - } else { - pad_emoji(&r.body) - }; - - let line = format!("{} {}", line, r.pretty_senders()); + // overflow warning + if wrapped.len() > 10 || self.reactions.len() > 5 { + lines.extend(Text::styled( + "* overflow: type \"O\" to view entire message", + Style::default().fg(Color::Red), + )) + } + // reactions + for r in self.reactions.iter().take(5) { + let line = format!("{} {}", r.display(), r.pretty_senders()); lines.extend(Text::styled(line, Style::default().fg(Color::DarkGray))) } @@ -273,6 +327,18 @@ impl Reaction { merged } + pub fn display(&self) -> String { + if let Some(emoji) = emojis::get(&self.body) { + if let Some(shortcode) = emoji.shortcode() { + format!("{} ({})", pad_emoji(&self.body), shortcode) + } else { + pad_emoji(&self.body) + } + } else { + pad_emoji(&self.body) + } + } + pub fn pretty_senders(&self) -> &str { self.pretty_senders.get_or_init(|| { let all: Vec<&str> = self