2018-08-11 15:00:21 +00:00
|
|
|
/*
|
2020-02-04 13:52:12 +00:00
|
|
|
* meli
|
2018-08-11 15:00:21 +00:00
|
|
|
*
|
|
|
|
* Copyright 2017-2018 Manos Pitsidianakis
|
|
|
|
*
|
|
|
|
* This file is part of meli.
|
|
|
|
*
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
use super::*;
|
2020-09-09 11:24:30 +00:00
|
|
|
use melib::email::attachment_types::{ContentType, MultipartType};
|
2019-11-18 18:37:48 +00:00
|
|
|
use melib::list_management;
|
2020-07-05 10:22:48 +00:00
|
|
|
use melib::Draft;
|
2018-08-11 15:00:21 +00:00
|
|
|
|
2020-07-05 10:22:48 +00:00
|
|
|
use crate::conf::accounts::JobRequest;
|
2020-10-09 16:34:55 +00:00
|
|
|
use crate::jobs::JoinHandle;
|
2019-11-05 06:35:07 +00:00
|
|
|
use crate::terminal::embed::EmbedGrid;
|
2020-08-18 09:20:23 +00:00
|
|
|
use indexmap::IndexSet;
|
2019-11-05 06:35:07 +00:00
|
|
|
use nix::sys::wait::WaitStatus;
|
2020-08-18 09:20:23 +00:00
|
|
|
use std::convert::TryInto;
|
2020-10-08 13:52:13 +00:00
|
|
|
use std::future::Future;
|
2020-10-09 18:21:15 +00:00
|
|
|
use std::process::{Command, Stdio};
|
2018-09-12 12:10:19 +00:00
|
|
|
use std::str::FromStr;
|
2019-11-05 06:35:07 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
2018-08-29 16:09:51 +00:00
|
|
|
|
2020-10-13 14:06:30 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
mod gpg;
|
|
|
|
|
2020-10-13 14:17:57 +00:00
|
|
|
mod edit_attachments;
|
|
|
|
use edit_attachments::*;
|
|
|
|
|
2019-02-18 21:14:06 +00:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
enum Cursor {
|
2019-03-02 06:11:38 +00:00
|
|
|
Headers,
|
2019-02-18 21:14:06 +00:00
|
|
|
Body,
|
2020-10-09 14:17:11 +00:00
|
|
|
Sign,
|
|
|
|
Encrypt,
|
|
|
|
Attachments,
|
2019-02-18 21:14:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-05 06:35:07 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum EmbedStatus {
|
|
|
|
Stopped(Arc<Mutex<EmbedGrid>>, File),
|
|
|
|
Running(Arc<Mutex<EmbedGrid>>, File),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Deref for EmbedStatus {
|
|
|
|
type Target = Arc<Mutex<EmbedGrid>>;
|
|
|
|
fn deref(&self) -> &Arc<Mutex<EmbedGrid>> {
|
|
|
|
use EmbedStatus::*;
|
|
|
|
match self {
|
|
|
|
Stopped(ref e, _) | Running(ref e, _) => e,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::DerefMut for EmbedStatus {
|
|
|
|
fn deref_mut(&mut self) -> &mut Arc<Mutex<EmbedGrid>> {
|
|
|
|
use EmbedStatus::*;
|
|
|
|
match self {
|
|
|
|
Stopped(ref mut e, _) | Running(ref mut e, _) => e,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 11:39:54 +00:00
|
|
|
#[derive(Debug)]
|
2018-08-16 13:32:47 +00:00
|
|
|
pub struct Composer {
|
2020-02-26 08:54:10 +00:00
|
|
|
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
2020-08-17 12:31:30 +00:00
|
|
|
account_hash: AccountHash,
|
2018-08-30 12:54:30 +00:00
|
|
|
|
2019-02-18 21:14:06 +00:00
|
|
|
cursor: Cursor,
|
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
pager: Pager,
|
2018-08-29 16:09:51 +00:00
|
|
|
draft: Draft,
|
2020-10-13 14:04:40 +00:00
|
|
|
form: FormWidget<bool>,
|
2018-08-30 12:54:30 +00:00
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
mode: ViewMode,
|
2019-11-05 06:35:07 +00:00
|
|
|
|
|
|
|
embed_area: Area,
|
|
|
|
embed: Option<EmbedStatus>,
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
gpg_state: gpg::GpgComposeState,
|
2018-08-30 12:54:30 +00:00
|
|
|
dirty: bool,
|
2019-10-20 08:17:54 +00:00
|
|
|
has_changes: bool,
|
2018-09-03 22:49:29 +00:00
|
|
|
initialized: bool,
|
2019-04-10 19:01:02 +00:00
|
|
|
id: ComponentId,
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 11:39:54 +00:00
|
|
|
#[derive(Debug)]
|
2018-08-16 13:32:47 +00:00
|
|
|
enum ViewMode {
|
2020-02-19 14:57:37 +00:00
|
|
|
Discard(Uuid, UIDialog<char>),
|
2020-10-13 14:17:57 +00:00
|
|
|
EditAttachments {
|
|
|
|
widget: EditAttachments,
|
|
|
|
},
|
2019-03-30 22:28:01 +00:00
|
|
|
Edit,
|
2019-11-05 06:35:07 +00:00
|
|
|
Embed,
|
2020-02-19 14:57:37 +00:00
|
|
|
SelectRecipients(UIDialog<Address>),
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
SelectEncryptKey(bool, gpg::KeySelection),
|
2020-02-22 09:47:13 +00:00
|
|
|
Send(UIConfirmationDialog),
|
2020-10-09 16:34:55 +00:00
|
|
|
WaitingForSendResult(UIDialog<char>, JoinHandle<Result<()>>),
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-08-11 15:00:21 +00:00
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
impl ViewMode {
|
2019-03-30 22:28:01 +00:00
|
|
|
fn is_edit(&self) -> bool {
|
|
|
|
if let ViewMode::Edit = self {
|
2018-09-03 22:49:29 +00:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-10-13 14:17:57 +00:00
|
|
|
|
|
|
|
fn is_edit_attachments(&self) -> bool {
|
|
|
|
if let ViewMode::EditAttachments { .. } = self {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
2018-08-11 15:00:21 +00:00
|
|
|
impl fmt::Display for Composer {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2018-09-03 22:49:29 +00:00
|
|
|
if self.reply_context.is_some() {
|
2020-08-25 21:11:51 +00:00
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"reply: {}",
|
|
|
|
(&self.draft.headers()["Subject"]).trim_at_boundary(8)
|
|
|
|
)
|
2018-09-03 22:49:29 +00:00
|
|
|
} else {
|
2019-11-29 10:15:05 +00:00
|
|
|
write!(f, "composing")
|
2018-09-03 22:49:29 +00:00
|
|
|
}
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-30 12:54:30 +00:00
|
|
|
impl Composer {
|
2019-11-29 10:15:05 +00:00
|
|
|
const DESCRIPTION: &'static str = "composing";
|
2020-10-16 09:35:51 +00:00
|
|
|
pub fn new(context: &Context) -> Self {
|
|
|
|
let mut pager = Pager::new(context);
|
|
|
|
pager.set_show_scrollbar(true);
|
|
|
|
Composer {
|
|
|
|
reply_context: None,
|
|
|
|
account_hash: 0,
|
|
|
|
cursor: Cursor::Headers,
|
|
|
|
pager,
|
|
|
|
draft: Draft::default(),
|
|
|
|
form: FormWidget::default(),
|
|
|
|
mode: ViewMode::Edit,
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
gpg_state: gpg::GpgComposeState::new(),
|
|
|
|
dirty: true,
|
|
|
|
has_changes: false,
|
|
|
|
embed_area: ((0, 0), (0, 0)),
|
|
|
|
embed: None,
|
|
|
|
initialized: false,
|
|
|
|
id: ComponentId::new_v4(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_account(account_hash: AccountHash, context: &Context) -> Self {
|
2020-02-08 21:33:18 +00:00
|
|
|
let mut ret = Composer {
|
2020-08-17 12:31:30 +00:00
|
|
|
account_hash,
|
2020-10-16 09:35:51 +00:00
|
|
|
..Composer::new(context)
|
2020-02-08 21:33:18 +00:00
|
|
|
};
|
2020-03-18 17:13:07 +00:00
|
|
|
for (h, v) in
|
2020-09-12 20:02:06 +00:00
|
|
|
account_settings!(context[account_hash].composing.default_header_values).iter()
|
2020-03-18 17:13:07 +00:00
|
|
|
{
|
2020-03-01 15:45:55 +00:00
|
|
|
if v.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header(h, v.into());
|
2020-03-01 15:45:55 +00:00
|
|
|
}
|
2020-09-12 20:02:06 +00:00
|
|
|
if *account_settings!(context[account_hash].composing.insert_user_agent) {
|
2020-08-18 09:07:50 +00:00
|
|
|
ret.draft.set_header(
|
2020-08-25 09:25:26 +00:00
|
|
|
"User-Agent",
|
2020-08-18 09:07:50 +00:00
|
|
|
format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")),
|
|
|
|
);
|
|
|
|
}
|
2020-11-24 00:12:42 +00:00
|
|
|
if *account_settings!(context[account_hash].composing.format_flowed) {
|
|
|
|
ret.pager
|
|
|
|
.set_reflow(melib::text_processing::Reflow::FormatFlowed);
|
|
|
|
}
|
2020-02-08 21:33:18 +00:00
|
|
|
ret
|
2019-04-05 21:43:50 +00:00
|
|
|
}
|
2020-01-17 23:48:29 +00:00
|
|
|
|
2020-08-25 09:25:26 +00:00
|
|
|
pub fn edit(
|
|
|
|
account_hash: AccountHash,
|
|
|
|
env_hash: EnvelopeHash,
|
|
|
|
bytes: &[u8],
|
|
|
|
context: &Context,
|
|
|
|
) -> Result<Self> {
|
2020-10-16 09:35:51 +00:00
|
|
|
let mut ret = Composer::with_account(account_hash, context);
|
2020-08-25 09:25:26 +00:00
|
|
|
let envelope: EnvelopeRef = context.accounts[&account_hash].collection.get_env(env_hash);
|
2019-03-26 13:26:09 +00:00
|
|
|
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft = Draft::edit(&envelope, bytes)?;
|
2019-03-26 13:26:09 +00:00
|
|
|
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.account_hash = account_hash;
|
2019-09-27 10:18:59 +00:00
|
|
|
Ok(ret)
|
2019-03-26 13:26:09 +00:00
|
|
|
}
|
2020-01-17 23:48:29 +00:00
|
|
|
|
2020-08-18 09:20:23 +00:00
|
|
|
pub fn reply_to(
|
|
|
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
|
|
|
bytes: &[u8],
|
2020-06-23 14:21:50 +00:00
|
|
|
context: &mut Context,
|
2020-08-18 09:20:23 +00:00
|
|
|
reply_to_all: bool,
|
2019-05-14 18:47:47 +00:00
|
|
|
) -> Self {
|
2020-10-16 09:35:51 +00:00
|
|
|
let mut ret = Composer::with_account(coordinates.0, context);
|
2020-08-17 12:31:30 +00:00
|
|
|
let account = &context.accounts[&coordinates.0];
|
2020-08-18 09:20:23 +00:00
|
|
|
let envelope = account.collection.get_env(coordinates.2);
|
|
|
|
let subject = envelope.subject();
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header(
|
|
|
|
"Subject",
|
2020-08-18 09:20:23 +00:00
|
|
|
if !subject.starts_with("Re: ") {
|
|
|
|
format!("Re: {}", subject)
|
|
|
|
} else {
|
|
|
|
subject.into()
|
|
|
|
},
|
|
|
|
);
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header(
|
|
|
|
"References",
|
2020-08-18 09:20:23 +00:00
|
|
|
format!(
|
|
|
|
"{} {}",
|
|
|
|
envelope
|
|
|
|
.references()
|
|
|
|
.iter()
|
|
|
|
.fold(String::new(), |mut acc, x| {
|
|
|
|
if !acc.is_empty() {
|
|
|
|
acc.push(' ');
|
|
|
|
}
|
|
|
|
acc.push_str(&x.to_string());
|
|
|
|
acc
|
|
|
|
}),
|
|
|
|
envelope.message_id_display()
|
|
|
|
),
|
|
|
|
);
|
|
|
|
ret.draft
|
2020-08-25 09:25:26 +00:00
|
|
|
.set_header("In-Reply-To", envelope.message_id_display().into());
|
2020-08-18 09:20:23 +00:00
|
|
|
|
|
|
|
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
|
|
|
|
// Mail-Reply-To/Reply-To/From for reply-to-author."
|
|
|
|
// source: https://cr.yp.to/proto/replyto.html
|
|
|
|
if reply_to_all {
|
|
|
|
let mut to = IndexSet::new();
|
|
|
|
|
|
|
|
if let Some(actions) = list_management::ListActions::detect(&envelope) {
|
|
|
|
if let Some(post) = actions.post {
|
|
|
|
if let list_management::ListAction::Email(list_post_addr) = post[0] {
|
|
|
|
if let Ok(list_address) =
|
|
|
|
melib::email::parser::generic::mailto(list_post_addr)
|
|
|
|
.map(|(_, m)| m.address)
|
|
|
|
{
|
|
|
|
to.insert(list_address);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(reply_to) = envelope
|
|
|
|
.other_headers()
|
|
|
|
.get("Mail-Followup-To")
|
|
|
|
.and_then(|v| v.as_str().try_into().ok())
|
|
|
|
{
|
|
|
|
to.insert(reply_to);
|
|
|
|
} else {
|
|
|
|
if let Some(reply_to) = envelope
|
|
|
|
.other_headers()
|
|
|
|
.get("Reply-To")
|
|
|
|
.and_then(|v| v.as_str().try_into().ok())
|
|
|
|
{
|
|
|
|
to.insert(reply_to);
|
|
|
|
} else {
|
|
|
|
to.extend(envelope.from().iter().cloned());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
to.extend(envelope.to().iter().cloned());
|
|
|
|
if let Some(ours) = TryInto::<Address>::try_into(
|
|
|
|
crate::components::mail::get_display_name(context, coordinates.0).as_str(),
|
|
|
|
)
|
|
|
|
.ok()
|
|
|
|
{
|
|
|
|
to.remove(&ours);
|
|
|
|
}
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header("To", {
|
2020-08-18 09:20:23 +00:00
|
|
|
let mut ret: String =
|
|
|
|
to.into_iter()
|
|
|
|
.fold(String::new(), |mut s: String, n: Address| {
|
|
|
|
s.extend(n.to_string().chars());
|
|
|
|
s.push_str(", ");
|
|
|
|
s
|
|
|
|
});
|
|
|
|
ret.pop();
|
|
|
|
ret.pop();
|
|
|
|
ret
|
|
|
|
});
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header("Cc", envelope.field_cc_to_string());
|
2020-08-18 09:20:23 +00:00
|
|
|
} else {
|
|
|
|
if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header("To", reply_to.to_string());
|
2020-08-18 09:20:23 +00:00
|
|
|
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header("To", reply_to.to_string());
|
2020-08-18 09:20:23 +00:00
|
|
|
} else {
|
2020-08-25 09:25:26 +00:00
|
|
|
ret.draft.set_header("To", envelope.field_from_to_string());
|
2020-08-18 09:20:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let body = envelope.body_bytes(bytes);
|
|
|
|
ret.draft.body = {
|
|
|
|
let reply_body_bytes = decode_rec(&body, None);
|
|
|
|
let reply_body = String::from_utf8_lossy(&reply_body_bytes);
|
|
|
|
let mut ret = format!(
|
|
|
|
"On {} {} wrote:\n",
|
|
|
|
envelope.date_as_str(),
|
|
|
|
envelope.from()[0],
|
|
|
|
);
|
|
|
|
for l in reply_body.lines() {
|
|
|
|
ret.push('>');
|
|
|
|
ret.push_str(l);
|
|
|
|
ret.push('\n');
|
|
|
|
}
|
|
|
|
ret
|
|
|
|
};
|
|
|
|
|
|
|
|
ret.account_hash = coordinates.0;
|
|
|
|
ret.reply_context = Some((coordinates.1, coordinates.2));
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reply_to_select(
|
|
|
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
|
|
|
bytes: &[u8],
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Self {
|
|
|
|
let mut ret = Composer::reply_to(coordinates, bytes, context, false);
|
|
|
|
let account = &context.accounts[&coordinates.0];
|
|
|
|
let parent_message = account.collection.get_env(coordinates.2);
|
2019-10-03 09:22:01 +00:00
|
|
|
/* If message is from a mailing list and we detect a List-Post header, ask user if they
|
|
|
|
* want to reply to the mailing list or the submitter of the message */
|
2019-11-18 12:55:48 +00:00
|
|
|
if let Some(actions) = list_management::ListActions::detect(&parent_message) {
|
2019-10-03 09:22:01 +00:00
|
|
|
if let Some(post) = actions.post {
|
2019-11-18 12:55:48 +00:00
|
|
|
if let list_management::ListAction::Email(list_post_addr) = post[0] {
|
2020-06-06 16:38:20 +00:00
|
|
|
if let Ok(list_address) = melib::email::parser::generic::mailto(list_post_addr)
|
|
|
|
.map(|(_, m)| m.address)
|
2019-11-18 12:55:48 +00:00
|
|
|
{
|
|
|
|
let list_address_string = list_address.to_string();
|
2020-02-19 14:57:37 +00:00
|
|
|
ret.mode = ViewMode::SelectRecipients(UIDialog::new(
|
2019-11-18 12:55:48 +00:00
|
|
|
"select recipients",
|
|
|
|
vec![
|
|
|
|
(
|
|
|
|
parent_message.from()[0].clone(),
|
|
|
|
parent_message.field_from_to_string(),
|
|
|
|
),
|
|
|
|
(list_address, list_address_string),
|
|
|
|
],
|
|
|
|
false,
|
2020-02-22 09:47:13 +00:00
|
|
|
Some(Box::new(move |id: ComponentId, results: &[Address]| {
|
2020-02-19 14:57:37 +00:00
|
|
|
Some(UIEvent::FinishedUIDialog(
|
|
|
|
id,
|
|
|
|
Box::new(
|
|
|
|
results
|
2020-07-05 12:28:55 +00:00
|
|
|
.iter()
|
2020-02-19 14:57:37 +00:00
|
|
|
.map(|a| a.to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", "),
|
|
|
|
),
|
|
|
|
))
|
2020-02-22 09:47:13 +00:00
|
|
|
})),
|
2019-11-18 12:55:48 +00:00
|
|
|
context,
|
|
|
|
));
|
|
|
|
}
|
2019-10-03 09:22:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2020-08-18 09:20:23 +00:00
|
|
|
pub fn reply_to_author(
|
|
|
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
|
|
|
bytes: &[u8],
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Self {
|
|
|
|
Composer::reply_to(coordinates, bytes, context, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reply_to_all(
|
|
|
|
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
|
|
|
bytes: &[u8],
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Self {
|
|
|
|
Composer::reply_to(coordinates, bytes, context, true)
|
|
|
|
}
|
|
|
|
|
2019-06-18 19:13:54 +00:00
|
|
|
pub fn set_draft(&mut self, draft: Draft) {
|
|
|
|
self.draft = draft;
|
|
|
|
self.update_form();
|
|
|
|
}
|
|
|
|
|
2019-03-02 06:11:38 +00:00
|
|
|
fn update_draft(&mut self) {
|
|
|
|
let header_values = self.form.values_mut();
|
|
|
|
let draft_header_map = self.draft.headers_mut();
|
|
|
|
for (k, v) in draft_header_map.iter_mut() {
|
2020-08-25 09:25:26 +00:00
|
|
|
if let Some(ref vn) = header_values.get(k.as_str()) {
|
2019-11-05 06:35:07 +00:00
|
|
|
*v = vn.as_str().to_string();
|
2019-03-02 06:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_form(&mut self) {
|
|
|
|
let old_cursor = self.form.cursor();
|
2020-10-13 14:04:40 +00:00
|
|
|
self.form = FormWidget::new(("Save".into(), true));
|
2019-03-02 06:11:38 +00:00
|
|
|
self.form.hide_buttons();
|
|
|
|
self.form.set_cursor(old_cursor);
|
|
|
|
let headers = self.draft.headers();
|
2020-08-17 12:31:30 +00:00
|
|
|
let account_hash = self.account_hash;
|
2019-03-02 06:11:38 +00:00
|
|
|
for &k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
|
2019-03-25 15:07:00 +00:00
|
|
|
if k == "To" || k == "Cc" || k == "Bcc" {
|
2019-03-14 10:19:25 +00:00
|
|
|
self.form.push_cl((
|
|
|
|
k.into(),
|
2020-10-13 14:04:40 +00:00
|
|
|
headers[k].to_string().into(),
|
2019-03-14 10:19:25 +00:00
|
|
|
Box::new(move |c, term| {
|
2020-08-17 12:31:30 +00:00
|
|
|
let book: &AddressBook = &c.accounts[&account_hash].address_book;
|
2019-03-14 10:19:25 +00:00
|
|
|
let results: Vec<String> = book.search(term);
|
|
|
|
results
|
2019-07-06 17:44:51 +00:00
|
|
|
.into_iter()
|
|
|
|
.map(|r| AutoCompleteEntry::from(r))
|
|
|
|
.collect::<Vec<AutoCompleteEntry>>()
|
2019-03-14 10:19:25 +00:00
|
|
|
}),
|
|
|
|
));
|
2020-10-24 11:33:52 +00:00
|
|
|
} else if k == "From" {
|
|
|
|
self.form.push_cl((
|
|
|
|
k.into(),
|
|
|
|
headers[k].to_string().into(),
|
|
|
|
Box::new(move |c, _term| {
|
|
|
|
let results: Vec<(String, String)> = c
|
|
|
|
.accounts
|
|
|
|
.values()
|
|
|
|
.map(|acc| {
|
|
|
|
let addr = if let Some(display_name) =
|
|
|
|
acc.settings.account.display_name()
|
|
|
|
{
|
|
|
|
format!(
|
|
|
|
"{} <{}>",
|
|
|
|
display_name,
|
|
|
|
acc.settings.account.identity()
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
acc.settings.account.identity().to_string()
|
|
|
|
};
|
|
|
|
let desc =
|
|
|
|
match account_settings!(c[acc.hash()].composing.send_mail) {
|
|
|
|
crate::conf::composing::SendMail::ShellCommand(ref cmd) => {
|
|
|
|
let mut cmd = cmd.as_str();
|
|
|
|
cmd.truncate_at_boundary(10);
|
|
|
|
format!("{} [exec: {}]", acc.name(), cmd)
|
|
|
|
}
|
|
|
|
#[cfg(feature = "smtp")]
|
|
|
|
crate::conf::composing::SendMail::Smtp(ref inner) => {
|
|
|
|
let mut hostname = inner.hostname.as_str();
|
|
|
|
hostname.truncate_at_boundary(10);
|
|
|
|
format!("{} [smtp: {}]", acc.name(), hostname)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
(addr, desc)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
results
|
|
|
|
.into_iter()
|
|
|
|
.map(|r| AutoCompleteEntry::from(r))
|
|
|
|
.collect::<Vec<AutoCompleteEntry>>()
|
|
|
|
}),
|
|
|
|
));
|
2019-03-09 08:24:28 +00:00
|
|
|
} else {
|
2020-10-13 14:04:40 +00:00
|
|
|
self.form.push((k.into(), headers[k].to_string().into()));
|
2019-03-09 08:24:28 +00:00
|
|
|
}
|
2019-03-02 06:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 07:46:49 +00:00
|
|
|
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
|
2019-08-01 09:28:36 +00:00
|
|
|
let attachments_no = self.draft.attachments().len();
|
2020-02-08 11:40:47 +00:00
|
|
|
let theme_default = crate::conf::value(context, "theme_default");
|
|
|
|
clear_area(grid, area, theme_default);
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
if self.gpg_state.sign_mail.is_true() {
|
|
|
|
let key_list = self
|
|
|
|
.gpg_state
|
|
|
|
.sign_keys
|
|
|
|
.iter()
|
|
|
|
.map(|k| k.fingerprint())
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ");
|
2019-09-28 07:46:49 +00:00
|
|
|
write_string_to_grid(
|
|
|
|
&format!(
|
|
|
|
"☑ sign with {}",
|
2020-10-10 21:32:55 +00:00
|
|
|
if self.gpg_state.sign_keys.is_empty() {
|
|
|
|
"default key"
|
|
|
|
} else {
|
|
|
|
key_list.as_str()
|
|
|
|
}
|
2019-09-28 07:46:49 +00:00
|
|
|
),
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Sign {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.attrs,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-09-28 07:46:49 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
"☐ don't sign",
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Sign {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.attrs,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-09-28 07:46:49 +00:00
|
|
|
);
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
if self.gpg_state.encrypt_mail.is_true() {
|
|
|
|
let key_list = self
|
|
|
|
.gpg_state
|
|
|
|
.encrypt_keys
|
|
|
|
.iter()
|
|
|
|
.map(|k| k.fingerprint())
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ");
|
|
|
|
|
2020-10-09 08:58:18 +00:00
|
|
|
write_string_to_grid(
|
|
|
|
&format!(
|
2020-10-10 21:32:55 +00:00
|
|
|
"{}{}",
|
|
|
|
if self.gpg_state.encrypt_keys.is_empty() {
|
|
|
|
"☐ no keys to encrypt with!"
|
|
|
|
} else {
|
|
|
|
"☑ encrypt with "
|
|
|
|
},
|
|
|
|
if self.gpg_state.encrypt_keys.is_empty() {
|
|
|
|
""
|
|
|
|
} else {
|
|
|
|
key_list.as_str()
|
|
|
|
}
|
2020-10-09 08:58:18 +00:00
|
|
|
),
|
|
|
|
grid,
|
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Encrypt {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-10-09 08:58:18 +00:00
|
|
|
theme_default.attrs,
|
|
|
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
"☐ don't encrypt",
|
|
|
|
grid,
|
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Encrypt {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-10-09 08:58:18 +00:00
|
|
|
theme_default.attrs,
|
|
|
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
2019-08-01 09:28:36 +00:00
|
|
|
if attachments_no == 0 {
|
|
|
|
write_string_to_grid(
|
|
|
|
"no attachments",
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Attachments {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.attrs,
|
2020-10-07 14:16:07 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 3)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-08-01 09:28:36 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!("{} attachments ", attachments_no),
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.cursor == Cursor::Attachments {
|
|
|
|
Color::Byte(237)
|
|
|
|
} else {
|
|
|
|
theme_default.bg
|
|
|
|
},
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.attrs,
|
2020-10-07 14:16:07 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 3)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-08-01 09:28:36 +00:00
|
|
|
);
|
|
|
|
for (i, a) in self.draft.attachments().iter().enumerate() {
|
|
|
|
if let Some(name) = a.content_type().name() {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!(
|
2020-10-10 14:54:42 +00:00
|
|
|
"[{}] \"{}\", {} {}",
|
2019-08-01 09:28:36 +00:00
|
|
|
i,
|
|
|
|
name,
|
|
|
|
a.content_type(),
|
2020-10-10 14:54:42 +00:00
|
|
|
melib::Bytes(a.raw.len())
|
2019-08-01 09:28:36 +00:00
|
|
|
),
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
|
|
|
theme_default.bg,
|
|
|
|
theme_default.attrs,
|
2020-10-07 14:16:07 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 4 + i)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-08-01 09:28:36 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
2020-10-10 14:54:42 +00:00
|
|
|
&format!("[{}] {} {}", i, a.content_type(), melib::Bytes(a.raw.len())),
|
2019-08-01 09:28:36 +00:00
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
|
|
|
theme_default.bg,
|
|
|
|
theme_default.attrs,
|
2020-10-07 14:16:07 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 4 + i)), bottom_right!(area)),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-08-01 09:28:36 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-30 12:54:30 +00:00
|
|
|
}
|
|
|
|
|
2018-08-11 15:00:21 +00:00
|
|
|
impl Component for Composer {
|
|
|
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
2018-08-16 13:32:47 +00:00
|
|
|
let upper_left = upper_left!(area);
|
|
|
|
let bottom_right = bottom_right!(area);
|
|
|
|
|
2018-08-29 20:08:23 +00:00
|
|
|
let upper_left = set_y(upper_left, get_y(upper_left) + 1);
|
2019-03-14 10:00:41 +00:00
|
|
|
|
2019-08-01 09:28:36 +00:00
|
|
|
if height!(area) < 4 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-18 12:53:41 +00:00
|
|
|
let width = width!(area);
|
2018-09-03 22:49:29 +00:00
|
|
|
|
2019-03-14 10:00:41 +00:00
|
|
|
if !self.initialized {
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
if self.gpg_state.sign_mail.is_unset() {
|
|
|
|
self.gpg_state.sign_mail = ToggleFlag::InternalVal(*account_settings!(
|
2020-08-17 12:31:30 +00:00
|
|
|
context[self.account_hash].pgp.auto_sign
|
2020-03-18 17:13:07 +00:00
|
|
|
));
|
2019-09-28 07:46:49 +00:00
|
|
|
}
|
2019-04-10 15:57:09 +00:00
|
|
|
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
|
|
|
|
{
|
2020-08-25 09:25:26 +00:00
|
|
|
self.draft.set_header(
|
|
|
|
"From",
|
2020-08-17 12:31:30 +00:00
|
|
|
crate::components::mail::get_display_name(context, self.account_hash),
|
2019-04-10 13:54:25 +00:00
|
|
|
);
|
|
|
|
}
|
2019-03-14 10:00:41 +00:00
|
|
|
self.pager.update_from_str(self.draft.body(), Some(77));
|
|
|
|
self.update_form();
|
|
|
|
self.initialized = true;
|
|
|
|
}
|
2019-08-01 09:28:36 +00:00
|
|
|
let header_height = self.form.len();
|
2020-02-08 11:40:47 +00:00
|
|
|
let theme_default = crate::conf::value(context, "theme_default");
|
2019-03-14 10:00:41 +00:00
|
|
|
|
2018-08-16 13:32:47 +00:00
|
|
|
let mid = if width > 80 {
|
|
|
|
let width = width - 80;
|
2019-11-18 12:53:41 +00:00
|
|
|
let mid = width / 2;
|
2018-08-16 13:32:47 +00:00
|
|
|
|
|
|
|
if self.dirty {
|
|
|
|
for i in get_y(upper_left)..=get_y(bottom_right) {
|
2018-08-29 20:08:23 +00:00
|
|
|
//set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
|
2020-02-08 11:40:47 +00:00
|
|
|
grid[(mid, i)]
|
|
|
|
.set_fg(theme_default.fg)
|
|
|
|
.set_bg(theme_default.bg);
|
2018-08-29 20:08:23 +00:00
|
|
|
//set_and_join_box(grid, (mid + 80, i), VERT_BOUNDARY);
|
2020-02-08 11:40:47 +00:00
|
|
|
grid[(mid + 80, i)]
|
|
|
|
.set_fg(theme_default.fg)
|
|
|
|
.set_bg(theme_default.bg);
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mid
|
2018-08-23 12:36:52 +00:00
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2018-08-16 13:32:47 +00:00
|
|
|
|
2019-11-18 12:53:41 +00:00
|
|
|
let header_area = (
|
|
|
|
set_x(upper_left, mid + 1),
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
2019-11-18 12:53:41 +00:00
|
|
|
get_x(bottom_right).saturating_sub(mid),
|
|
|
|
get_y(upper_left) + header_height,
|
|
|
|
),
|
|
|
|
);
|
2019-08-01 09:28:36 +00:00
|
|
|
let attachments_no = self.draft.attachments().len();
|
2019-11-18 12:53:41 +00:00
|
|
|
let attachment_area = (
|
2020-10-09 14:17:11 +00:00
|
|
|
(
|
|
|
|
mid + 1,
|
|
|
|
get_y(bottom_right).saturating_sub(4 + attachments_no),
|
|
|
|
),
|
2019-11-18 12:53:41 +00:00
|
|
|
pos_dec(bottom_right, (mid, 0)),
|
|
|
|
);
|
2018-08-16 13:32:47 +00:00
|
|
|
|
2019-11-18 12:53:41 +00:00
|
|
|
let body_area = (
|
2020-10-16 09:35:51 +00:00
|
|
|
pos_inc(upper_left, (mid, header_height + 1)),
|
|
|
|
pos_dec(bottom_right, (mid, 5 + attachments_no)),
|
2019-11-18 12:53:41 +00:00
|
|
|
);
|
2019-08-01 09:28:36 +00:00
|
|
|
|
2019-03-22 22:28:17 +00:00
|
|
|
let (x, y) = write_string_to_grid(
|
2019-03-26 17:53:39 +00:00
|
|
|
if self.reply_context.is_some() {
|
|
|
|
"COMPOSING REPLY"
|
|
|
|
} else {
|
|
|
|
"COMPOSING MESSAGE"
|
|
|
|
},
|
|
|
|
grid,
|
|
|
|
Color::Byte(189),
|
|
|
|
Color::Byte(167),
|
2020-04-05 09:04:25 +00:00
|
|
|
Attr::DEFAULT,
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
|
|
|
pos_dec(upper_left!(header_area), (0, 1)),
|
|
|
|
bottom_right!(header_area),
|
|
|
|
),
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-03-26 17:53:39 +00:00
|
|
|
);
|
2020-02-08 11:40:47 +00:00
|
|
|
clear_area(grid, ((x, y), (set_y(bottom_right, y))), theme_default);
|
2019-03-26 17:53:39 +00:00
|
|
|
change_colors(
|
|
|
|
grid,
|
|
|
|
(
|
|
|
|
set_x(pos_dec(upper_left!(header_area), (0, 1)), x),
|
|
|
|
set_y(bottom_right!(header_area), y),
|
|
|
|
),
|
|
|
|
Color::Byte(189),
|
|
|
|
Color::Byte(167),
|
|
|
|
);
|
2020-02-28 07:15:11 +00:00
|
|
|
clear_area(
|
|
|
|
grid,
|
|
|
|
(
|
|
|
|
pos_dec(upper_left, (0, 1)),
|
|
|
|
set_x(bottom_right, get_x(upper_left) + mid),
|
|
|
|
),
|
|
|
|
theme_default,
|
|
|
|
);
|
|
|
|
clear_area(
|
|
|
|
grid,
|
|
|
|
(
|
2019-10-20 08:17:54 +00:00
|
|
|
(
|
2020-02-28 07:15:11 +00:00
|
|
|
get_x(bottom_right).saturating_sub(mid),
|
|
|
|
get_y(upper_left) - 1,
|
2019-10-20 08:17:54 +00:00
|
|
|
),
|
2020-02-28 07:15:11 +00:00
|
|
|
bottom_right,
|
|
|
|
),
|
|
|
|
theme_default,
|
|
|
|
);
|
2019-03-26 17:53:39 +00:00
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
/* Regardless of view mode, do the following */
|
2019-03-26 13:27:02 +00:00
|
|
|
self.form.draw(grid, header_area, context);
|
2019-11-05 06:35:07 +00:00
|
|
|
if let Some(ref mut embed_pty) = self.embed {
|
2019-11-19 20:47:34 +00:00
|
|
|
let embed_area = (upper_left!(header_area), bottom_right!(body_area));
|
2019-11-05 06:35:07 +00:00
|
|
|
match embed_pty {
|
|
|
|
EmbedStatus::Running(_, _) => {
|
|
|
|
let mut guard = embed_pty.lock().unwrap();
|
2020-02-08 11:40:47 +00:00
|
|
|
clear_area(grid, embed_area, theme_default);
|
2019-11-05 06:35:07 +00:00
|
|
|
copy_area(
|
|
|
|
grid,
|
|
|
|
&guard.grid,
|
2019-11-19 20:47:34 +00:00
|
|
|
embed_area,
|
2019-11-05 06:35:07 +00:00
|
|
|
((0, 0), pos_dec(guard.terminal_size, (1, 1))),
|
|
|
|
);
|
2019-11-19 20:47:34 +00:00
|
|
|
guard.set_terminal_size((width!(embed_area), height!(embed_area)));
|
2019-11-05 06:35:07 +00:00
|
|
|
context.dirty_areas.push_back(area);
|
|
|
|
self.dirty = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EmbedStatus::Stopped(_, _) => {
|
2020-02-08 11:40:47 +00:00
|
|
|
clear_area(grid, body_area, theme_default);
|
2019-11-05 06:35:07 +00:00
|
|
|
write_string_to_grid(
|
|
|
|
"process has stopped, press 'e' to re-activate",
|
|
|
|
grid,
|
2020-02-08 11:40:47 +00:00
|
|
|
theme_default.fg,
|
|
|
|
theme_default.bg,
|
|
|
|
theme_default.attrs,
|
2019-11-05 06:35:07 +00:00
|
|
|
body_area,
|
2019-11-18 11:06:30 +00:00
|
|
|
None,
|
2019-11-05 06:35:07 +00:00
|
|
|
);
|
2019-11-19 20:47:34 +00:00
|
|
|
context.dirty_areas.push_back(area);
|
2019-11-05 06:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.embed_area = (upper_left!(header_area), bottom_right!(body_area));
|
|
|
|
}
|
|
|
|
|
2020-10-16 09:35:51 +00:00
|
|
|
if !self.mode.is_edit_attachments() {
|
|
|
|
self.pager.set_dirty(true);
|
|
|
|
if self.pager.size().0 > width!(body_area) {
|
|
|
|
self.pager.set_initialised(false);
|
|
|
|
}
|
|
|
|
self.pager.draw(grid, body_area, context);
|
|
|
|
}
|
|
|
|
|
2020-10-09 14:17:11 +00:00
|
|
|
match self.cursor {
|
|
|
|
Cursor::Headers => {
|
|
|
|
change_colors(
|
|
|
|
grid,
|
|
|
|
(
|
2020-10-16 09:35:51 +00:00
|
|
|
pos_dec(upper_left!(body_area), (1, 0)),
|
|
|
|
pos_dec(
|
|
|
|
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
|
|
|
|
(1, 0),
|
|
|
|
),
|
2020-10-09 14:17:11 +00:00
|
|
|
),
|
|
|
|
theme_default.fg,
|
|
|
|
theme_default.bg,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Cursor::Body => {
|
|
|
|
change_colors(
|
|
|
|
grid,
|
|
|
|
(
|
2020-10-16 09:35:51 +00:00
|
|
|
pos_dec(upper_left!(body_area), (1, 0)),
|
|
|
|
pos_dec(
|
|
|
|
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
|
|
|
|
(1, 0),
|
|
|
|
),
|
2020-10-09 14:17:11 +00:00
|
|
|
),
|
|
|
|
theme_default.fg,
|
|
|
|
Color::Byte(237),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {}
|
2019-10-20 08:17:54 +00:00
|
|
|
}
|
2019-02-18 21:14:06 +00:00
|
|
|
|
|
|
|
match self.mode {
|
2019-11-18 12:53:41 +00:00
|
|
|
ViewMode::Edit | ViewMode::Embed => {}
|
2020-10-13 14:17:57 +00:00
|
|
|
ViewMode::EditAttachments { ref mut widget } => {
|
|
|
|
let inner_area = create_box(
|
|
|
|
grid,
|
|
|
|
(upper_left!(body_area), bottom_right!(attachment_area)),
|
|
|
|
);
|
|
|
|
(EditAttachmentsRefMut {
|
|
|
|
inner: widget,
|
|
|
|
draft: &mut self.draft,
|
|
|
|
})
|
|
|
|
.draw(
|
|
|
|
grid,
|
|
|
|
(
|
|
|
|
pos_inc(upper_left!(inner_area), (1, 1)),
|
|
|
|
bottom_right!(inner_area),
|
|
|
|
),
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
}
|
2019-11-19 20:46:25 +00:00
|
|
|
ViewMode::Send(ref mut s) => {
|
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
ViewMode::SelectEncryptKey(
|
|
|
|
_,
|
|
|
|
gpg::KeySelection::Loaded {
|
|
|
|
ref mut widget,
|
|
|
|
keys: _,
|
|
|
|
},
|
|
|
|
) => {
|
|
|
|
widget.draw(grid, center_area(area, widget.content.size()), context);
|
|
|
|
}
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
ViewMode::SelectEncryptKey(_, _) => {}
|
2019-10-03 09:22:01 +00:00
|
|
|
ViewMode::SelectRecipients(ref mut s) => {
|
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
|
|
|
}
|
2019-10-02 22:03:20 +00:00
|
|
|
ViewMode::Discard(_, ref mut s) => {
|
2019-02-18 21:14:06 +00:00
|
|
|
/* Let user choose whether to quit with/without saving or cancel */
|
2019-10-02 22:03:20 +00:00
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2020-10-09 16:34:55 +00:00
|
|
|
ViewMode::WaitingForSendResult(ref mut s, _) => {
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
/* Let user choose whether to wait for success or cancel */
|
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
|
|
|
}
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2020-10-13 14:17:57 +00:00
|
|
|
if !self.mode.is_edit_attachments() {
|
|
|
|
self.draw_attachments(grid, attachment_area, context);
|
|
|
|
}
|
2019-10-20 08:17:54 +00:00
|
|
|
self.dirty = false;
|
2018-09-03 22:49:29 +00:00
|
|
|
context.dirty_areas.push_back(area);
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2020-02-22 09:47:13 +00:00
|
|
|
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
2019-11-29 10:15:05 +00:00
|
|
|
let shortcuts = self.get_shortcuts(context);
|
2020-02-22 09:47:13 +00:00
|
|
|
match (&mut self.mode, &mut event) {
|
2020-01-17 23:48:29 +00:00
|
|
|
(ViewMode::Edit, _) => {
|
2018-09-03 22:49:29 +00:00
|
|
|
if self.pager.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-13 14:17:57 +00:00
|
|
|
(ViewMode::EditAttachments { ref mut widget }, _) => {
|
|
|
|
if (EditAttachmentsRefMut {
|
|
|
|
inner: widget,
|
|
|
|
draft: &mut self.draft,
|
|
|
|
})
|
|
|
|
.process_event(event, context)
|
|
|
|
{
|
|
|
|
if widget.buttons.result() == Some(FormButtonActions::Cancel) {
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
(ViewMode::Send(ref selector), UIEvent::FinishedUIDialog(id, result))
|
|
|
|
if selector.id() == *id =>
|
|
|
|
{
|
|
|
|
if let Some(true) = result.downcast_ref::<bool>() {
|
|
|
|
self.update_draft();
|
2020-10-08 13:52:13 +00:00
|
|
|
match send_draft_async(
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
self.gpg_state.clone(),
|
2020-02-22 09:47:13 +00:00
|
|
|
context,
|
2020-08-17 12:31:30 +00:00
|
|
|
self.account_hash,
|
2020-02-22 09:47:13 +00:00
|
|
|
self.draft.clone(),
|
|
|
|
SpecialUsageMailbox::Sent,
|
|
|
|
Flag::SEEN,
|
|
|
|
) {
|
2020-10-08 13:52:13 +00:00
|
|
|
Ok(job) => {
|
2020-10-09 16:34:55 +00:00
|
|
|
let handle = context.job_executor.spawn_blocking(job);
|
2020-10-16 19:30:56 +00:00
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::NewJob(
|
|
|
|
handle.job_id,
|
|
|
|
)));
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
self.mode = ViewMode::WaitingForSendResult(
|
|
|
|
UIDialog::new(
|
|
|
|
"Waiting for confirmation.. The tab will close automatically on successful submission.",
|
|
|
|
vec![
|
|
|
|
('c', "force close tab".to_string()),
|
|
|
|
('n', "close this message and return to edit mode".to_string()),
|
|
|
|
],
|
|
|
|
true,
|
|
|
|
Some(Box::new(move |id: ComponentId, results: &[char]| {
|
|
|
|
Some(UIEvent::FinishedUIDialog(
|
|
|
|
id,
|
2020-10-08 13:52:13 +00:00
|
|
|
Box::new(results.get(0).cloned().unwrap_or('c')),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
))
|
|
|
|
})),
|
|
|
|
context,
|
2020-10-09 16:34:55 +00:00
|
|
|
), handle);
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
err.to_string(),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(err.kind)),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
));
|
|
|
|
save_draft(
|
|
|
|
self.draft.clone().finalise().unwrap().as_bytes(),
|
|
|
|
context,
|
|
|
|
SpecialUsageMailbox::Drafts,
|
|
|
|
Flag::SEEN | Flag::DRAFT,
|
2020-08-17 12:31:30 +00:00
|
|
|
self.account_hash,
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
);
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
}
|
|
|
|
}
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
self.set_dirty(true);
|
2020-02-22 09:47:13 +00:00
|
|
|
return true;
|
|
|
|
}
|
2020-10-09 14:18:17 +00:00
|
|
|
(ViewMode::Send(ref dialog), UIEvent::ComponentKill(ref id)) if *id == dialog.id() => {
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
}
|
|
|
|
(ViewMode::SelectRecipients(ref dialog), UIEvent::ComponentKill(ref id))
|
|
|
|
if *id == dialog.id() =>
|
|
|
|
{
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
}
|
|
|
|
(ViewMode::Discard(_, ref dialog), UIEvent::ComponentKill(ref id))
|
|
|
|
if *id == dialog.id() =>
|
|
|
|
{
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
(ViewMode::SelectEncryptKey(_, ref mut selector), UIEvent::ComponentKill(ref id))
|
|
|
|
if *id == selector.id() =>
|
|
|
|
{
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
return true;
|
|
|
|
}
|
2020-01-17 23:48:29 +00:00
|
|
|
(ViewMode::Send(ref mut selector), _) => {
|
2019-11-19 20:46:25 +00:00
|
|
|
if selector.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
(
|
|
|
|
ViewMode::SelectRecipients(ref selector),
|
|
|
|
UIEvent::FinishedUIDialog(id, ref mut result),
|
|
|
|
) if selector.id() == *id => {
|
|
|
|
if let Some(to_val) = result.downcast_mut::<String>() {
|
|
|
|
self.draft
|
2020-08-25 09:25:26 +00:00
|
|
|
.set_header("To", std::mem::replace(to_val, String::new()));
|
2020-02-22 09:47:13 +00:00
|
|
|
self.update_form();
|
|
|
|
}
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
return true;
|
|
|
|
}
|
2020-01-17 23:48:29 +00:00
|
|
|
(ViewMode::SelectRecipients(ref mut selector), _) => {
|
2019-10-03 09:22:01 +00:00
|
|
|
if selector.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
(ViewMode::Discard(u, ref selector), UIEvent::FinishedUIDialog(id, ref mut result))
|
|
|
|
if selector.id() == *id =>
|
|
|
|
{
|
|
|
|
if let Some(key) = result.downcast_mut::<char>() {
|
|
|
|
match key {
|
|
|
|
'x' => {
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(Kill(*u))));
|
|
|
|
return true;
|
2019-10-02 22:03:20 +00:00
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
'n' => {}
|
|
|
|
'y' => {
|
|
|
|
save_draft(
|
|
|
|
self.draft.clone().finalise().unwrap().as_bytes(),
|
|
|
|
context,
|
|
|
|
SpecialUsageMailbox::Drafts,
|
|
|
|
Flag::SEEN | Flag::DRAFT,
|
2020-08-17 12:31:30 +00:00
|
|
|
self.account_hash,
|
2020-02-22 09:47:13 +00:00
|
|
|
);
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(Kill(*u))));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
_ => {}
|
2019-10-02 22:03:20 +00:00
|
|
|
}
|
2020-02-22 09:47:13 +00:00
|
|
|
}
|
|
|
|
self.set_dirty(true);
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
(ViewMode::Discard(_, ref mut selector), _) => {
|
|
|
|
if selector.process_event(event, context) {
|
2019-10-02 22:03:20 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
(
|
2020-10-09 16:34:55 +00:00
|
|
|
ViewMode::WaitingForSendResult(ref selector, _),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
UIEvent::FinishedUIDialog(id, result),
|
|
|
|
) if selector.id() == *id => {
|
|
|
|
if let Some(key) = result.downcast_mut::<char>() {
|
|
|
|
match key {
|
|
|
|
'c' => {
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
'n' => {
|
|
|
|
self.set_dirty(true);
|
2020-10-09 16:34:55 +00:00
|
|
|
if let ViewMode::WaitingForSendResult(_, handle) =
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
std::mem::replace(&mut self.mode, ViewMode::Edit)
|
|
|
|
{
|
2020-08-17 12:31:30 +00:00
|
|
|
context.accounts[&self.account_hash].active_jobs.insert(
|
2020-10-09 16:34:55 +00:00
|
|
|
handle.job_id,
|
|
|
|
JobRequest::SendMessageBackground { handle },
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
(
|
2020-10-09 16:34:55 +00:00
|
|
|
ViewMode::WaitingForSendResult(_, ref mut handle),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)),
|
2020-10-09 16:34:55 +00:00
|
|
|
) if handle.job_id == *job_id => {
|
|
|
|
let result = handle.chan.try_recv().unwrap();
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
if let Some(Err(err)) = result {
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
err.to_string(),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(err.kind)),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
));
|
|
|
|
self.set_dirty(true);
|
|
|
|
} else {
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
|
|
|
}
|
2020-11-15 19:07:53 +00:00
|
|
|
return false;
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
}
|
2020-10-09 16:34:55 +00:00
|
|
|
(ViewMode::WaitingForSendResult(ref mut selector, _), _) => {
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
if selector.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
(
|
|
|
|
ViewMode::SelectEncryptKey(is_encrypt, ref mut selector),
|
|
|
|
UIEvent::FinishedUIDialog(id, result),
|
|
|
|
) if *id == selector.id() => {
|
|
|
|
debug!(&result);
|
|
|
|
if let Some(key) = result.downcast_mut::<Option<melib::gpgme::Key>>() {
|
|
|
|
debug!("got key {:?}", key);
|
|
|
|
if let Some(key) = key {
|
|
|
|
if *is_encrypt {
|
|
|
|
self.gpg_state.encrypt_keys.clear();
|
|
|
|
self.gpg_state.encrypt_keys.push(key.clone());
|
|
|
|
} else {
|
|
|
|
self.gpg_state.sign_keys.clear();
|
|
|
|
self.gpg_state.sign_keys.push(key.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
self.set_dirty(true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
(ViewMode::SelectEncryptKey(_, ref mut selector), _) => {
|
|
|
|
if selector.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
_ => {}
|
2018-08-23 11:39:54 +00:00
|
|
|
}
|
2019-11-19 20:47:34 +00:00
|
|
|
if self.cursor == Cursor::Headers
|
|
|
|
&& self.mode.is_edit()
|
|
|
|
&& self.form.process_event(event, context)
|
|
|
|
{
|
2019-10-20 08:17:54 +00:00
|
|
|
if let UIEvent::InsertInput(_) = event {
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
2019-03-02 06:11:38 +00:00
|
|
|
return true;
|
|
|
|
}
|
2018-08-23 11:39:54 +00:00
|
|
|
|
2019-04-10 20:37:20 +00:00
|
|
|
match *event {
|
|
|
|
UIEvent::Resize => {
|
2019-12-14 16:50:05 +00:00
|
|
|
self.set_dirty(true);
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-03-02 06:11:38 +00:00
|
|
|
/*
|
2018-09-03 22:49:29 +00:00
|
|
|
/* Switch e-mail From: field to the `left` configured account. */
|
2019-04-10 20:37:20 +00:00
|
|
|
UIEvent::Input(Key::Left) if self.cursor == Cursor::From => {
|
2019-03-14 10:19:25 +00:00
|
|
|
self.draft.headers_mut().insert(
|
|
|
|
"From".into(),
|
2020-08-17 12:31:30 +00:00
|
|
|
get_display_name(context, self.account_hash),
|
2019-03-14 10:19:25 +00:00
|
|
|
);
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
2018-08-30 12:54:30 +00:00
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
/* Switch e-mail From: field to the `right` configured account. */
|
2019-04-10 20:37:20 +00:00
|
|
|
UIEvent::Input(Key::Right) if self.cursor == Cursor::From => {
|
2019-03-14 10:19:25 +00:00
|
|
|
if self.account_cursor + 1 < context.accounts.len() {
|
|
|
|
self.account_cursor += 1;
|
|
|
|
self.draft.headers_mut().insert(
|
|
|
|
"From".into(),
|
|
|
|
get_display_name(context, self.account_cursor),
|
|
|
|
);
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
return true;
|
2019-03-02 06:11:38 +00:00
|
|
|
}*/
|
2019-12-14 12:16:12 +00:00
|
|
|
UIEvent::Input(ref key)
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.mode.is_edit()
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["scroll_up"]) =>
|
2019-12-14 12:16:12 +00:00
|
|
|
{
|
2020-10-09 14:17:11 +00:00
|
|
|
self.cursor = match self.cursor {
|
|
|
|
Cursor::Headers => return true,
|
|
|
|
Cursor::Body => {
|
|
|
|
self.form.process_event(event, context);
|
|
|
|
Cursor::Headers
|
|
|
|
}
|
|
|
|
Cursor::Sign => Cursor::Body,
|
|
|
|
Cursor::Encrypt => Cursor::Sign,
|
|
|
|
Cursor::Attachments => Cursor::Encrypt,
|
|
|
|
};
|
2019-10-20 08:17:54 +00:00
|
|
|
self.dirty = true;
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-12-14 12:16:12 +00:00
|
|
|
UIEvent::Input(ref key)
|
2020-10-09 14:17:11 +00:00
|
|
|
if self.mode.is_edit()
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["scroll_down"]) =>
|
2019-12-14 12:16:12 +00:00
|
|
|
{
|
2020-10-09 14:17:11 +00:00
|
|
|
self.cursor = match self.cursor {
|
|
|
|
Cursor::Headers => Cursor::Body,
|
|
|
|
Cursor::Body => Cursor::Sign,
|
|
|
|
Cursor::Sign => Cursor::Encrypt,
|
|
|
|
Cursor::Encrypt => Cursor::Attachments,
|
|
|
|
Cursor::Attachments => return true,
|
|
|
|
};
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
UIEvent::Input(Key::Char('\n'))
|
|
|
|
if self.mode.is_edit()
|
|
|
|
&& (self.cursor == Cursor::Sign || self.cursor == Cursor::Encrypt) =>
|
|
|
|
{
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
2020-10-09 14:17:11 +00:00
|
|
|
match self.cursor {
|
|
|
|
Cursor::Sign => {
|
2020-10-10 21:32:55 +00:00
|
|
|
let is_true = self.gpg_state.sign_mail.is_true();
|
|
|
|
self.gpg_state.sign_mail = ToggleFlag::from(!is_true);
|
2020-10-09 14:17:11 +00:00
|
|
|
}
|
|
|
|
Cursor::Encrypt => {
|
2020-10-10 21:32:55 +00:00
|
|
|
let is_true = self.gpg_state.encrypt_mail.is_true();
|
|
|
|
self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true);
|
2020-10-09 14:17:11 +00:00
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
};
|
2019-10-20 08:17:54 +00:00
|
|
|
self.dirty = true;
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-11-29 10:15:05 +00:00
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if shortcut!(key == shortcuts[Self::DESCRIPTION]["send_mail"])
|
|
|
|
&& self.mode.is_edit() =>
|
|
|
|
{
|
2019-06-18 19:13:54 +00:00
|
|
|
self.update_draft();
|
2020-02-22 09:47:13 +00:00
|
|
|
self.mode = ViewMode::Send(UIConfirmationDialog::new(
|
2019-11-19 20:46:25 +00:00
|
|
|
"send mail?",
|
|
|
|
vec![(true, "yes".to_string()), (false, "no".to_string())],
|
|
|
|
/* only one choice */
|
|
|
|
true,
|
2020-02-22 09:47:13 +00:00
|
|
|
Some(Box::new(move |id: ComponentId, result: bool| {
|
|
|
|
Some(UIEvent::FinishedUIDialog(id, Box::new(result)))
|
|
|
|
})),
|
2019-09-28 07:46:49 +00:00
|
|
|
context,
|
2019-11-19 20:46:25 +00:00
|
|
|
));
|
2019-04-05 22:08:33 +00:00
|
|
|
return true;
|
|
|
|
}
|
2019-11-05 06:35:07 +00:00
|
|
|
UIEvent::EmbedInput((Key::Ctrl('z'), _)) => {
|
|
|
|
self.embed.as_ref().unwrap().lock().unwrap().stop();
|
2019-11-19 20:47:34 +00:00
|
|
|
match self.embed.take() {
|
|
|
|
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => {
|
|
|
|
self.embed = Some(EmbedStatus::Stopped(e, f));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2019-11-05 06:35:07 +00:00
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
UIEvent::EmbedInput((ref k, ref b)) => {
|
|
|
|
use std::io::Write;
|
|
|
|
if let Some(ref mut embed) = self.embed {
|
|
|
|
let mut embed_guard = embed.lock().unwrap();
|
|
|
|
if embed_guard.stdin.write_all(b).is_err() {
|
|
|
|
match embed_guard.is_active() {
|
|
|
|
Ok(WaitStatus::Exited(_, exit_code)) => {
|
|
|
|
drop(embed_guard);
|
|
|
|
if exit_code != 0 {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
format!(
|
|
|
|
"Subprocess has exited with exit code {}",
|
|
|
|
exit_code
|
|
|
|
),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(
|
|
|
|
melib::error::ErrorKind::External,
|
|
|
|
)),
|
2019-11-05 06:35:07 +00:00
|
|
|
));
|
|
|
|
} else if let EmbedStatus::Running(_, f) = embed {
|
|
|
|
let result = f.read_to_string();
|
|
|
|
match Draft::from_str(result.as_str()) {
|
|
|
|
Ok(mut new_draft) => {
|
|
|
|
std::mem::swap(
|
|
|
|
self.draft.attachments_mut(),
|
|
|
|
new_draft.attachments_mut(),
|
|
|
|
);
|
|
|
|
if self.draft != new_draft {
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
|
|
|
self.draft = new_draft;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
Err(err) => {
|
2019-11-05 06:35:07 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2020-09-13 12:23:14 +00:00
|
|
|
Some("Could not parse draft headers correctly.".to_string()),
|
|
|
|
format!("{}\nThe invalid text has been set as the body of your draft", &err),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
|
|
|
));
|
2019-11-05 06:35:07 +00:00
|
|
|
self.draft.set_body(result);
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.initialized = false;
|
|
|
|
}
|
|
|
|
self.embed = None;
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
Ok(WaitStatus::PtraceEvent(_, _, _))
|
|
|
|
| Ok(WaitStatus::PtraceSyscall(_)) => {
|
|
|
|
drop(embed_guard);
|
|
|
|
match self.embed.take() {
|
|
|
|
Some(EmbedStatus::Running(e, f))
|
|
|
|
| Some(EmbedStatus::Stopped(e, f)) => {
|
|
|
|
self.embed = Some(EmbedStatus::Stopped(e, f));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2019-11-05 06:35:07 +00:00
|
|
|
Ok(WaitStatus::Stopped(_, _)) => {
|
|
|
|
drop(embed_guard);
|
|
|
|
match self.embed.take() {
|
|
|
|
Some(EmbedStatus::Running(e, f))
|
|
|
|
| Some(EmbedStatus::Stopped(e, f)) => {
|
|
|
|
self.embed = Some(EmbedStatus::Stopped(e, f));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2019-11-19 20:47:34 +00:00
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
2019-11-05 06:35:07 +00:00
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Ok(WaitStatus::Continued(_)) | Ok(WaitStatus::StillAlive) => {
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::EmbedInput((k.clone(), b.to_vec())));
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
Ok(WaitStatus::Signaled(_, signal, _)) => {
|
|
|
|
drop(embed_guard);
|
2019-11-05 06:35:07 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
2020-09-13 12:23:14 +00:00
|
|
|
format!("Subprocess was killed by {} signal", signal),
|
|
|
|
Some(NotificationType::Error(
|
|
|
|
melib::error::ErrorKind::External,
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
self.embed = None;
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("Embed editor crashed.".to_string()),
|
|
|
|
format!("Subprocess has exited with reason {}", &err),
|
|
|
|
Some(NotificationType::Error(
|
|
|
|
melib::error::ErrorKind::External,
|
|
|
|
)),
|
2019-11-05 06:35:07 +00:00
|
|
|
));
|
|
|
|
drop(embed_guard);
|
|
|
|
self.embed = None;
|
|
|
|
self.mode = ViewMode::Edit;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Normal));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-14 16:50:05 +00:00
|
|
|
self.set_dirty(true);
|
2019-11-05 06:35:07 +00:00
|
|
|
return true;
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if self.mode.is_edit()
|
|
|
|
&& self.cursor == Cursor::Sign
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
|
|
|
|
{
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
match melib::email::parser::address::rfc2822address_list(
|
|
|
|
self.form.values()["From"].as_str().as_bytes(),
|
|
|
|
)
|
|
|
|
.map_err(|_err| -> MeliError { "No valid sender address in `From:`".into() })
|
|
|
|
.and_then(|(_, list)| {
|
|
|
|
list.get(0)
|
|
|
|
.cloned()
|
|
|
|
.ok_or_else(|| "No valid sender address in `From:`".into())
|
|
|
|
})
|
|
|
|
.and_then(|addr| {
|
|
|
|
gpg::KeySelection::new(
|
|
|
|
false,
|
|
|
|
account_settings!(context[self.account_hash].pgp.allow_remote_lookup)
|
|
|
|
.is_true(),
|
|
|
|
addr.get_email(),
|
|
|
|
*account_settings!(context[self.account_hash].pgp.allow_remote_lookup),
|
|
|
|
context,
|
|
|
|
)
|
|
|
|
}) {
|
|
|
|
Ok(widget) => {
|
|
|
|
self.gpg_state.sign_mail = ToggleFlag::from(true);
|
|
|
|
self.mode = ViewMode::SelectEncryptKey(false, widget);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("Could not list keys.".to_string()),
|
|
|
|
format!("libgpgme error: {}", &err),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.set_dirty(true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if self.mode.is_edit()
|
|
|
|
&& self.cursor == Cursor::Encrypt
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
|
|
|
|
{
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
match melib::email::parser::address::rfc2822address_list(
|
|
|
|
self.form.values()["To"].as_str().as_bytes(),
|
|
|
|
)
|
|
|
|
.map_err(|_err| -> MeliError { "No valid recipient addresses in `To:`".into() })
|
|
|
|
.and_then(|(_, list)| {
|
|
|
|
list.get(0)
|
|
|
|
.cloned()
|
|
|
|
.ok_or_else(|| "No valid recipient addresses in `To:`".into())
|
|
|
|
})
|
|
|
|
.and_then(|addr| {
|
|
|
|
gpg::KeySelection::new(
|
|
|
|
false,
|
|
|
|
account_settings!(context[self.account_hash].pgp.allow_remote_lookup)
|
|
|
|
.is_true(),
|
|
|
|
addr.get_email(),
|
|
|
|
*account_settings!(context[self.account_hash].pgp.allow_remote_lookup),
|
|
|
|
context,
|
|
|
|
)
|
|
|
|
}) {
|
|
|
|
Ok(widget) => {
|
|
|
|
self.gpg_state.encrypt_mail = ToggleFlag::from(true);
|
|
|
|
self.mode = ViewMode::SelectEncryptKey(true, widget);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("Could not list keys.".to_string()),
|
|
|
|
format!("libgpgme error: {}", &err),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.set_dirty(true);
|
|
|
|
return true;
|
|
|
|
}
|
2020-10-13 14:17:57 +00:00
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if self.mode.is_edit()
|
|
|
|
&& self.cursor == Cursor::Attachments
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
|
|
|
|
{
|
|
|
|
self.mode = ViewMode::EditAttachments {
|
|
|
|
widget: EditAttachments::new(),
|
|
|
|
};
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-11-29 10:15:05 +00:00
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if self.embed.is_some()
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
|
|
|
|
{
|
2019-11-05 06:35:07 +00:00
|
|
|
self.embed.as_ref().unwrap().lock().unwrap().wake_up();
|
2019-11-19 20:47:34 +00:00
|
|
|
match self.embed.take() {
|
|
|
|
Some(EmbedStatus::Running(e, f)) | Some(EmbedStatus::Stopped(e, f)) => {
|
|
|
|
self.embed = Some(EmbedStatus::Running(e, f));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
self.mode = ViewMode::Embed;
|
2019-11-05 06:35:07 +00:00
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
2019-12-14 16:50:05 +00:00
|
|
|
self.set_dirty(true);
|
2019-11-05 06:35:07 +00:00
|
|
|
return true;
|
|
|
|
}
|
2019-11-29 10:15:05 +00:00
|
|
|
UIEvent::Input(ref key)
|
|
|
|
if self.mode.is_edit()
|
|
|
|
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
|
|
|
|
{
|
2019-03-14 10:19:25 +00:00
|
|
|
/* Edit draft in $EDITOR */
|
2020-07-08 09:09:37 +00:00
|
|
|
let editor = if let Some(editor_command) =
|
2020-09-12 20:02:06 +00:00
|
|
|
account_settings!(context[self.account_hash].composing.editor_command).as_ref()
|
2020-03-18 17:13:07 +00:00
|
|
|
{
|
2020-07-08 09:09:37 +00:00
|
|
|
editor_command.to_string()
|
2019-09-27 09:48:48 +00:00
|
|
|
} else {
|
|
|
|
match std::env::var("EDITOR") {
|
2020-09-13 12:23:14 +00:00
|
|
|
Err(err) => {
|
2019-09-27 09:48:48 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(err.to_string()),
|
2020-07-08 09:09:37 +00:00
|
|
|
"$EDITOR is not set. You can change an envvar's value with setenv or set composing.editor_command setting in your configuration.".to_string(),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
2019-08-01 09:26:35 +00:00
|
|
|
));
|
2019-09-27 09:48:48 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Ok(v) => v,
|
2019-08-01 09:26:35 +00:00
|
|
|
}
|
|
|
|
};
|
2019-03-14 10:19:25 +00:00
|
|
|
/* update Draft's headers based on form values */
|
|
|
|
self.update_draft();
|
2019-07-31 10:29:55 +00:00
|
|
|
let f = create_temp_file(
|
|
|
|
self.draft.to_string().unwrap().as_str().as_bytes(),
|
|
|
|
None,
|
|
|
|
None,
|
2019-09-23 06:30:23 +00:00
|
|
|
true,
|
2019-07-31 10:29:55 +00:00
|
|
|
);
|
2019-03-14 10:19:25 +00:00
|
|
|
|
2020-09-12 20:02:06 +00:00
|
|
|
if *account_settings!(context[self.account_hash].composing.embed) {
|
2019-11-05 06:35:07 +00:00
|
|
|
self.embed = Some(EmbedStatus::Running(
|
|
|
|
crate::terminal::embed::create_pty(
|
2019-11-19 18:39:43 +00:00
|
|
|
width!(self.embed_area),
|
|
|
|
height!(self.embed_area),
|
2019-11-05 06:35:07 +00:00
|
|
|
[editor, f.path().display().to_string()].join(" "),
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
f,
|
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::ChangeMode(UIMode::Embed));
|
2019-11-19 20:47:34 +00:00
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Embed(
|
|
|
|
self.embed.as_ref().unwrap().lock().unwrap().child_pid,
|
|
|
|
)));
|
2019-11-05 06:35:07 +00:00
|
|
|
self.mode = ViewMode::Embed;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
|
|
|
{
|
|
|
|
context.input_kill();
|
|
|
|
}
|
|
|
|
|
2020-07-08 09:09:37 +00:00
|
|
|
let editor_command = format!("{} {}", editor, f.path().display());
|
2020-05-28 13:27:02 +00:00
|
|
|
log(
|
2020-07-08 09:09:37 +00:00
|
|
|
format!(
|
|
|
|
"Executing: sh -c \"{}\"",
|
|
|
|
editor_command.replace("\"", "\\\"")
|
|
|
|
),
|
2020-05-28 13:27:02 +00:00
|
|
|
DEBUG,
|
|
|
|
);
|
|
|
|
match Command::new("sh")
|
2020-07-08 09:09:37 +00:00
|
|
|
.args(&["-c", &editor_command])
|
2019-03-14 10:19:25 +00:00
|
|
|
.stdin(Stdio::inherit())
|
|
|
|
.stdout(Stdio::inherit())
|
2020-03-01 15:56:58 +00:00
|
|
|
.spawn()
|
2019-08-01 09:26:35 +00:00
|
|
|
{
|
2020-03-01 15:56:58 +00:00
|
|
|
Ok(mut child) => {
|
|
|
|
let _ = child.wait();
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!("Failed to execute {}: {}", editor, err)),
|
|
|
|
err.to_string(),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
2020-03-01 15:56:58 +00:00
|
|
|
));
|
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
|
|
|
context.restore_input();
|
|
|
|
return true;
|
|
|
|
}
|
2019-08-01 09:26:35 +00:00
|
|
|
}
|
2019-11-05 06:35:07 +00:00
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
2019-03-14 10:19:25 +00:00
|
|
|
let result = f.read_to_string();
|
2019-11-05 06:35:07 +00:00
|
|
|
match Draft::from_str(result.as_str()) {
|
|
|
|
Ok(mut new_draft) => {
|
|
|
|
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
|
|
|
|
if self.draft != new_draft {
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
|
|
|
self.draft = new_draft;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
Err(err) => {
|
2019-11-05 06:35:07 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2020-09-13 12:23:14 +00:00
|
|
|
Some("Could not parse draft headers correctly.".to_string()),
|
|
|
|
format!(
|
|
|
|
"{}\nThe invalid text has been set as the body of your draft",
|
|
|
|
&err
|
|
|
|
),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
|
|
|
));
|
2019-11-05 06:35:07 +00:00
|
|
|
self.draft.set_body(result);
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
2019-10-20 08:17:54 +00:00
|
|
|
}
|
2019-03-14 10:19:25 +00:00
|
|
|
self.initialized = false;
|
2018-08-16 13:32:47 +00:00
|
|
|
self.dirty = true;
|
2018-08-23 11:39:54 +00:00
|
|
|
return true;
|
2018-08-23 12:36:52 +00:00
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
UIEvent::Action(ref a) => match a {
|
|
|
|
Action::Compose(ComposeAction::AddAttachmentPipe(ref command)) => {
|
|
|
|
if command.is_empty() {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
format!("pipe command value is invalid: {}", command),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
|
|
|
));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let f = create_temp_file(&[], None, None, true);
|
2020-10-09 18:21:15 +00:00
|
|
|
match Command::new("sh")
|
2020-09-13 12:23:14 +00:00
|
|
|
.args(&["-c", command])
|
2020-10-09 18:21:15 +00:00
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(Stdio::from(f.file()))
|
2020-09-13 12:23:14 +00:00
|
|
|
.spawn()
|
2020-10-09 18:21:15 +00:00
|
|
|
.and_then(|child| Ok(child.wait_with_output()?.stderr))
|
2020-09-13 12:23:14 +00:00
|
|
|
{
|
2020-10-09 18:21:15 +00:00
|
|
|
Ok(stderr) => {
|
|
|
|
if !stderr.is_empty() {
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
"Command stderr output: `{}`.",
|
|
|
|
String::from_utf8_lossy(&stderr)
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
}
|
2020-09-16 16:57:06 +00:00
|
|
|
let attachment =
|
2020-09-13 12:23:14 +00:00
|
|
|
match melib::email::compose::attachment_from_file(f.path()) {
|
|
|
|
Ok(a) => a,
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("could not add attachment".to_string()),
|
|
|
|
err.to_string(),
|
|
|
|
Some(NotificationType::Error(
|
|
|
|
melib::error::ErrorKind::None,
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
2020-03-01 15:47:23 +00:00
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
};
|
|
|
|
self.draft.attachments_mut().push(attachment);
|
2020-10-09 18:21:15 +00:00
|
|
|
self.has_changes = true;
|
2020-09-13 12:23:14 +00:00
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
2020-03-01 15:47:23 +00:00
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
format!("could not execute pipe command {}: {}", command, &err),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
|
|
|
));
|
|
|
|
return true;
|
2019-08-01 09:28:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
}
|
|
|
|
Action::Compose(ComposeAction::AddAttachment(ref path)) => {
|
2020-09-16 16:57:06 +00:00
|
|
|
let attachment = match melib::email::compose::attachment_from_file(path) {
|
2020-09-13 12:23:14 +00:00
|
|
|
Ok(a) => a,
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("could not add attachment".to_string()),
|
|
|
|
err.to_string(),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
2019-08-01 09:28:36 +00:00
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
};
|
|
|
|
self.draft.attachments_mut().push(attachment);
|
2020-10-09 18:21:15 +00:00
|
|
|
self.has_changes = true;
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Action::Compose(ComposeAction::AddAttachmentFilePicker(ref command)) => {
|
|
|
|
let command = if let Some(ref cmd) = command
|
|
|
|
.as_ref()
|
|
|
|
.or_else(|| context.settings.terminal.file_picker_command.as_ref())
|
|
|
|
{
|
|
|
|
cmd.as_str()
|
|
|
|
} else {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
"You haven't defined any command to launch.".into(),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::None)),
|
|
|
|
));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
|
|
|
{
|
|
|
|
context.input_kill();
|
|
|
|
}
|
|
|
|
|
|
|
|
log(
|
|
|
|
format!("Executing: sh -c \"{}\"", command.replace("\"", "\\\"")),
|
|
|
|
DEBUG,
|
|
|
|
);
|
|
|
|
match Command::new("sh")
|
|
|
|
.args(&["-c", command])
|
|
|
|
.stdin(Stdio::inherit())
|
|
|
|
.stdout(Stdio::inherit())
|
|
|
|
.stderr(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.and_then(|child| Ok(child.wait_with_output()?.stderr))
|
|
|
|
{
|
|
|
|
Ok(stderr) => {
|
|
|
|
debug!(&String::from_utf8_lossy(&stderr));
|
|
|
|
for path in stderr.split(|c| [b'\0', b'\t', b'\n'].contains(c)) {
|
|
|
|
match melib::email::compose::attachment_from_file(
|
|
|
|
&String::from_utf8_lossy(&path).as_ref(),
|
|
|
|
) {
|
|
|
|
Ok(a) => {
|
|
|
|
self.draft.attachments_mut().push(a);
|
|
|
|
self.has_changes = true;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!(
|
|
|
|
"could not add attachment: {}",
|
|
|
|
String::from_utf8_lossy(&path)
|
|
|
|
)),
|
|
|
|
err.to_string(),
|
|
|
|
Some(NotificationType::Error(
|
|
|
|
melib::error::ErrorKind::None,
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
let command = command.to_string();
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!("Failed to execute {}: {}", command, err)),
|
|
|
|
err.to_string(),
|
|
|
|
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
|
|
|
));
|
|
|
|
context.restore_input();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
2020-09-13 12:23:14 +00:00
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Action::Compose(ComposeAction::RemoveAttachment(idx)) => {
|
|
|
|
if *idx + 1 > self.draft.attachments().len() {
|
2019-08-01 09:28:36 +00:00
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
2020-09-13 12:23:14 +00:00
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
"attachment with given index does not exist".to_string(),
|
|
|
|
),
|
2019-08-01 09:28:36 +00:00
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
self.draft.attachments_mut().remove(*idx);
|
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
|
|
|
|
"attachment removed".to_string(),
|
|
|
|
)));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
2019-08-01 09:28:36 +00:00
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
Action::Compose(ComposeAction::SaveDraft) => {
|
|
|
|
save_draft(
|
|
|
|
self.draft.clone().finalise().unwrap().as_bytes(),
|
|
|
|
context,
|
|
|
|
SpecialUsageMailbox::Drafts,
|
|
|
|
Flag::SEEN | Flag::DRAFT,
|
|
|
|
self.account_hash,
|
|
|
|
);
|
|
|
|
return true;
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
2020-09-13 12:23:14 +00:00
|
|
|
Action::Compose(ComposeAction::ToggleSign) => {
|
2020-10-10 21:32:55 +00:00
|
|
|
let is_true = self.gpg_state.sign_mail.is_true();
|
|
|
|
self.gpg_state.sign_mail = ToggleFlag::from(!is_true);
|
2020-09-13 12:23:14 +00:00
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
2020-10-07 14:16:07 +00:00
|
|
|
Action::Compose(ComposeAction::ToggleEncrypt) => {
|
2020-10-10 21:32:55 +00:00
|
|
|
let is_true = self.gpg_state.encrypt_mail.is_true();
|
|
|
|
self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true);
|
2020-10-09 08:58:18 +00:00
|
|
|
self.dirty = true;
|
2020-10-07 14:16:07 +00:00
|
|
|
return true;
|
|
|
|
}
|
2020-09-13 12:23:14 +00:00
|
|
|
_ => {}
|
|
|
|
},
|
2018-08-23 12:36:52 +00:00
|
|
|
_ => {}
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-08-23 11:39:54 +00:00
|
|
|
false
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-08-11 15:00:21 +00:00
|
|
|
|
|
|
|
fn is_dirty(&self) -> bool {
|
2019-11-05 06:35:07 +00:00
|
|
|
match self.mode {
|
|
|
|
ViewMode::Embed => true,
|
2020-10-13 14:17:57 +00:00
|
|
|
ViewMode::EditAttachments { ref widget } => widget.dirty || widget.buttons.is_dirty(),
|
2020-10-10 14:53:53 +00:00
|
|
|
ViewMode::Edit => self.dirty || self.pager.is_dirty() || self.form.is_dirty(),
|
|
|
|
ViewMode::Discard(_, ref widget) => {
|
|
|
|
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
|
|
|
|
}
|
|
|
|
ViewMode::SelectRecipients(ref widget) => {
|
|
|
|
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
|
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
ViewMode::SelectEncryptKey(_, ref widget) => {
|
|
|
|
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
|
|
|
|
}
|
2020-10-10 14:53:53 +00:00
|
|
|
ViewMode::Send(ref widget) => {
|
|
|
|
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
|
|
|
|
}
|
|
|
|
ViewMode::WaitingForSendResult(ref widget, _) => {
|
|
|
|
widget.is_dirty() || self.pager.is_dirty() || self.form.is_dirty()
|
|
|
|
}
|
2019-11-05 06:35:07 +00:00
|
|
|
}
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-08-24 23:23:40 +00:00
|
|
|
|
2019-12-14 16:50:05 +00:00
|
|
|
fn set_dirty(&mut self, value: bool) {
|
|
|
|
self.dirty = value;
|
|
|
|
self.pager.set_dirty(value);
|
|
|
|
self.form.set_dirty(value);
|
2020-10-13 14:17:57 +00:00
|
|
|
if let ViewMode::EditAttachments { ref mut widget } = self.mode {
|
|
|
|
(EditAttachmentsRefMut {
|
|
|
|
inner: widget,
|
|
|
|
draft: &mut self.draft,
|
|
|
|
})
|
|
|
|
.set_dirty(value);
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 08:28:12 +00:00
|
|
|
fn kill(&mut self, uuid: Uuid, context: &mut Context) {
|
2020-02-28 07:18:31 +00:00
|
|
|
if self.id != uuid {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-20 08:17:54 +00:00
|
|
|
if !self.has_changes {
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(Kill(uuid))));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-02 22:03:20 +00:00
|
|
|
self.mode = ViewMode::Discard(
|
|
|
|
uuid,
|
2020-02-19 14:57:37 +00:00
|
|
|
UIDialog::new(
|
2019-10-02 22:03:20 +00:00
|
|
|
"this draft has unsaved changes",
|
|
|
|
vec![
|
|
|
|
('x', "quit without saving".to_string()),
|
|
|
|
('y', "save draft and quit".to_string()),
|
|
|
|
('n', "cancel".to_string()),
|
|
|
|
],
|
|
|
|
true,
|
2020-02-22 09:47:13 +00:00
|
|
|
Some(Box::new(move |id: ComponentId, results: &[char]| {
|
|
|
|
Some(UIEvent::FinishedUIDialog(
|
|
|
|
id,
|
|
|
|
Box::new(results.get(0).map(|c| *c).unwrap_or('n')),
|
|
|
|
))
|
|
|
|
})),
|
2019-10-06 08:28:12 +00:00
|
|
|
context,
|
2019-10-02 22:03:20 +00:00
|
|
|
),
|
|
|
|
);
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
2019-03-30 22:28:01 +00:00
|
|
|
|
2019-05-10 19:00:56 +00:00
|
|
|
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
|
2019-11-18 12:53:41 +00:00
|
|
|
let mut map = if self.mode.is_edit() {
|
2019-04-04 11:24:05 +00:00
|
|
|
self.pager.get_shortcuts(context)
|
2019-03-30 22:28:01 +00:00
|
|
|
} else {
|
|
|
|
Default::default()
|
|
|
|
};
|
|
|
|
|
2020-03-18 17:13:07 +00:00
|
|
|
let our_map: ShortcutMap =
|
2020-09-12 20:02:06 +00:00
|
|
|
account_settings!(context[self.account_hash].shortcuts.composing).key_values();
|
2019-11-18 20:20:18 +00:00
|
|
|
map.insert(Composer::DESCRIPTION, our_map);
|
2019-03-30 22:28:01 +00:00
|
|
|
|
|
|
|
map
|
|
|
|
}
|
2019-04-10 19:01:02 +00:00
|
|
|
|
|
|
|
fn id(&self) -> ComponentId {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
fn set_id(&mut self, id: ComponentId) {
|
|
|
|
self.id = id;
|
|
|
|
}
|
2019-09-27 10:14:16 +00:00
|
|
|
|
2019-10-06 08:28:12 +00:00
|
|
|
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
2019-10-20 08:17:54 +00:00
|
|
|
if !self.has_changes {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-19 14:57:37 +00:00
|
|
|
let id = self.id;
|
2019-09-27 10:14:16 +00:00
|
|
|
/* Play it safe and ask user for confirmation */
|
2019-10-02 22:03:20 +00:00
|
|
|
self.mode = ViewMode::Discard(
|
2020-02-19 14:57:37 +00:00
|
|
|
id,
|
|
|
|
UIDialog::new(
|
2019-10-02 22:03:20 +00:00
|
|
|
"this draft has unsaved changes",
|
|
|
|
vec![
|
|
|
|
('x', "quit without saving".to_string()),
|
|
|
|
('y', "save draft and quit".to_string()),
|
|
|
|
('n', "cancel".to_string()),
|
|
|
|
],
|
|
|
|
true,
|
2020-02-22 09:47:13 +00:00
|
|
|
Some(Box::new(move |id: ComponentId, results: &[char]| {
|
|
|
|
Some(UIEvent::FinishedUIDialog(
|
|
|
|
id,
|
|
|
|
Box::new(results.get(0).map(|c| *c).unwrap_or('n')),
|
|
|
|
))
|
|
|
|
})),
|
2019-10-06 08:28:12 +00:00
|
|
|
context,
|
2019-10-02 22:03:20 +00:00
|
|
|
),
|
|
|
|
);
|
2019-12-14 16:50:05 +00:00
|
|
|
self.set_dirty(true);
|
2019-09-27 10:14:16 +00:00
|
|
|
false
|
|
|
|
}
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
2018-08-30 12:54:30 +00:00
|
|
|
|
2019-09-28 07:46:49 +00:00
|
|
|
pub fn send_draft(
|
2020-10-10 21:32:55 +00:00
|
|
|
_sign_mail: ToggleFlag,
|
2019-09-28 07:46:49 +00:00
|
|
|
context: &mut Context,
|
2020-08-17 12:31:30 +00:00
|
|
|
account_hash: AccountHash,
|
2019-09-28 07:46:49 +00:00
|
|
|
mut draft: Draft,
|
2020-02-26 08:54:10 +00:00
|
|
|
mailbox_type: SpecialUsageMailbox,
|
2019-12-03 11:27:35 +00:00
|
|
|
flags: Flag,
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
complete_in_background: bool,
|
2020-10-09 16:34:55 +00:00
|
|
|
) -> Result<Option<JoinHandle<Result<()>>>> {
|
2020-09-12 20:02:06 +00:00
|
|
|
let format_flowed = *account_settings!(context[account_hash].composing.format_flowed);
|
2020-10-10 21:32:55 +00:00
|
|
|
/* if sign_mail.is_true() {
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
let mut content_type = ContentType::default();
|
|
|
|
if format_flowed {
|
|
|
|
if let ContentType::Text {
|
|
|
|
ref mut parameters, ..
|
|
|
|
} = content_type
|
|
|
|
{
|
|
|
|
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
2019-11-17 11:27:22 +00:00
|
|
|
}
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
}
|
2019-11-17 11:27:22 +00:00
|
|
|
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
let mut body: AttachmentBuilder = Attachment::new(
|
|
|
|
content_type,
|
|
|
|
Default::default(),
|
|
|
|
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
if !draft.attachments.is_empty() {
|
|
|
|
let mut parts = std::mem::replace(&mut draft.attachments, Vec::new());
|
|
|
|
parts.insert(0, body);
|
|
|
|
let boundary = ContentType::make_boundary(&parts);
|
|
|
|
body = Attachment::new(
|
|
|
|
ContentType::Multipart {
|
|
|
|
boundary: boundary.into_bytes(),
|
|
|
|
kind: MultipartType::Mixed,
|
|
|
|
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
|
|
|
|
},
|
2019-09-28 07:46:49 +00:00
|
|
|
Default::default(),
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
Vec::new(),
|
2019-09-28 07:46:49 +00:00
|
|
|
)
|
|
|
|
.into();
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
let output = todo!();
|
|
|
|
crate::components::mail::pgp::sign(
|
|
|
|
body.into(),
|
|
|
|
account_settings!(context[account_hash].pgp.gpg_binary)
|
|
|
|
.as_ref()
|
|
|
|
.map(|s| s.as_str()),
|
|
|
|
account_settings!(context[account_hash].pgp.sign_key)
|
|
|
|
.as_ref()
|
|
|
|
.map(|s| s.as_str()),
|
|
|
|
);
|
|
|
|
match output {
|
|
|
|
Err(err) => {
|
|
|
|
debug!("{:?} could not sign draft msg", err);
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Could not sign draft in account `{}`: {}.",
|
|
|
|
context.accounts[&account_hash].name(),
|
|
|
|
err.to_string()
|
|
|
|
),
|
|
|
|
ERROR,
|
|
|
|
);
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!(
|
|
|
|
"Could not sign draft in account `{}`.",
|
|
|
|
context.accounts[&account_hash].name()
|
|
|
|
)),
|
|
|
|
err.to_string(),
|
|
|
|
Some(NotificationType::Error(err.kind)),
|
|
|
|
));
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
Ok(output) => {
|
|
|
|
draft.attachments.push(output);
|
2019-09-28 07:46:49 +00:00
|
|
|
}
|
2020-10-10 21:32:55 +00:00
|
|
|
}
|
2019-09-23 06:30:23 +00:00
|
|
|
} else {
|
2020-10-10 21:32:55 +00:00
|
|
|
*/
|
|
|
|
{
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
let mut content_type = ContentType::default();
|
|
|
|
if format_flowed {
|
|
|
|
if let ContentType::Text {
|
|
|
|
ref mut parameters, ..
|
|
|
|
} = content_type
|
|
|
|
{
|
|
|
|
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
|
|
|
}
|
|
|
|
|
|
|
|
let body: AttachmentBuilder = Attachment::new(
|
|
|
|
content_type,
|
|
|
|
Default::default(),
|
|
|
|
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
2019-11-19 20:46:25 +00:00
|
|
|
)
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
.into();
|
|
|
|
draft.attachments.insert(0, body);
|
|
|
|
}
|
2019-06-18 19:13:54 +00:00
|
|
|
}
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
let bytes = draft.finalise().unwrap();
|
2020-09-12 20:02:06 +00:00
|
|
|
let send_mail = account_settings!(context[account_hash].composing.send_mail).clone();
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
let ret =
|
2020-08-17 12:31:30 +00:00
|
|
|
context.accounts[&account_hash].send(bytes.clone(), send_mail, complete_in_background);
|
|
|
|
save_draft(bytes.as_bytes(), context, mailbox_type, flags, account_hash);
|
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
|
|
|
ret
|
2019-06-18 19:13:54 +00:00
|
|
|
}
|
2019-12-03 11:27:35 +00:00
|
|
|
|
|
|
|
pub fn save_draft(
|
|
|
|
bytes: &[u8],
|
|
|
|
context: &mut Context,
|
2020-02-26 08:54:10 +00:00
|
|
|
mailbox_type: SpecialUsageMailbox,
|
2019-12-03 11:27:35 +00:00
|
|
|
flags: Flag,
|
2020-08-17 12:31:30 +00:00
|
|
|
account_hash: AccountHash,
|
2019-12-03 11:27:35 +00:00
|
|
|
) {
|
2020-08-17 12:31:30 +00:00
|
|
|
match context.accounts[&account_hash].save_special(bytes, mailbox_type, flags) {
|
2020-06-23 16:25:01 +00:00
|
|
|
Err(MeliError {
|
2020-09-13 12:23:14 +00:00
|
|
|
summary,
|
|
|
|
details,
|
|
|
|
kind,
|
|
|
|
..
|
2020-06-23 16:25:01 +00:00
|
|
|
}) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
summary.map(|s| s.into()),
|
|
|
|
details.into(),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Error(kind)),
|
2020-06-23 16:25:01 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
Ok(mailbox_hash) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2020-06-26 17:22:22 +00:00
|
|
|
Some("Message saved".into()),
|
2020-06-23 16:25:01 +00:00
|
|
|
format!(
|
2020-06-26 17:22:22 +00:00
|
|
|
"Message saved in `{}`",
|
2020-08-17 12:31:30 +00:00
|
|
|
&context.accounts[&account_hash].mailbox_entries[&mailbox_hash].name
|
2020-06-23 16:25:01 +00:00
|
|
|
),
|
2020-09-13 12:23:14 +00:00
|
|
|
Some(NotificationType::Info),
|
2020-06-23 16:25:01 +00:00
|
|
|
));
|
|
|
|
}
|
2019-12-03 11:27:35 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-08 13:52:13 +00:00
|
|
|
|
|
|
|
pub fn send_draft_async(
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")] gpg_state: gpg::GpgComposeState,
|
2020-10-08 13:52:13 +00:00
|
|
|
context: &mut Context,
|
|
|
|
account_hash: AccountHash,
|
|
|
|
mut draft: Draft,
|
|
|
|
mailbox_type: SpecialUsageMailbox,
|
|
|
|
flags: Flag,
|
|
|
|
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
|
2020-11-09 20:22:11 +00:00
|
|
|
let store_sent_mail = *account_settings!(context[account_hash].composing.store_sent_mail);
|
2020-10-08 13:52:13 +00:00
|
|
|
let format_flowed = *account_settings!(context[account_hash].composing.format_flowed);
|
|
|
|
let event_sender = context.sender.clone();
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
2020-10-08 13:52:13 +00:00
|
|
|
let mut filters_stack: Vec<
|
|
|
|
Box<
|
|
|
|
dyn FnOnce(
|
|
|
|
AttachmentBuilder,
|
|
|
|
)
|
|
|
|
-> Pin<Box<dyn Future<Output = Result<AttachmentBuilder>> + Send>>
|
|
|
|
+ Send,
|
|
|
|
>,
|
|
|
|
> = vec![];
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
if gpg_state.sign_mail.is_true() && !gpg_state.encrypt_mail.is_true() {
|
2020-10-08 13:52:13 +00:00
|
|
|
filters_stack.push(Box::new(crate::components::mail::pgp::sign_filter(
|
2020-10-10 21:32:55 +00:00
|
|
|
gpg_state.sign_keys.clone(),
|
2020-10-08 13:52:13 +00:00
|
|
|
)?));
|
2020-10-10 21:32:55 +00:00
|
|
|
} else if gpg_state.encrypt_mail.is_true() {
|
2020-10-09 08:58:18 +00:00
|
|
|
filters_stack.push(Box::new(crate::components::mail::pgp::encrypt_filter(
|
2020-10-10 21:32:55 +00:00
|
|
|
if gpg_state.sign_mail.is_true() {
|
|
|
|
Some(gpg_state.sign_keys.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
gpg_state.encrypt_keys.clone(),
|
2020-10-09 08:58:18 +00:00
|
|
|
)?));
|
|
|
|
}
|
2020-10-08 13:52:13 +00:00
|
|
|
let send_mail = account_settings!(context[account_hash].composing.send_mail).clone();
|
|
|
|
let send_cb = context.accounts[&account_hash].send_async(send_mail);
|
|
|
|
let mut content_type = ContentType::default();
|
|
|
|
if format_flowed {
|
|
|
|
if let ContentType::Text {
|
|
|
|
ref mut parameters, ..
|
|
|
|
} = content_type
|
|
|
|
{
|
|
|
|
parameters.push((b"format".to_vec(), b"flowed".to_vec()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut body: AttachmentBuilder = Attachment::new(
|
|
|
|
content_type,
|
|
|
|
Default::default(),
|
|
|
|
std::mem::replace(&mut draft.body, String::new()).into_bytes(),
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
if !draft.attachments.is_empty() {
|
|
|
|
let mut parts = std::mem::replace(&mut draft.attachments, Vec::new());
|
|
|
|
parts.insert(0, body);
|
|
|
|
let boundary = ContentType::make_boundary(&parts);
|
|
|
|
body = Attachment::new(
|
|
|
|
ContentType::Multipart {
|
|
|
|
boundary: boundary.into_bytes(),
|
|
|
|
kind: MultipartType::Mixed,
|
|
|
|
parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
|
|
|
|
},
|
|
|
|
Default::default(),
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
}
|
|
|
|
Ok(Box::pin(async move {
|
2020-10-10 21:32:55 +00:00
|
|
|
#[cfg(feature = "gpgme")]
|
2020-10-08 13:52:13 +00:00
|
|
|
for f in filters_stack {
|
|
|
|
body = f(body).await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
draft.attachments.insert(0, body);
|
|
|
|
let message = Arc::new(draft.finalise()?);
|
|
|
|
let ret = send_cb(message.clone()).await;
|
|
|
|
let is_ok = ret.is_ok();
|
2020-11-09 20:22:11 +00:00
|
|
|
if !is_ok || (store_sent_mail && is_ok) {
|
|
|
|
event_sender
|
|
|
|
.send(ThreadEvent::UIEvent(UIEvent::Callback(CallbackFn(
|
|
|
|
Box::new(move |context| {
|
|
|
|
save_draft(
|
|
|
|
message.as_bytes(),
|
|
|
|
context,
|
|
|
|
if is_ok {
|
|
|
|
mailbox_type
|
|
|
|
} else {
|
|
|
|
SpecialUsageMailbox::Drafts
|
|
|
|
},
|
|
|
|
if is_ok {
|
|
|
|
flags
|
|
|
|
} else {
|
|
|
|
Flag::SEEN | Flag::DRAFT
|
|
|
|
},
|
|
|
|
account_hash,
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
))))
|
|
|
|
.unwrap();
|
|
|
|
} else if !store_sent_mail && is_ok {
|
|
|
|
let f = create_temp_file(message.as_bytes(), None, None, false);
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"store_sent_mail is false; stored sent mail to {}",
|
|
|
|
f.path().display()
|
|
|
|
),
|
|
|
|
INFO,
|
|
|
|
);
|
|
|
|
}
|
2020-10-08 13:52:13 +00:00
|
|
|
ret
|
|
|
|
}))
|
|
|
|
}
|