Browse Source

ui: Add ability to call mailing list actions from Envelope view

tags/pre-alpha-0.0
Manos Pitsidianakis 2 years ago
parent
commit
9026fb866e
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 104
      ui/src/components/mail/compose.rs
  2. 2
      ui/src/components/mail/listing.rs
  3. 175
      ui/src/components/mail/view.rs
  4. 8
      ui/src/components/utilities.rs
  5. 4
      ui/src/execute/actions.rs

104
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;
}

2
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(_) => {

175
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;
}

8
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;

4
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<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…
Cancel
Save