From 9026fb866e6e77221a5c248de431df144125407e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 18 Jun 2019 22:13:54 +0300 Subject: [PATCH] ui: Add ability to call mailing list actions from Envelope view --- ui/src/components/mail/compose.rs | 104 ++++++++++-------- ui/src/components/mail/listing.rs | 2 +- ui/src/components/mail/view.rs | 175 ++++++++++++++++++++++++++++++ ui/src/components/utilities.rs | 8 +- ui/src/execute/actions.rs | 4 +- 5 files changed, 244 insertions(+), 49 deletions(-) diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index ef12e292b..417421663 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -173,6 +173,11 @@ impl Composer { ret } + pub fn set_draft(&mut self, draft: Draft) { + self.draft = draft; + self.update_form(); + } + fn update_draft(&mut self) { let header_values = self.form.values_mut(); let draft_header_map = self.draft.headers_mut(); @@ -526,51 +531,12 @@ impl Component for Composer { return true; } UIEvent::Input(Key::Char('s')) if self.mode.is_overview() => { - use std::io::Write; - use std::process::{Command, Stdio}; - let settings = &context.settings; - let parts = split_command!(settings.mailer.mailer_cmd); - let (cmd, args) = (parts[0], &parts[1..]); - let mut msmtp = Command::new(cmd) - .args(args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to start mailer command"); - { - let stdin = msmtp.stdin.as_mut().expect("failed to open stdin"); - self.update_draft(); - let draft = self.draft.clone().finalise().unwrap(); - stdin - .write_all(draft.as_bytes()) - .expect("Failed to write to stdin"); - if let Err(e) = context.accounts[self.account_cursor].save( - draft.as_bytes(), - &context.accounts[self.account_cursor] - .settings - .conf() - .sent_folder(), - ) { - debug!("{:?} could not save sent msg", e); - context.replies.push_back(UIEvent::Notification( - Some("Could not save in 'Sent' folder.".into()), - e.into(), - )); - } + self.update_draft(); + if send_draft(context, self.account_cursor, self.draft.clone()) { + context + .replies + .push_back(UIEvent::Action(Tab(Kill(self.id)))); } - context.replies.push_back(UIEvent::Notification( - Some("Sent.".into()), - format!( - "Mailer output: {:#?}", - msmtp - .wait_with_output() - .expect("Failed to wait on filter") - .stdout - ), - )); - context - .replies - .push_back(UIEvent::Action(Tab(Kill(self.id)))); return true; } UIEvent::Input(Key::Char('e')) if self.cursor == Cursor::Body => { @@ -685,3 +651,53 @@ fn get_display_name(context: &Context, idx: usize) -> String { settings.identity.to_string() } } + +pub fn send_draft(context: &mut Context, account_cursor: usize, draft: Draft) -> bool { + use std::io::Write; + use std::process::{Command, Stdio}; + let mut failure = true; + let settings = &context.settings; + let parts = split_command!(settings.mailer.mailer_cmd); + let (cmd, args) = (parts[0], &parts[1..]); + let mut msmtp = Command::new(cmd) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to start mailer command"); + { + let stdin = msmtp.stdin.as_mut().expect("failed to open stdin"); + let draft = draft.finalise().unwrap(); + stdin + .write_all(draft.as_bytes()) + .expect("Failed to write to stdin"); + if let Err(e) = context.accounts[account_cursor].save( + draft.as_bytes(), + &context.accounts[account_cursor] + .settings + .conf() + .sent_folder(), + ) { + debug!("{:?} could not save sent msg", e); + context.replies.push_back(UIEvent::Notification( + Some("Could not save in 'Sent' folder.".into()), + e.into(), + )); + } else { + failure = false; + } + } + if !failure { + context.replies.push_back(UIEvent::Notification( + Some("Sent.".into()), + format!( + "Mailer output: {:#?}", + msmtp + .wait_with_output() + .expect("Failed to wait on filter") + .stdout + ), + )); + } + return !failure; +} diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index a6510a049..94e731749 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -288,7 +288,7 @@ impl Component for Listing { UIEvent::Input(ref k) if k == shortcuts["new_mail"] => { context .replies - .push_back(UIEvent::Action(Tab(NewDraft(self.cursor_pos.0)))); + .push_back(UIEvent::Action(Tab(NewDraft(self.cursor_pos.0, None)))); return true; } UIEvent::StartupCheck(_) => { diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index c9f032d74..ab67aca1e 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -22,6 +22,7 @@ use super::*; use linkify::{Link, LinkFinder}; +use std::convert::TryFrom; use std::process::{Command, Stdio}; mod list_management; @@ -408,6 +409,91 @@ impl Component for MailView { } y = _y; } + if let Some(list_management::ListActions { + ref id, + ref archive, + ref post, + ref unsubscribe, + }) = list_management::detect(envelope) + { + let mut x = get_x(upper_left); + y += 1; + if let Some(id) = id { + let (_x, _) = write_string_to_grid( + "List-ID: ", + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, y), bottom_right), + false, + ); + let (_x, _) = write_string_to_grid( + id, + grid, + Color::Default, + Color::Default, + ((_x, y), bottom_right), + false, + ); + x = _x; + } + if archive.is_some() || post.is_some() || unsubscribe.is_some() { + let (_x, _) = write_string_to_grid( + " Available actions: [ ", + grid, + Color::Byte(33), + Color::Default, + ((x, y), bottom_right), + false, + ); + x = _x; + } + if archive.is_some() { + let (_x, _) = write_string_to_grid( + "list-archive, ", + grid, + Color::Default, + Color::Default, + ((x, y), bottom_right), + false, + ); + x = _x; + } + if post.is_some() { + let (_x, _) = write_string_to_grid( + "list-post, ", + grid, + Color::Default, + Color::Default, + ((x, y), bottom_right), + false, + ); + x = _x; + } + if unsubscribe.is_some() { + let (_x, _) = write_string_to_grid( + "list-unsubscribe, ", + grid, + Color::Default, + Color::Default, + ((x, y), bottom_right), + false, + ); + x = _x; + } + if archive.is_some() || post.is_some() || unsubscribe.is_some() { + grid[(x - 2, y)].set_ch(' '); + grid[(x - 1, y)].set_fg(Color::Byte(33)); + grid[(x - 1, y)].set_bg(Color::Default); + grid[(x - 1, y)].set_ch(']'); + } + for x in x..=get_x(bottom_right) { + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); + } + } + clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 1))); context .dirty_areas @@ -744,6 +830,95 @@ impl Component for MailView { UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => { self.coordinates.2 = new_hash; } + UIEvent::Action(MailingListAction(ref e)) => { + let unsafe_context = context as *mut Context; + let account = &context.accounts[self.coordinates.0]; + if !account.contains_key(self.coordinates.2) { + /* The envelope has been renamed or removed, so wait for the appropriate event to + * arrive */ + return true; + } + let envelope: &Envelope = &account.get_env(&self.coordinates.2); + if let Some(actions) = list_management::detect(envelope) { + match e { + MailingListAction::ListPost if actions.post.is_some() => { + /* open composer */ + let mut draft = Draft::default(); + draft.set_header("To", actions.post.unwrap().to_string()); + context.replies.push_back(UIEvent::Action(Tab(NewDraft( + self.coordinates.0, + Some(draft), + )))); + return true; + } + MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => { + /* autosend or open unsubscribe option*/ + let unsubscribe = actions.unsubscribe.unwrap(); + for option in unsubscribe { + /* TODO: Ask for confirmation before proceding with an action */ + match option { + list_management::UnsubscribeOption::Email(email) => { + if let Ok(mailto) = Mailto::try_from(email) { + let draft: Draft = mailto.into(); + if super::compose::send_draft( + /* FIXME: refactor to avoid unsafe. + * + * actions contains byte slices from the envelope's + * headers send_draft only needs a mut ref for + * context to push back replies and save the sent + * message */ + unsafe { &mut *(unsafe_context) }, + self.coordinates.0, + draft, + ) { + context.replies.push_back(UIEvent::Notification( + Some("Sent unsubscribe email.".into()), + "Sent unsubscribe email".to_string(), + )); + return true; + } + } + } + list_management::UnsubscribeOption::Url(url) => { + if let Err(e) = Command::new("xdg-open") + .arg(String::from_utf8_lossy(url).into_owned()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + { + context.replies.push_back(UIEvent::StatusEvent( + StatusEvent::DisplayMessage(format!( + "Couldn't launch xdg-open: {}", + e + )), + )); + } + return true; + } + } + } + } + MailingListAction::ListArchive if actions.archive.is_some() => { + /* open archive url with xdg-open */ + if let Err(e) = Command::new("xdg-open") + .arg(actions.archive.unwrap()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + { + context.replies.push_back(UIEvent::StatusEvent( + StatusEvent::DisplayMessage(format!( + "Couldn't launch xdg-open: {}", + e + )), + )); + } + return true; + } + _ => { /* error print message to user */ } + } + } + } _ => { return false; } diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 10cb56dd3..5d22d4b3a 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -1247,8 +1247,12 @@ impl Component for Tabbed { self.set_dirty(); return true; } - UIEvent::Action(Tab(NewDraft(account_idx))) => { - self.add_component(Box::new(Composer::new(account_idx))); + UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => { + let mut composer = Composer::new(account_idx); + if let Some(draft) = draft { + composer.set_draft(draft.clone()); + } + self.add_component(Box::new(composer)); self.cursor_pos = self.children.len() - 1; self.children[self.cursor_pos].set_dirty(); return true; diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs index 7745576c4..2a89ef24b 100644 --- a/ui/src/execute/actions.rs +++ b/ui/src/execute/actions.rs @@ -26,7 +26,7 @@ use crate::components::Component; pub use melib::mailbox::{SortField, SortOrder}; use melib::thread::ThreadHash; -use melib::EnvelopeHash; +use melib::{Draft, EnvelopeHash}; extern crate uuid; use uuid::Uuid; @@ -41,7 +41,7 @@ pub enum ListingAction { #[derive(Debug)] pub enum TabAction { TabOpen(Option>), - NewDraft(usize), + NewDraft(usize, Option), Reply((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash Close, Edit(usize, EnvelopeHash), // account_position, envelope hash