2018-08-11 15:00:21 +00:00
|
|
|
/*
|
|
|
|
* meli - ui crate
|
|
|
|
*
|
|
|
|
* 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::*;
|
|
|
|
|
2018-08-29 16:09:51 +00:00
|
|
|
use melib::Draft;
|
2019-08-01 09:28:36 +00:00
|
|
|
use mime_apps::query_mime_info;
|
2018-09-12 12:10:19 +00:00
|
|
|
use std::str::FromStr;
|
2018-08-29 16:09:51 +00:00
|
|
|
|
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,
|
2019-03-03 20:11:15 +00:00
|
|
|
//Attachments,
|
2019-02-18 21:14:06 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 11:39:54 +00:00
|
|
|
#[derive(Debug)]
|
2018-08-16 13:32:47 +00:00
|
|
|
pub struct Composer {
|
2018-09-06 10:05:35 +00:00
|
|
|
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
2018-09-03 22:49:29 +00:00
|
|
|
account_cursor: usize,
|
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,
|
2019-03-02 06:11:38 +00:00
|
|
|
form: FormWidget,
|
2018-08-30 12:54:30 +00:00
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
mode: ViewMode,
|
2019-09-28 07:46:49 +00:00
|
|
|
sign_mail: ToggleFlag,
|
2018-08-30 12:54:30 +00:00
|
|
|
dirty: 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
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Composer {
|
|
|
|
fn default() -> Self {
|
|
|
|
Composer {
|
2018-09-03 22:49:29 +00:00
|
|
|
reply_context: None,
|
|
|
|
account_cursor: 0,
|
|
|
|
|
2019-03-02 06:11:38 +00:00
|
|
|
cursor: Cursor::Headers,
|
2019-02-18 21:14:06 +00:00
|
|
|
|
2018-08-30 12:54:30 +00:00
|
|
|
pager: Pager::default(),
|
2018-08-29 16:09:51 +00:00
|
|
|
draft: Draft::default(),
|
2019-03-02 06:11:38 +00:00
|
|
|
form: FormWidget::default(),
|
2018-09-03 22:49:29 +00:00
|
|
|
|
2019-03-30 22:28:01 +00:00
|
|
|
mode: ViewMode::Edit,
|
2019-09-28 07:46:49 +00:00
|
|
|
sign_mail: ToggleFlag::Unset,
|
2018-09-03 22:49:29 +00:00
|
|
|
dirty: true,
|
|
|
|
initialized: false,
|
2019-04-10 21:04:17 +00:00
|
|
|
id: ComponentId::new_v4(),
|
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 {
|
2019-10-02 22:03:20 +00:00
|
|
|
Discard(Uuid, Selector<char>),
|
2019-03-30 22:28:01 +00:00
|
|
|
Edit,
|
2019-10-03 09:22:01 +00:00
|
|
|
SelectRecipients(Selector<Address>),
|
2019-09-27 10:27:07 +00:00
|
|
|
ThreadView,
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-27 10:27:07 +00:00
|
|
|
fn is_threadview(&self) -> bool {
|
|
|
|
if let ViewMode::ThreadView = self {
|
2018-09-03 22:49:29 +00:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-11 15:00:21 +00:00
|
|
|
impl fmt::Display for Composer {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
// TODO display subject/info
|
2018-09-03 22:49:29 +00:00
|
|
|
if self.reply_context.is_some() {
|
|
|
|
write!(f, "reply: {:8}", self.draft.headers()["Subject"])
|
|
|
|
} else {
|
|
|
|
write!(f, "compose")
|
|
|
|
}
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-30 12:54:30 +00:00
|
|
|
impl Composer {
|
2019-05-10 19:00:56 +00:00
|
|
|
const DESCRIPTION: &'static str = "compose";
|
2019-04-05 21:43:50 +00:00
|
|
|
pub fn new(account_cursor: usize) -> Self {
|
|
|
|
Composer {
|
|
|
|
account_cursor,
|
2019-04-10 21:04:17 +00:00
|
|
|
id: ComponentId::new_v4(),
|
2019-04-05 21:43:50 +00:00
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
/*
|
2018-09-06 10:05:35 +00:00
|
|
|
* coordinates: (account index, mailbox index, root set thread_node index)
|
|
|
|
* msg: index of message we reply to in thread_nodes
|
2018-09-03 22:49:29 +00:00
|
|
|
* context: current context
|
|
|
|
*/
|
2019-09-27 10:18:59 +00:00
|
|
|
pub fn edit(account_pos: usize, h: EnvelopeHash, context: &Context) -> Result<Self> {
|
2019-03-26 13:26:09 +00:00
|
|
|
let mut ret = Composer::default();
|
2019-06-18 18:13:58 +00:00
|
|
|
let op = context.accounts[account_pos].operation(h);
|
2019-05-25 23:34:03 +00:00
|
|
|
let envelope: &Envelope = context.accounts[account_pos].get_env(&h);
|
2019-03-26 13:26:09 +00:00
|
|
|
|
2019-09-27 10:18:59 +00:00
|
|
|
ret.draft = Draft::edit(envelope, op)?;
|
2019-03-26 13:26:09 +00:00
|
|
|
|
2019-05-25 23:34:03 +00:00
|
|
|
ret.account_cursor = account_pos;
|
2019-09-27 10:18:59 +00:00
|
|
|
Ok(ret)
|
2019-03-26 13:26:09 +00:00
|
|
|
}
|
2019-05-14 18:47:47 +00:00
|
|
|
pub fn with_context(
|
|
|
|
coordinates: (usize, usize, usize),
|
|
|
|
msg: ThreadHash,
|
|
|
|
context: &Context,
|
|
|
|
) -> Self {
|
2019-05-25 23:34:03 +00:00
|
|
|
let account = &context.accounts[coordinates.0];
|
2019-07-28 15:52:45 +00:00
|
|
|
let mailbox = &account[coordinates.1].unwrap();
|
2019-05-25 23:34:03 +00:00
|
|
|
let threads = &account.collection.threads[&mailbox.folder.hash()];
|
2018-09-06 10:05:35 +00:00
|
|
|
let thread_nodes = &threads.thread_nodes();
|
2018-09-03 22:49:29 +00:00
|
|
|
let mut ret = Composer::default();
|
2019-05-14 18:47:47 +00:00
|
|
|
let p = &thread_nodes[&msg];
|
2019-05-25 23:34:03 +00:00
|
|
|
let parent_message = &account.collection[&p.message().unwrap()];
|
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 */
|
|
|
|
if let Some(actions) = list_management::detect(parent_message) {
|
|
|
|
if let Some(post) = actions.post {
|
|
|
|
/* Try to parse header value in this order
|
|
|
|
* - <mailto:*****@*****>
|
|
|
|
* - mailto:******@*****
|
|
|
|
* - <***@****>
|
|
|
|
*/
|
|
|
|
if let Ok(list_address) = melib::email::parser::message_id(post.as_bytes())
|
|
|
|
.to_full_result()
|
|
|
|
.and_then(|b| melib::email::parser::mailto(b).to_full_result())
|
|
|
|
.or(melib::email::parser::mailto(post.as_bytes()).to_full_result())
|
|
|
|
.map(|m| m.address)
|
|
|
|
.or(melib::email::parser::address(post.as_bytes()).to_full_result())
|
|
|
|
{
|
|
|
|
let list_address_string = list_address.to_string();
|
|
|
|
ret.mode = ViewMode::SelectRecipients(Selector::new(
|
|
|
|
"select recipients",
|
|
|
|
vec![
|
|
|
|
(
|
|
|
|
parent_message.from()[0].clone(),
|
|
|
|
parent_message.field_from_to_string(),
|
|
|
|
),
|
|
|
|
(list_address, list_address_string),
|
|
|
|
],
|
|
|
|
false,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-18 18:13:58 +00:00
|
|
|
let mut op = account.operation(parent_message.hash());
|
2018-09-04 12:00:23 +00:00
|
|
|
let parent_bytes = op.as_bytes();
|
|
|
|
|
2018-09-12 12:10:19 +00:00
|
|
|
ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap());
|
2018-09-03 22:49:29 +00:00
|
|
|
ret.draft.headers_mut().insert(
|
|
|
|
"Subject".into(),
|
|
|
|
if p.show_subject() {
|
|
|
|
format!(
|
|
|
|
"Re: {}",
|
2019-05-25 23:34:03 +00:00
|
|
|
account.get_env(&p.message().unwrap()).subject().clone()
|
2018-09-03 22:49:29 +00:00
|
|
|
)
|
|
|
|
} else {
|
2019-05-25 23:34:03 +00:00
|
|
|
account.get_env(&p.message().unwrap()).subject().into()
|
2018-09-03 22:49:29 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
ret.account_cursor = coordinates.0;
|
|
|
|
ret.reply_context = Some((
|
|
|
|
(coordinates.1, coordinates.2),
|
|
|
|
Box::new(ThreadView::new(coordinates, Some(msg), context)),
|
|
|
|
));
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
/* avoid extra allocations by updating values instead of inserting */
|
|
|
|
for (k, v) in draft_header_map.iter_mut() {
|
|
|
|
if let Some(vn) = header_values.remove(k) {
|
|
|
|
std::mem::swap(v, &mut vn.into_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_form(&mut self) {
|
|
|
|
let old_cursor = self.form.cursor();
|
|
|
|
self.form = FormWidget::new("Save".into());
|
|
|
|
self.form.hide_buttons();
|
|
|
|
self.form.set_cursor(old_cursor);
|
|
|
|
let headers = self.draft.headers();
|
2019-03-09 08:24:28 +00:00
|
|
|
let account_cursor = self.account_cursor;
|
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(),
|
|
|
|
headers[k].to_string(),
|
|
|
|
Box::new(move |c, term| {
|
|
|
|
let book: &AddressBook = &c.accounts[account_cursor].address_book;
|
|
|
|
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
|
|
|
}),
|
|
|
|
));
|
2019-03-09 08:24:28 +00:00
|
|
|
} else {
|
|
|
|
self.form.push((k.into(), headers[k].to_string()));
|
|
|
|
}
|
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();
|
2019-09-28 07:46:49 +00:00
|
|
|
if self.sign_mail.is_true() {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!(
|
|
|
|
"☑ sign with {}",
|
|
|
|
context
|
|
|
|
.settings
|
|
|
|
.pgp
|
|
|
|
.key
|
|
|
|
.as_ref()
|
|
|
|
.map(String::as_str)
|
|
|
|
.unwrap_or("default key")
|
|
|
|
),
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
|
|
|
Attr::Default,
|
|
|
|
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
"☐ don't sign",
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
|
|
|
Attr::Default,
|
|
|
|
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
2019-08-01 09:28:36 +00:00
|
|
|
if attachments_no == 0 {
|
|
|
|
write_string_to_grid(
|
|
|
|
"no attachments",
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
2019-08-18 12:44:40 +00:00
|
|
|
Attr::Default,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
2019-08-01 09:28:36 +00:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!("{} attachments ", attachments_no),
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
2019-08-18 12:44:40 +00:00
|
|
|
Attr::Default,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
2019-08-01 09:28:36 +00:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
for (i, a) in self.draft.attachments().iter().enumerate() {
|
|
|
|
if let Some(name) = a.content_type().name() {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!(
|
|
|
|
"[{}] \"{}\", {} {} bytes",
|
|
|
|
i,
|
|
|
|
name,
|
|
|
|
a.content_type(),
|
|
|
|
a.raw.len()
|
|
|
|
),
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
2019-08-18 12:44:40 +00:00
|
|
|
Attr::Default,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
2019-08-01 09:28:36 +00:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
write_string_to_grid(
|
|
|
|
&format!("[{}] {} {} bytes", i, a.content_type(), a.raw.len()),
|
|
|
|
grid,
|
|
|
|
Color::Default,
|
|
|
|
Color::Default,
|
2019-08-18 12:44:40 +00:00
|
|
|
Attr::Default,
|
2019-09-28 07:46:49 +00:00
|
|
|
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
2019-08-01 09:28:36 +00:00
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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) {
|
2019-03-09 08:24:28 +00:00
|
|
|
clear_area(grid, area);
|
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;
|
|
|
|
}
|
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
let width = if width!(area) > 80 && self.reply_context.is_some() {
|
|
|
|
width!(area) / 2
|
|
|
|
} else {
|
|
|
|
width!(area)
|
|
|
|
};
|
|
|
|
|
2019-03-14 10:00:41 +00:00
|
|
|
if !self.initialized {
|
2019-09-28 07:46:49 +00:00
|
|
|
if self.sign_mail.is_unset() {
|
|
|
|
self.sign_mail = ToggleFlag::InternalVal(context.settings.pgp.auto_sign);
|
|
|
|
}
|
2019-04-10 15:57:09 +00:00
|
|
|
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
|
|
|
|
{
|
2019-04-10 13:54:25 +00:00
|
|
|
self.draft.headers_mut().insert(
|
|
|
|
"From".into(),
|
2019-07-22 12:14:39 +00:00
|
|
|
crate::components::mail::get_display_name(context, self.account_cursor),
|
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();
|
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;
|
2018-09-03 22:49:29 +00:00
|
|
|
let mid = if self.reply_context.is_some() {
|
2019-03-26 17:53:39 +00:00
|
|
|
get_x(upper_left) + width!(area) / 2
|
2018-09-03 22:49:29 +00:00
|
|
|
} else {
|
|
|
|
width / 2
|
|
|
|
};
|
|
|
|
|
|
|
|
if self.reply_context.is_some() {
|
2019-03-22 22:28:17 +00:00
|
|
|
for i in get_y(upper_left) - 1..=get_y(bottom_right) {
|
2019-09-27 10:27:07 +00:00
|
|
|
//set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
|
2018-09-03 22:49:29 +00:00
|
|
|
grid[(mid, i)].set_fg(Color::Default);
|
|
|
|
grid[(mid, i)].set_bg(Color::Default);
|
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
//grid[set_x(bottom_right, mid)].set_ch(VERT_BOUNDARY); // Enforce full vert bar at the bottom
|
2019-03-30 19:12:13 +00:00
|
|
|
grid[set_x(bottom_right, mid)].set_fg(Color::Byte(240));
|
2018-09-03 22:49:29 +00:00
|
|
|
}
|
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);
|
2018-08-16 13:32:47 +00:00
|
|
|
grid[(mid, i)].set_fg(Color::Default);
|
|
|
|
grid[(mid, i)].set_bg(Color::Default);
|
2018-08-29 20:08:23 +00:00
|
|
|
//set_and_join_box(grid, (mid + 80, i), VERT_BOUNDARY);
|
2018-08-16 13:32:47 +00:00
|
|
|
grid[(mid + 80, i)].set_fg(Color::Default);
|
|
|
|
grid[(mid + 80, i)].set_bg(Color::Default);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mid
|
2018-08-23 12:36:52 +00:00
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2018-08-16 13:32:47 +00:00
|
|
|
|
2018-09-03 22:49:29 +00:00
|
|
|
if width > 80 && self.reply_context.is_some() {
|
2019-03-30 19:12:13 +00:00
|
|
|
let area = (pos_dec(upper_left, (0, 1)), set_x(bottom_right, mid - 1));
|
2018-09-03 22:49:29 +00:00
|
|
|
let view = &mut self.reply_context.as_mut().unwrap().1;
|
2019-03-22 22:28:17 +00:00
|
|
|
view.set_dirty();
|
2019-03-26 13:27:02 +00:00
|
|
|
view.draw(grid, area, context);
|
2018-09-03 22:49:29 +00:00
|
|
|
}
|
|
|
|
|
2019-03-22 22:28:17 +00:00
|
|
|
let header_area = if self.reply_context.is_some() {
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
|
|
|
set_x(upper_left, mid + 1),
|
2019-08-01 09:28:36 +00:00
|
|
|
set_y(bottom_right, get_y(upper_left) + header_height),
|
2019-03-26 17:53:39 +00:00
|
|
|
)
|
2019-03-22 22:28:17 +00:00
|
|
|
} else {
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
|
|
|
set_x(upper_left, mid + 1),
|
|
|
|
(
|
|
|
|
get_x(bottom_right).saturating_sub(mid),
|
2019-08-01 09:28:36 +00:00
|
|
|
get_y(upper_left) + header_height,
|
2019-03-26 17:53:39 +00:00
|
|
|
),
|
|
|
|
)
|
2019-03-22 22:28:17 +00:00
|
|
|
};
|
2019-08-01 09:28:36 +00:00
|
|
|
let attachments_no = self.draft.attachments().len();
|
|
|
|
let attachment_area = if self.reply_context.is_some() {
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
2019-08-01 09:28:36 +00:00
|
|
|
(mid + 1, get_y(bottom_right) - 2 - attachments_no),
|
2019-03-26 17:53:39 +00:00
|
|
|
bottom_right,
|
|
|
|
)
|
2019-03-22 22:28:17 +00:00
|
|
|
} else {
|
2019-03-14 10:19:25 +00:00
|
|
|
(
|
2019-08-01 09:28:36 +00:00
|
|
|
(mid + 1, get_y(bottom_right) - 2 - attachments_no),
|
2019-03-26 17:53:39 +00:00
|
|
|
pos_dec(bottom_right, (mid, 0)),
|
|
|
|
)
|
2019-03-22 22:28:17 +00:00
|
|
|
};
|
2018-08-16 13:32:47 +00:00
|
|
|
|
2019-08-01 09:28:36 +00:00
|
|
|
let body_area = if self.reply_context.is_some() {
|
|
|
|
(
|
|
|
|
(mid + 1, get_y(upper_left) + header_height + 1),
|
|
|
|
set_y(bottom_right, get_y(bottom_right) - 3 - attachments_no),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
pos_inc(upper_left, (mid + 1, header_height + 1)),
|
|
|
|
pos_dec(bottom_right, (mid, 3 + attachments_no)),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
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),
|
2019-08-18 12:44:40 +00:00
|
|
|
Attr::Default,
|
2019-03-26 17:53:39 +00:00
|
|
|
(
|
|
|
|
pos_dec(upper_left!(header_area), (0, 1)),
|
|
|
|
bottom_right!(header_area),
|
|
|
|
),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
|
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-02-18 21:14:06 +00:00
|
|
|
|
|
|
|
match self.mode {
|
2019-09-27 10:27:07 +00:00
|
|
|
ViewMode::ThreadView | ViewMode::Edit => {
|
2019-03-10 11:37:49 +00:00
|
|
|
self.pager.set_dirty();
|
2019-03-26 13:27:02 +00:00
|
|
|
self.pager.draw(grid, body_area, context);
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-10-03 09:22:01 +00:00
|
|
|
ViewMode::SelectRecipients(ref mut s) => {
|
|
|
|
self.pager.set_dirty();
|
|
|
|
self.pager.draw(grid, body_area, context);
|
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
|
|
|
}
|
2019-10-02 22:03:20 +00:00
|
|
|
ViewMode::Discard(_, ref mut s) => {
|
|
|
|
self.pager.set_dirty();
|
|
|
|
self.pager.draw(grid, body_area, context);
|
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
|
|
|
}
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
|
2019-08-01 09:28:36 +00:00
|
|
|
self.draw_attachments(grid, attachment_area, context);
|
2018-09-03 22:49:29 +00:00
|
|
|
context.dirty_areas.push_back(area);
|
2018-08-11 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2019-02-26 15:50:47 +00:00
|
|
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
2019-04-10 20:37:20 +00:00
|
|
|
match (&mut self.mode, &mut self.reply_context, &event) {
|
2019-03-30 22:28:01 +00:00
|
|
|
// don't pass Reply command to thread view in reply_context
|
2019-04-10 20:37:20 +00:00
|
|
|
(_, _, UIEvent::Input(Key::Char('R'))) => {}
|
2019-09-27 10:27:07 +00:00
|
|
|
(ViewMode::ThreadView, Some((_, ref mut view)), _) => {
|
2019-03-30 22:28:01 +00:00
|
|
|
if view.process_event(event, context) {
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
/* Cannot mutably borrow in pattern guard, pah! */
|
|
|
|
if self.pager.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-10-03 09:22:01 +00:00
|
|
|
(ViewMode::SelectRecipients(ref mut selector), _, _) => {
|
|
|
|
if selector.process_event(event, context) {
|
|
|
|
if selector.is_done() {
|
|
|
|
let s = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
|
|
|
|
ViewMode::SelectRecipients(s) => s,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
let new_recipients = s.collect();
|
|
|
|
self.draft.headers_mut().insert(
|
|
|
|
"To".to_string(),
|
|
|
|
new_recipients
|
|
|
|
.into_iter()
|
|
|
|
.map(|a| a.to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", "),
|
|
|
|
);
|
|
|
|
self.update_form();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
(ViewMode::ThreadView, _, _) => {
|
2019-03-30 22:28:01 +00:00
|
|
|
/* Cannot mutably borrow in pattern guard, pah! */
|
|
|
|
if self.pager.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-10-02 22:03:20 +00:00
|
|
|
(ViewMode::Discard(_, ref mut selector), _, _) => {
|
|
|
|
if selector.process_event(event, context) {
|
|
|
|
if selector.is_done() {
|
2019-10-03 09:22:01 +00:00
|
|
|
let (u, s) = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
|
2019-10-02 22:03:20 +00:00
|
|
|
ViewMode::Discard(u, s) => (u, s),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
let key = s.collect()[0] as char;
|
|
|
|
match key {
|
|
|
|
'x' => {
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(Kill(u))));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
'n' => {}
|
|
|
|
'y' => {
|
|
|
|
let mut failure = true;
|
|
|
|
let draft = std::mem::replace(&mut self.draft, Draft::default());
|
|
|
|
|
|
|
|
let draft = draft.finalise().unwrap();
|
|
|
|
for folder in &[
|
|
|
|
&context.accounts[self.account_cursor]
|
|
|
|
.special_use_folder(SpecialUseMailbox::Drafts),
|
|
|
|
&context.accounts[self.account_cursor]
|
|
|
|
.special_use_folder(SpecialUseMailbox::Inbox),
|
|
|
|
&context.accounts[self.account_cursor]
|
|
|
|
.special_use_folder(SpecialUseMailbox::Normal),
|
|
|
|
] {
|
|
|
|
if folder.is_none() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let folder = folder.unwrap();
|
|
|
|
if let Err(e) = context.accounts[self.account_cursor].save(
|
|
|
|
draft.as_bytes(),
|
|
|
|
folder,
|
|
|
|
Some(Flag::SEEN | Flag::DRAFT),
|
|
|
|
) {
|
|
|
|
debug!("{:?} could not save draft msg", e);
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Could not save draft in '{}' folder: {}.",
|
|
|
|
folder,
|
|
|
|
e.to_string()
|
|
|
|
),
|
|
|
|
ERROR,
|
|
|
|
);
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!(
|
|
|
|
"Could not save draft in '{}' folder.",
|
|
|
|
folder
|
|
|
|
)),
|
|
|
|
e.into(),
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
failure = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if failure {
|
|
|
|
let file =
|
|
|
|
create_temp_file(draft.as_bytes(), None, None, false);
|
|
|
|
debug!("message saved in {}", file.path.display());
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Message was stored in {} so that you can restore it manually.",
|
|
|
|
file.path.display()
|
|
|
|
),
|
|
|
|
INFO,
|
|
|
|
);
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("Could not save in any folder".into()),
|
|
|
|
format!(
|
|
|
|
"Message was stored in {} so that you can restore it manually.",
|
|
|
|
file.path.display()
|
|
|
|
),
|
|
|
|
Some(NotificationType::INFO),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(Kill(u))));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
self.set_dirty();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2018-09-03 22:49:29 +00:00
|
|
|
_ => {}
|
2018-08-23 11:39:54 +00:00
|
|
|
}
|
2019-03-02 06:11:38 +00:00
|
|
|
if self.form.process_event(event, context) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-08-23 11:39:54 +00:00
|
|
|
|
2019-04-10 20:37:20 +00:00
|
|
|
match *event {
|
|
|
|
UIEvent::Resize => {
|
2018-09-04 11:16:01 +00:00
|
|
|
self.set_dirty();
|
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.account_cursor = self.account_cursor.saturating_sub(1);
|
|
|
|
self.draft.headers_mut().insert(
|
|
|
|
"From".into(),
|
|
|
|
get_display_name(context, self.account_cursor),
|
|
|
|
);
|
|
|
|
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-04-10 20:37:20 +00:00
|
|
|
UIEvent::Input(Key::Up) => {
|
2019-03-02 06:11:38 +00:00
|
|
|
self.cursor = Cursor::Headers;
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-04-10 20:37:20 +00:00
|
|
|
UIEvent::Input(Key::Down) => {
|
2019-03-02 06:11:38 +00:00
|
|
|
self.cursor = Cursor::Body;
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
/* Switch to thread view mode if we're on Edit mode */
|
2019-04-10 20:37:20 +00:00
|
|
|
UIEvent::Input(Key::Char('v')) if self.mode.is_edit() => {
|
2019-09-27 10:27:07 +00:00
|
|
|
self.mode = ViewMode::ThreadView;
|
2018-09-03 22:49:29 +00:00
|
|
|
self.set_dirty();
|
|
|
|
return true;
|
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
/* Switch to Edit mode if we're on ThreadView mode */
|
|
|
|
UIEvent::Input(Key::Char('o')) if self.mode.is_threadview() => {
|
2019-03-30 22:28:01 +00:00
|
|
|
self.mode = ViewMode::Edit;
|
2018-09-03 22:49:29 +00:00
|
|
|
self.set_dirty();
|
|
|
|
return true;
|
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
UIEvent::Input(Key::Char('s')) => {
|
2019-06-18 19:13:54 +00:00
|
|
|
self.update_draft();
|
2019-09-28 07:46:49 +00:00
|
|
|
if send_draft(
|
|
|
|
self.sign_mail,
|
|
|
|
context,
|
|
|
|
self.account_cursor,
|
|
|
|
self.draft.clone(),
|
|
|
|
) {
|
2019-06-18 19:13:54 +00:00
|
|
|
context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
2019-04-05 22:08:33 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2019-09-27 09:48:48 +00:00
|
|
|
UIEvent::Input(Key::Char('e')) => {
|
2019-03-14 10:19:25 +00:00
|
|
|
/* Edit draft in $EDITOR */
|
|
|
|
use std::process::{Command, Stdio};
|
2019-09-27 09:48:48 +00:00
|
|
|
let settings = &context.settings;
|
|
|
|
let editor = if let Some(editor_cmd) = settings.composing.editor_cmd.as_ref() {
|
|
|
|
editor_cmd.to_string()
|
|
|
|
} else {
|
|
|
|
match std::env::var("EDITOR") {
|
|
|
|
Err(e) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(e.to_string()),
|
|
|
|
"$EDITOR is not set. You can change an envvar's value with setenv or set composing.editor_cmd setting in your configuration.".to_string(),
|
2019-09-15 20:35:30 +00:00
|
|
|
Some(NotificationType::ERROR),
|
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
|
|
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
|
|
|
{
|
|
|
|
context.input_kill();
|
|
|
|
}
|
|
|
|
/* 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
|
|
|
|
2019-09-27 09:48:48 +00:00
|
|
|
let parts = split_command!(editor);
|
|
|
|
let (cmd, args) = (parts[0], &parts[1..]);
|
|
|
|
if let Err(e) = Command::new(cmd)
|
|
|
|
.args(args)
|
2019-03-14 10:19:25 +00:00
|
|
|
.arg(&f.path())
|
|
|
|
.stdin(Stdio::inherit())
|
|
|
|
.stdout(Stdio::inherit())
|
|
|
|
.output()
|
2019-08-01 09:26:35 +00:00
|
|
|
{
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!("Failed to execute {}", editor)),
|
|
|
|
e.to_string(),
|
2019-09-15 20:35:30 +00:00
|
|
|
Some(NotificationType::ERROR),
|
2019-08-01 09:26:35 +00:00
|
|
|
));
|
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
|
|
|
context.restore_input();
|
|
|
|
return true;
|
|
|
|
}
|
2019-03-14 10:19:25 +00:00
|
|
|
let result = f.read_to_string();
|
2019-08-01 09:28:36 +00:00
|
|
|
let mut new_draft = Draft::from_str(result.as_str()).unwrap();
|
|
|
|
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
|
|
|
|
self.draft = new_draft;
|
2019-03-14 10:19:25 +00:00
|
|
|
self.initialized = false;
|
2019-04-10 20:37:20 +00:00
|
|
|
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
2019-03-14 10:19:25 +00:00
|
|
|
context.restore_input();
|
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
|
|
|
}
|
2019-08-01 09:28:36 +00:00
|
|
|
UIEvent::Action(ref a) => {
|
|
|
|
match a {
|
|
|
|
Action::Compose(ComposeAction::AddAttachment(ref path)) => {
|
|
|
|
let mut attachment = match melib::email::attachment_from_file(path) {
|
|
|
|
Ok(a) => a,
|
|
|
|
Err(e) => {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("could not add attachment".to_string()),
|
|
|
|
e.to_string(),
|
2019-09-15 20:35:30 +00:00
|
|
|
Some(NotificationType::ERROR),
|
2019-08-01 09:28:36 +00:00
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if let Ok(mime_type) = query_mime_info(path) {
|
|
|
|
match attachment.content_type {
|
|
|
|
ContentType::Other { ref mut tag, .. } => {
|
|
|
|
*tag = mime_type;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.draft.attachments_mut().push(attachment);
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Action::Compose(ComposeAction::RemoveAttachment(idx)) => {
|
|
|
|
if *idx + 1 > self.draft.attachments().len() {
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
"attachment with given index does not exist".to_string(),
|
|
|
|
),
|
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
self.draft.attachments_mut().remove(*idx);
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage("attachment removed".to_string()),
|
|
|
|
));
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2019-09-28 07:46:49 +00:00
|
|
|
Action::Compose(ComposeAction::ToggleSign) => {
|
|
|
|
let is_true = self.sign_mail.is_true();
|
|
|
|
self.sign_mail = ToggleFlag::from(!is_true);
|
|
|
|
self.dirty = true;
|
|
|
|
return true;
|
|
|
|
}
|
2019-08-01 09:28:36 +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-03-14 10:19:25 +00:00
|
|
|
self.dirty
|
|
|
|
|| self.pager.is_dirty()
|
|
|
|
|| self
|
|
|
|
.reply_context
|
|
|
|
.as_ref()
|
|
|
|
.map(|(_, p)| p.is_dirty())
|
|
|
|
.unwrap_or(false)
|
|
|
|
|| self.form.is_dirty()
|
2018-08-16 13:32:47 +00:00
|
|
|
}
|
2018-08-24 23:23:40 +00:00
|
|
|
|
2018-08-16 13:32:47 +00:00
|
|
|
fn set_dirty(&mut self) {
|
|
|
|
self.dirty = true;
|
|
|
|
self.pager.set_dirty();
|
2019-03-02 06:11:38 +00:00
|
|
|
self.form.set_dirty();
|
2018-09-03 22:49:29 +00:00
|
|
|
if let Some((_, ref mut view)) = self.reply_context {
|
|
|
|
view.set_dirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-07 19:07:13 +00:00
|
|
|
fn kill(&mut self, uuid: Uuid, _context: &mut Context) {
|
2019-10-02 22:03:20 +00:00
|
|
|
self.mode = ViewMode::Discard(
|
|
|
|
uuid,
|
|
|
|
Selector::new(
|
|
|
|
"this draft has unsaved changes",
|
|
|
|
vec![
|
|
|
|
('x', "quit without saving".to_string()),
|
|
|
|
('y', "save draft and quit".to_string()),
|
|
|
|
('n', "cancel".to_string()),
|
|
|
|
],
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
);
|
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-09-27 10:27:07 +00:00
|
|
|
let mut map = if self.mode.is_threadview() {
|
2019-04-04 11:24:05 +00:00
|
|
|
self.pager.get_shortcuts(context)
|
2019-03-30 22:28:01 +00:00
|
|
|
} else {
|
|
|
|
Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some((_, ref view)) = self.reply_context {
|
|
|
|
map.extend(view.get_shortcuts(context));
|
|
|
|
}
|
|
|
|
|
2019-05-10 19:00:56 +00:00
|
|
|
let mut our_map: ShortcutMap = Default::default();
|
2019-09-27 10:27:07 +00:00
|
|
|
if self.mode.is_threadview() {
|
|
|
|
our_map.insert("Switch to right panel (draft editing).", Key::Char('o'));
|
2019-03-30 22:28:01 +00:00
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
if self.mode.is_edit() && self.reply_context.is_some() {
|
|
|
|
our_map.insert("Switch to left panel (thread view)", Key::Char('v'));
|
2019-03-30 22:28:01 +00:00
|
|
|
}
|
2019-09-27 10:27:07 +00:00
|
|
|
our_map.insert("Deliver draft to mailer.", Key::Char('s'));
|
2019-05-10 19:00:56 +00:00
|
|
|
our_map.insert("Edit in $EDITOR", Key::Char('e'));
|
|
|
|
map.insert(Composer::DESCRIPTION.to_string(), 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
|
|
|
|
|
|
|
fn can_quit_cleanly(&mut self) -> bool {
|
|
|
|
/* Play it safe and ask user for confirmation */
|
2019-10-02 22:03:20 +00:00
|
|
|
self.mode = ViewMode::Discard(
|
|
|
|
self.id,
|
|
|
|
Selector::new(
|
|
|
|
"this draft has unsaved changes",
|
|
|
|
vec![
|
|
|
|
('x', "quit without saving".to_string()),
|
|
|
|
('y', "save draft and quit".to_string()),
|
|
|
|
('n', "cancel".to_string()),
|
|
|
|
],
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
);
|
2019-09-27 10:14:16 +00:00
|
|
|
self.set_dirty();
|
|
|
|
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(
|
|
|
|
sign_mail: ToggleFlag,
|
|
|
|
context: &mut Context,
|
|
|
|
account_cursor: usize,
|
|
|
|
mut draft: Draft,
|
|
|
|
) -> bool {
|
2019-06-18 19:13:54 +00:00
|
|
|
use std::io::Write;
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
let mut failure = true;
|
|
|
|
let settings = &context.settings;
|
2019-09-27 09:48:48 +00:00
|
|
|
let parts = split_command!(settings.composing.mailer_cmd);
|
2019-06-18 19:13:54 +00:00
|
|
|
let (cmd, args) = (parts[0], &parts[1..]);
|
|
|
|
let mut msmtp = Command::new(cmd)
|
|
|
|
.args(args)
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.expect("Failed to start mailer command");
|
|
|
|
{
|
|
|
|
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
|
2019-09-28 07:46:49 +00:00
|
|
|
if sign_mail.is_true() {
|
|
|
|
let mut body: AttachmentBuilder = Attachment::new(
|
|
|
|
Default::default(),
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
let output = crate::components::mail::pgp::sign(
|
|
|
|
body.into(),
|
|
|
|
context.settings.pgp.gpg_binary.as_ref().map(String::as_str),
|
|
|
|
context.settings.pgp.key.as_ref().map(String::as_str),
|
|
|
|
);
|
|
|
|
if let Err(e) = &output {
|
|
|
|
debug!("{:?} could not sign draft msg", e);
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Could not sign draft in account `{}`: {}.",
|
|
|
|
context.accounts[account_cursor].name(),
|
|
|
|
e.to_string()
|
|
|
|
),
|
|
|
|
ERROR,
|
|
|
|
);
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!(
|
|
|
|
"Could not sign draft in account `{}`.",
|
|
|
|
context.accounts[account_cursor].name()
|
|
|
|
)),
|
|
|
|
e.to_string(),
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
draft.attachments.push(output.unwrap());
|
|
|
|
}
|
2019-06-18 19:13:54 +00:00
|
|
|
let draft = draft.finalise().unwrap();
|
|
|
|
stdin
|
|
|
|
.write_all(draft.as_bytes())
|
|
|
|
.expect("Failed to write to stdin");
|
2019-09-23 06:30:23 +00:00
|
|
|
for folder in &[
|
|
|
|
&context.accounts[account_cursor].special_use_folder(SpecialUseMailbox::Sent),
|
|
|
|
&context.accounts[account_cursor].special_use_folder(SpecialUseMailbox::Inbox),
|
|
|
|
&context.accounts[account_cursor].special_use_folder(SpecialUseMailbox::Normal),
|
|
|
|
] {
|
2019-09-27 10:25:13 +00:00
|
|
|
if folder.is_none() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let folder = folder.unwrap();
|
2019-09-23 06:30:23 +00:00
|
|
|
if let Err(e) =
|
|
|
|
context.accounts[account_cursor].save(draft.as_bytes(), folder, Some(Flag::SEEN))
|
|
|
|
{
|
|
|
|
debug!("{:?} could not save sent msg", e);
|
2019-09-27 10:25:13 +00:00
|
|
|
log(
|
|
|
|
format!("Could not save in '{}' folder: {}.", folder, e.to_string()),
|
|
|
|
ERROR,
|
|
|
|
);
|
2019-09-23 06:30:23 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some(format!("Could not save in '{}' folder.", folder)),
|
|
|
|
e.into(),
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
failure = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if failure {
|
|
|
|
let file = create_temp_file(draft.as_bytes(), None, None, false);
|
|
|
|
debug!("message saved in {}", file.path.display());
|
2019-09-27 10:25:13 +00:00
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Message was stored in {} so that you can restore it manually.",
|
|
|
|
file.path.display()
|
|
|
|
),
|
|
|
|
INFO,
|
|
|
|
);
|
2019-06-18 19:13:54 +00:00
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2019-09-23 06:30:23 +00:00
|
|
|
Some("Could not save in any folder".into()),
|
|
|
|
format!(
|
|
|
|
"Message was stored in {} so that you can restore it manually.",
|
|
|
|
file.path.display()
|
|
|
|
),
|
|
|
|
Some(NotificationType::INFO),
|
2019-06-18 19:13:54 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2019-09-23 06:30:23 +00:00
|
|
|
let output = msmtp.wait().expect("Failed to wait on mailer");
|
|
|
|
if output.success() {
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
Some("Sent.".into()),
|
|
|
|
String::new(),
|
|
|
|
None,
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
if let Some(exit_code) = output.code() {
|
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Could not send e-mail using `{}`: Process exited with {}",
|
|
|
|
cmd, exit_code
|
|
|
|
),
|
|
|
|
ERROR,
|
|
|
|
);
|
2019-09-15 18:53:25 +00:00
|
|
|
} else {
|
2019-09-23 06:30:23 +00:00
|
|
|
log(
|
|
|
|
format!(
|
|
|
|
"Could not send e-mail using `{}`: Process was killed by signal",
|
|
|
|
cmd
|
|
|
|
),
|
|
|
|
ERROR,
|
|
|
|
);
|
2019-09-15 18:53:25 +00:00
|
|
|
}
|
2019-06-18 19:13:54 +00:00
|
|
|
}
|
2019-07-06 09:47:27 +00:00
|
|
|
!failure
|
2019-06-18 19:13:54 +00:00
|
|
|
}
|