ui: Add ability to call mailing list actions from Envelope view
parent
07700ca00f
commit
9026fb866e
|
@ -173,6 +173,11 @@ impl Composer {
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_draft(&mut self, draft: Draft) {
|
||||||
|
self.draft = draft;
|
||||||
|
self.update_form();
|
||||||
|
}
|
||||||
|
|
||||||
fn update_draft(&mut self) {
|
fn update_draft(&mut self) {
|
||||||
let header_values = self.form.values_mut();
|
let header_values = self.form.values_mut();
|
||||||
let draft_header_map = self.draft.headers_mut();
|
let draft_header_map = self.draft.headers_mut();
|
||||||
|
@ -526,51 +531,12 @@ impl Component for Composer {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Input(Key::Char('s')) if self.mode.is_overview() => {
|
UIEvent::Input(Key::Char('s')) if self.mode.is_overview() => {
|
||||||
use std::io::Write;
|
self.update_draft();
|
||||||
use std::process::{Command, Stdio};
|
if send_draft(context, self.account_cursor, self.draft.clone()) {
|
||||||
let settings = &context.settings;
|
context
|
||||||
let parts = split_command!(settings.mailer.mailer_cmd);
|
.replies
|
||||||
let (cmd, args) = (parts[0], &parts[1..]);
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Input(Key::Char('e')) if self.cursor == Cursor::Body => {
|
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()
|
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"] => {
|
UIEvent::Input(ref k) if k == shortcuts["new_mail"] => {
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::Action(Tab(NewDraft(self.cursor_pos.0))));
|
.push_back(UIEvent::Action(Tab(NewDraft(self.cursor_pos.0, None))));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::StartupCheck(_) => {
|
UIEvent::StartupCheck(_) => {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use linkify::{Link, LinkFinder};
|
use linkify::{Link, LinkFinder};
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
mod list_management;
|
mod list_management;
|
||||||
|
@ -408,6 +409,91 @@ impl Component for MailView {
|
||||||
}
|
}
|
||||||
y = _y;
|
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)));
|
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 1)));
|
||||||
context
|
context
|
||||||
.dirty_areas
|
.dirty_areas
|
||||||
|
@ -744,6 +830,95 @@ impl Component for MailView {
|
||||||
UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => {
|
UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => {
|
||||||
self.coordinates.2 = new_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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1247,8 +1247,12 @@ impl Component for Tabbed {
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Action(Tab(NewDraft(account_idx))) => {
|
UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => {
|
||||||
self.add_component(Box::new(Composer::new(account_idx)));
|
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.cursor_pos = self.children.len() - 1;
|
||||||
self.children[self.cursor_pos].set_dirty();
|
self.children[self.cursor_pos].set_dirty();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
pub use melib::mailbox::{SortField, SortOrder};
|
pub use melib::mailbox::{SortField, SortOrder};
|
||||||
use melib::thread::ThreadHash;
|
use melib::thread::ThreadHash;
|
||||||
use melib::EnvelopeHash;
|
use melib::{Draft, EnvelopeHash};
|
||||||
|
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -41,7 +41,7 @@ pub enum ListingAction {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TabAction {
|
pub enum TabAction {
|
||||||
TabOpen(Option<Box<Component>>),
|
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
|
Reply((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash
|
||||||
Close,
|
Close,
|
||||||
Edit(usize, EnvelopeHash), // account_position, envelope hash
|
Edit(usize, EnvelopeHash), // account_position, envelope hash
|
||||||
|
|
Loading…
Reference in New Issue