ui: Add ability to call mailing list actions from Envelope view
parent
07700ca00f
commit
9026fb866e
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(_) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Box<Component>>),
|
||||
NewDraft(usize),
|
||||
NewDraft(usize, Option<Draft>),
|
||||
Reply((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash
|
||||
Close,
|
||||
Edit(usize, EnvelopeHash), // account_position, envelope hash
|
||||
|
|
Loading…
Reference in New Issue