2018-08-07 15:01:15 +03: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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2018-07-22 18:02:44 +03:00
|
|
|
|
use super::*;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
use linkify::{Link, LinkFinder};
|
2019-06-18 21:13:58 +03:00
|
|
|
|
|
2019-06-18 22:13:54 +03:00
|
|
|
|
use std::convert::TryFrom;
|
2018-07-23 15:40:13 +03:00
|
|
|
|
use std::process::{Command, Stdio};
|
2018-07-22 18:02:44 +03:00
|
|
|
|
|
2019-10-03 12:22:01 +03:00
|
|
|
|
pub mod list_management;
|
2019-06-18 22:01:02 +03:00
|
|
|
|
|
2018-08-09 16:50:33 +03:00
|
|
|
|
mod html;
|
|
|
|
|
pub use self::html::*;
|
2018-08-11 18:00:21 +03:00
|
|
|
|
mod thread;
|
|
|
|
|
pub use self::thread::*;
|
2018-08-09 16:50:33 +03:00
|
|
|
|
|
2018-08-18 17:50:31 +03:00
|
|
|
|
mod envelope;
|
|
|
|
|
pub use self::envelope::*;
|
|
|
|
|
|
2018-07-25 19:05:42 +03:00
|
|
|
|
use mime_apps::query_default_app;
|
|
|
|
|
|
2019-09-15 14:10:06 +03:00
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2018-07-23 15:40:13 +03:00
|
|
|
|
enum ViewMode {
|
|
|
|
|
Normal,
|
|
|
|
|
Url,
|
2018-07-26 00:07:00 +03:00
|
|
|
|
Attachment(usize),
|
2018-08-05 12:44:31 +03:00
|
|
|
|
Raw,
|
2018-08-09 16:50:33 +03:00
|
|
|
|
Subview,
|
2019-10-03 01:03:20 +03:00
|
|
|
|
ContactSelector(Selector<Card>),
|
2018-07-25 22:37:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-23 14:39:54 +03:00
|
|
|
|
impl Default for ViewMode {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
ViewMode::Normal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-25 22:37:28 +03:00
|
|
|
|
impl ViewMode {
|
|
|
|
|
fn is_attachment(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
ViewMode::Attachment(_) => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-03 01:03:20 +03:00
|
|
|
|
fn is_contact_selector(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
ViewMode::ContactSelector(_) => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-23 15:40:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-22 23:11:07 +03:00
|
|
|
|
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
|
|
|
|
|
/// menus
|
2018-08-23 14:39:54 +03:00
|
|
|
|
#[derive(Debug, Default)]
|
2018-07-22 18:02:44 +03:00
|
|
|
|
pub struct MailView {
|
2018-09-06 13:05:35 +03:00
|
|
|
|
coordinates: (usize, usize, EnvelopeHash),
|
2018-07-22 18:02:44 +03:00
|
|
|
|
pager: Option<Pager>,
|
2019-09-09 11:54:47 +03:00
|
|
|
|
subview: Option<Box<dyn Component>>,
|
2018-07-22 18:02:44 +03:00
|
|
|
|
dirty: bool,
|
2018-07-23 15:40:13 +03:00
|
|
|
|
mode: ViewMode,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
expand_headers: bool,
|
2018-07-23 15:40:13 +03:00
|
|
|
|
|
|
|
|
|
cmd_buf: String,
|
2019-04-10 22:01:02 +03:00
|
|
|
|
id: ComponentId,
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-15 14:10:06 +03:00
|
|
|
|
impl Clone for MailView {
|
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
|
MailView {
|
|
|
|
|
subview: None,
|
|
|
|
|
cmd_buf: String::with_capacity(4),
|
|
|
|
|
pager: self.pager.clone(),
|
|
|
|
|
mode: self.mode.clone(),
|
|
|
|
|
..*self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 18:00:21 +03:00
|
|
|
|
impl fmt::Display for MailView {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
// TODO display subject/info
|
2019-05-10 22:00:56 +03:00
|
|
|
|
write!(f, "{}", MailView::DESCRIPTION)
|
2018-08-11 18:00:21 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-22 18:02:44 +03:00
|
|
|
|
impl MailView {
|
2019-05-10 22:00:56 +03:00
|
|
|
|
const DESCRIPTION: &'static str = "mail";
|
2018-07-27 18:01:52 +03:00
|
|
|
|
pub fn new(
|
2018-09-06 13:05:35 +03:00
|
|
|
|
coordinates: (usize, usize, EnvelopeHash),
|
2018-07-27 18:01:52 +03:00
|
|
|
|
pager: Option<Pager>,
|
2019-09-09 11:54:47 +03:00
|
|
|
|
subview: Option<Box<dyn Component>>,
|
2018-08-23 15:36:52 +03:00
|
|
|
|
) -> Self {
|
2018-07-22 18:02:44 +03:00
|
|
|
|
MailView {
|
2018-08-07 15:01:15 +03:00
|
|
|
|
coordinates,
|
|
|
|
|
pager,
|
|
|
|
|
subview,
|
2018-07-22 18:02:44 +03:00
|
|
|
|
dirty: true,
|
2018-07-23 15:40:13 +03:00
|
|
|
|
mode: ViewMode::Normal,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
expand_headers: false,
|
2018-07-23 15:40:13 +03:00
|
|
|
|
|
|
|
|
|
cmd_buf: String::with_capacity(4),
|
2019-04-11 00:04:17 +03:00
|
|
|
|
id: ComponentId::new_v4(),
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-08 19:06:00 +03:00
|
|
|
|
|
|
|
|
|
/// Returns the string to be displayed in the Viewer
|
2019-04-06 00:30:06 +03:00
|
|
|
|
fn attachment_to_text<'closure, 's: 'closure, 'context: 's>(
|
|
|
|
|
&'s self,
|
|
|
|
|
body: &'context Attachment,
|
|
|
|
|
context: &'context mut Context,
|
|
|
|
|
) -> String {
|
2018-08-08 19:06:00 +03:00
|
|
|
|
let finder = LinkFinder::new();
|
2018-08-18 17:50:31 +03:00
|
|
|
|
let body_text = String::from_utf8_lossy(&decode_rec(
|
2019-04-06 00:30:06 +03:00
|
|
|
|
body,
|
|
|
|
|
Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| {
|
2018-08-23 15:36:52 +03:00
|
|
|
|
if a.content_type().is_text_html() {
|
|
|
|
|
use std::io::Write;
|
2019-10-03 19:51:34 +03:00
|
|
|
|
let settings = &context.settings;
|
2019-05-03 23:51:21 +03:00
|
|
|
|
/* FIXME: duplication with view/html.rs */
|
2019-10-03 19:51:34 +03:00
|
|
|
|
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
|
2019-04-06 00:30:06 +03:00
|
|
|
|
let parts = split_command!(filter_invocation);
|
|
|
|
|
let (cmd, args) = (parts[0], &parts[1..]);
|
|
|
|
|
let command_obj = Command::new(cmd)
|
|
|
|
|
.args(args)
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn();
|
|
|
|
|
if command_obj.is_err() {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!(
|
|
|
|
|
"Failed to start html filter process: {}",
|
|
|
|
|
filter_invocation,
|
|
|
|
|
)),
|
|
|
|
|
String::new(),
|
2019-09-15 23:35:30 +03:00
|
|
|
|
Some(NotificationType::ERROR),
|
2019-04-10 23:37:20 +03:00
|
|
|
|
));
|
2019-04-06 00:30:06 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-08-08 19:06:00 +03:00
|
|
|
|
|
2019-04-06 00:30:06 +03:00
|
|
|
|
let mut html_filter = command_obj.unwrap();
|
|
|
|
|
html_filter
|
|
|
|
|
.stdin
|
|
|
|
|
.as_mut()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&v)
|
|
|
|
|
.expect("Failed to write to stdin");
|
2019-04-06 01:08:33 +03:00
|
|
|
|
*v = format!(
|
|
|
|
|
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
|
|
|
|
|
filter_invocation
|
|
|
|
|
)
|
|
|
|
|
.into_bytes();
|
2019-04-06 00:30:06 +03:00
|
|
|
|
v.extend(html_filter.wait_with_output().unwrap().stdout);
|
2019-05-03 23:51:21 +03:00
|
|
|
|
} else {
|
|
|
|
|
if let Ok(mut html_filter) = Command::new("w3m")
|
|
|
|
|
.args(&["-I", "utf-8", "-T", "text/html"])
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
|
|
|
|
{
|
|
|
|
|
html_filter
|
|
|
|
|
.stdin
|
|
|
|
|
.as_mut()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&v)
|
|
|
|
|
.expect("Failed to write to html filter stdin");
|
|
|
|
|
*v = String::from(
|
|
|
|
|
"Text piped through `w3m`. Press `v` to open in web browser. \n\n",
|
|
|
|
|
)
|
|
|
|
|
.into_bytes();
|
|
|
|
|
v.extend(html_filter.wait_with_output().unwrap().stdout);
|
|
|
|
|
} else {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2019-06-18 21:13:58 +03:00
|
|
|
|
Some(
|
2019-05-03 23:51:21 +03:00
|
|
|
|
"Failed to find any application to use as html filter"
|
2019-06-18 21:13:58 +03:00
|
|
|
|
.to_string(),
|
|
|
|
|
),
|
2019-05-03 23:51:21 +03:00
|
|
|
|
String::new(),
|
2019-09-15 23:35:30 +03:00
|
|
|
|
Some(NotificationType::ERROR),
|
2019-05-03 23:51:21 +03:00
|
|
|
|
));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-06 00:30:06 +03:00
|
|
|
|
}
|
2019-09-25 23:12:38 +03:00
|
|
|
|
} else if a.is_signed() {
|
|
|
|
|
v.clear();
|
2019-09-26 12:10:36 +03:00
|
|
|
|
if context.settings.pgp.auto_verify_signatures {
|
2019-09-28 10:46:49 +03:00
|
|
|
|
v.extend(crate::mail::pgp::verify_signature(a, context).into_iter());
|
2019-09-25 23:12:38 +03:00
|
|
|
|
}
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
|
|
|
|
})),
|
2019-03-14 12:19:25 +02:00
|
|
|
|
))
|
|
|
|
|
.into_owned();
|
2018-08-08 19:06:00 +03:00
|
|
|
|
match self.mode {
|
2019-11-10 13:33:56 +02:00
|
|
|
|
ViewMode::Normal | ViewMode::Subview | ViewMode::ContactSelector(_) => {
|
2018-08-08 19:06:00 +03:00
|
|
|
|
let mut t = body_text.to_string();
|
2019-05-12 15:10:08 +03:00
|
|
|
|
t.push('\n');
|
2018-08-08 19:06:00 +03:00
|
|
|
|
if body.count_attachments() > 1 {
|
2018-08-11 18:00:21 +03:00
|
|
|
|
t = body
|
|
|
|
|
.attachments()
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.fold(t, |mut s, (idx, a)| {
|
2019-05-12 15:10:08 +03:00
|
|
|
|
s.push_str(&format!("\n[{}] {}\n", idx, a));
|
2018-08-08 19:06:00 +03:00
|
|
|
|
s
|
2018-08-11 18:00:21 +03:00
|
|
|
|
});
|
2018-08-08 19:06:00 +03:00
|
|
|
|
}
|
|
|
|
|
t
|
|
|
|
|
}
|
2019-09-25 22:00:30 +03:00
|
|
|
|
ViewMode::Raw => String::from_utf8_lossy(body.body()).into_owned(),
|
2018-08-08 19:06:00 +03:00
|
|
|
|
ViewMode::Url => {
|
|
|
|
|
let mut t = body_text.to_string();
|
|
|
|
|
for (lidx, l) in finder.links(&body.text()).enumerate() {
|
|
|
|
|
let offset = if lidx < 10 {
|
|
|
|
|
lidx * 3
|
|
|
|
|
} else if lidx < 100 {
|
|
|
|
|
26 + (lidx - 9) * 4
|
|
|
|
|
} else if lidx < 1000 {
|
|
|
|
|
385 + (lidx - 99) * 5
|
|
|
|
|
} else {
|
2019-05-03 23:51:21 +03:00
|
|
|
|
panic!("FIXME: Message body with more than 100 urls, fix this");
|
2018-08-08 19:06:00 +03:00
|
|
|
|
};
|
|
|
|
|
t.insert_str(l.start() + offset, &format!("[{}]", lidx));
|
|
|
|
|
}
|
|
|
|
|
if body.count_attachments() > 1 {
|
2018-08-11 18:00:21 +03:00
|
|
|
|
t = body
|
|
|
|
|
.attachments()
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.fold(t, |mut s, (idx, a)| {
|
2018-08-08 19:06:00 +03:00
|
|
|
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
|
|
|
|
s
|
2018-08-11 18:00:21 +03:00
|
|
|
|
});
|
2018-08-08 19:06:00 +03:00
|
|
|
|
}
|
|
|
|
|
t
|
|
|
|
|
}
|
|
|
|
|
ViewMode::Attachment(aidx) => {
|
|
|
|
|
let attachments = body.attachments();
|
|
|
|
|
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
|
|
|
|
|
ret.push_str(&attachments[aidx].text());
|
|
|
|
|
ret
|
2019-03-14 12:19:25 +02:00
|
|
|
|
}
|
2018-08-08 19:06:00 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-23 15:36:52 +03:00
|
|
|
|
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
|
2018-08-09 16:50:33 +03:00
|
|
|
|
let mut buf = CellBuffer::from(s);
|
|
|
|
|
|
|
|
|
|
if highlight_urls {
|
2019-07-14 14:52:31 +03:00
|
|
|
|
let lines: Vec<&str> = s.split('\n').map(|l| l.trim_end()).collect();
|
2018-08-09 16:50:33 +03:00
|
|
|
|
let mut shift = 0;
|
|
|
|
|
let mut lidx_total = 0;
|
|
|
|
|
let finder = LinkFinder::new();
|
|
|
|
|
for r in &lines {
|
|
|
|
|
for l in finder.links(&r) {
|
|
|
|
|
let offset = if lidx_total < 10 {
|
|
|
|
|
3
|
|
|
|
|
} else if lidx_total < 100 {
|
|
|
|
|
4
|
|
|
|
|
} else if lidx_total < 1000 {
|
|
|
|
|
5
|
|
|
|
|
} else {
|
|
|
|
|
panic!("BUG: Message body with more than 100 urls");
|
|
|
|
|
};
|
|
|
|
|
for i in 1..=offset {
|
|
|
|
|
buf[(l.start() + shift - i, 0)].set_fg(Color::Byte(226));
|
|
|
|
|
//buf[(l.start() + shift - 2, 0)].set_fg(Color::Byte(226));
|
|
|
|
|
//buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
|
|
|
|
|
}
|
|
|
|
|
lidx_total += 1;
|
|
|
|
|
}
|
|
|
|
|
// Each Cell represents one char so next line will be:
|
|
|
|
|
shift += r.chars().count() + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
buf
|
|
|
|
|
}
|
2019-06-05 01:27:13 +03:00
|
|
|
|
|
|
|
|
|
pub fn update(&mut self, new_coordinates: (usize, usize, EnvelopeHash)) {
|
|
|
|
|
self.coordinates = new_coordinates;
|
|
|
|
|
self.mode = ViewMode::Normal;
|
|
|
|
|
self.set_dirty();
|
|
|
|
|
}
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Component for MailView {
|
|
|
|
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
2019-05-01 13:55:12 +03:00
|
|
|
|
if !self.is_dirty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-07-22 18:02:44 +03:00
|
|
|
|
let upper_left = upper_left!(area);
|
|
|
|
|
let bottom_right = bottom_right!(area);
|
|
|
|
|
|
2019-05-03 23:51:21 +03:00
|
|
|
|
let y: usize = {
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-06-18 21:13:58 +03:00
|
|
|
|
if !account.contains_key(self.coordinates.2) {
|
2019-05-03 23:51:21 +03:00
|
|
|
|
/* The envelope has been renamed or removed, so wait for the appropriate event to
|
|
|
|
|
* arrive */
|
2019-09-25 23:14:24 +03:00
|
|
|
|
self.dirty = false;
|
2019-05-03 23:51:21 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-06-06 00:27:40 +03:00
|
|
|
|
let (hash, is_seen) = {
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-06-06 00:27:40 +03:00
|
|
|
|
(envelope.hash(), envelope.is_seen())
|
|
|
|
|
};
|
|
|
|
|
if !is_seen {
|
2019-06-18 21:13:58 +03:00
|
|
|
|
let op = account.operation(hash);
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let mut envelope: EnvelopeRefMut =
|
|
|
|
|
account.collection.get_env_mut(self.coordinates.2);
|
2019-07-18 20:16:51 +03:00
|
|
|
|
if let Err(e) = envelope.set_seen(op) {
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Could not set message as seen: {}",
|
|
|
|
|
e
|
|
|
|
|
))));
|
|
|
|
|
}
|
2019-06-06 00:27:40 +03:00
|
|
|
|
}
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-05-01 13:55:12 +03:00
|
|
|
|
|
2019-10-06 10:58:47 +03:00
|
|
|
|
let header_fg = if context.settings.terminal.theme == "light" {
|
|
|
|
|
Color::Black
|
|
|
|
|
} else {
|
|
|
|
|
Color::Byte(33)
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-03 23:51:21 +03:00
|
|
|
|
if self.mode == ViewMode::Raw {
|
|
|
|
|
clear_area(grid, area);
|
|
|
|
|
context.dirty_areas.push_back(area);
|
|
|
|
|
get_y(upper_left) - 1
|
|
|
|
|
} else {
|
|
|
|
|
let (x, y) = write_string_to_grid(
|
|
|
|
|
&format!("Date: {}", envelope.date_as_str()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
area,
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
2018-08-05 12:44:31 +03:00
|
|
|
|
}
|
2019-05-03 23:51:21 +03:00
|
|
|
|
let (x, y) = write_string_to_grid(
|
|
|
|
|
&format!("From: {}", envelope.field_from_to_string()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
|
|
|
|
}
|
|
|
|
|
let (x, y) = write_string_to_grid(
|
|
|
|
|
&format!("To: {}", envelope.field_to_to_string()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
|
|
|
|
}
|
|
|
|
|
let (x, y) = write_string_to_grid(
|
|
|
|
|
&format!("Subject: {}", envelope.subject()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
|
|
|
|
}
|
2019-06-05 01:27:13 +03:00
|
|
|
|
let (x, mut y) = write_string_to_grid(
|
2019-05-03 23:51:21 +03:00
|
|
|
|
&format!("Message-ID: <{}>", envelope.message_id_raw()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-05-03 23:51:21 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
|
|
|
|
}
|
2019-06-05 01:27:13 +03:00
|
|
|
|
if self.expand_headers && envelope.in_reply_to().is_some() {
|
|
|
|
|
let (x, _y) = write_string_to_grid(
|
|
|
|
|
&format!("In-Reply-To: {}", envelope.in_reply_to_display().unwrap()),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, _y)].set_ch(' ');
|
|
|
|
|
grid[(x, _y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, _y)].set_fg(Color::Default);
|
|
|
|
|
}
|
|
|
|
|
let (x, _y) = write_string_to_grid(
|
|
|
|
|
&format!(
|
|
|
|
|
"References: {}",
|
|
|
|
|
envelope
|
|
|
|
|
.references()
|
|
|
|
|
.iter()
|
2019-06-18 21:13:58 +03:00
|
|
|
|
.map(std::string::ToString::to_string)
|
2019-06-05 01:27:13 +03:00
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
),
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-05 01:27:13 +03:00
|
|
|
|
(set_y(upper_left, _y + 1), bottom_right),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, _y)].set_ch(' ');
|
|
|
|
|
grid[(x, _y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, _y)].set_fg(Color::Default);
|
|
|
|
|
}
|
|
|
|
|
y = _y;
|
|
|
|
|
}
|
2019-06-18 22:13:54 +03:00
|
|
|
|
if let Some(list_management::ListActions {
|
|
|
|
|
ref id,
|
|
|
|
|
ref archive,
|
|
|
|
|
ref post,
|
|
|
|
|
ref unsubscribe,
|
2019-11-02 12:14:31 +02:00
|
|
|
|
}) = list_management::detect(&envelope)
|
2019-06-18 22:13:54 +03:00
|
|
|
|
{
|
|
|
|
|
let mut x = get_x(upper_left);
|
|
|
|
|
y += 1;
|
|
|
|
|
if let Some(id) = id {
|
|
|
|
|
let (_x, _) = write_string_to_grid(
|
|
|
|
|
"List-ID: ",
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
(set_y(upper_left, y), bottom_right),
|
|
|
|
|
false,
|
|
|
|
|
);
|
2019-10-06 11:33:18 +03:00
|
|
|
|
let (_x, _y) = write_string_to_grid(
|
2019-06-18 22:13:54 +03:00
|
|
|
|
id,
|
|
|
|
|
grid,
|
|
|
|
|
Color::Default,
|
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
((_x, y), bottom_right),
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
x = _x;
|
2019-10-06 11:33:18 +03:00
|
|
|
|
if _y != y {
|
|
|
|
|
x = get_x(upper_left);
|
|
|
|
|
}
|
|
|
|
|
y = _y;
|
2019-06-18 22:13:54 +03:00
|
|
|
|
}
|
|
|
|
|
if archive.is_some() || post.is_some() || unsubscribe.is_some() {
|
2019-10-06 11:33:18 +03:00
|
|
|
|
let (_x, _y) = write_string_to_grid(
|
2019-06-18 22:13:54 +03:00
|
|
|
|
" Available actions: [ ",
|
|
|
|
|
grid,
|
2019-10-06 10:58:47 +03:00
|
|
|
|
header_fg,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
((x, y), bottom_right),
|
2019-10-06 11:33:18 +03:00
|
|
|
|
true,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
);
|
|
|
|
|
x = _x;
|
2019-10-06 11:33:18 +03:00
|
|
|
|
if _y != y {
|
|
|
|
|
x = get_x(upper_left);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
y = _y;
|
2019-06-18 22:13:54 +03:00
|
|
|
|
}
|
|
|
|
|
if archive.is_some() {
|
2019-10-06 11:33:18 +03:00
|
|
|
|
let (_x, _y) = write_string_to_grid(
|
2019-06-18 22:13:54 +03:00
|
|
|
|
"list-archive, ",
|
|
|
|
|
grid,
|
|
|
|
|
Color::Default,
|
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
((x, y), bottom_right),
|
2019-10-06 11:33:18 +03:00
|
|
|
|
true,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
);
|
|
|
|
|
x = _x;
|
2019-10-06 11:33:18 +03:00
|
|
|
|
if _y != y {
|
|
|
|
|
x = get_x(upper_left);
|
|
|
|
|
}
|
|
|
|
|
y = _y;
|
2019-06-18 22:13:54 +03:00
|
|
|
|
}
|
|
|
|
|
if post.is_some() {
|
2019-10-06 11:33:18 +03:00
|
|
|
|
let (_x, _y) = write_string_to_grid(
|
2019-06-18 22:13:54 +03:00
|
|
|
|
"list-post, ",
|
|
|
|
|
grid,
|
|
|
|
|
Color::Default,
|
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
((x, y), bottom_right),
|
2019-10-06 11:33:18 +03:00
|
|
|
|
true,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
);
|
|
|
|
|
x = _x;
|
2019-10-06 11:33:18 +03:00
|
|
|
|
if _y != y {
|
|
|
|
|
x = get_x(upper_left);
|
|
|
|
|
}
|
|
|
|
|
y = _y;
|
2019-06-18 22:13:54 +03:00
|
|
|
|
}
|
|
|
|
|
if unsubscribe.is_some() {
|
2019-10-06 11:33:18 +03:00
|
|
|
|
let (_x, _y) = write_string_to_grid(
|
2019-06-18 22:13:54 +03:00
|
|
|
|
"list-unsubscribe, ",
|
|
|
|
|
grid,
|
|
|
|
|
Color::Default,
|
|
|
|
|
Color::Default,
|
2019-08-18 15:44:40 +03:00
|
|
|
|
Attr::Default,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
((x, y), bottom_right),
|
2019-10-06 11:33:18 +03:00
|
|
|
|
true,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
);
|
|
|
|
|
x = _x;
|
2019-10-06 11:33:18 +03:00
|
|
|
|
if _y != y {
|
|
|
|
|
x = get_x(upper_left);
|
|
|
|
|
}
|
|
|
|
|
y = _y;
|
2019-06-18 22:13:54 +03:00
|
|
|
|
}
|
|
|
|
|
if archive.is_some() || post.is_some() || unsubscribe.is_some() {
|
|
|
|
|
grid[(x - 2, y)].set_ch(' ');
|
2019-10-06 10:58:47 +03:00
|
|
|
|
grid[(x - 1, y)].set_fg(header_fg);
|
2019-06-18 22:13:54 +03:00
|
|
|
|
grid[(x - 1, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x - 1, y)].set_ch(']');
|
|
|
|
|
}
|
|
|
|
|
for x in x..=get_x(bottom_right) {
|
|
|
|
|
grid[(x, y)].set_ch(' ');
|
|
|
|
|
grid[(x, y)].set_bg(Color::Default);
|
|
|
|
|
grid[(x, y)].set_fg(Color::Default);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-03 23:51:21 +03:00
|
|
|
|
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 1)));
|
|
|
|
|
context
|
|
|
|
|
.dirty_areas
|
|
|
|
|
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
|
|
|
|
y + 1
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-07-23 15:40:13 +03:00
|
|
|
|
|
2019-05-03 23:51:21 +03:00
|
|
|
|
if self.dirty {
|
2019-04-06 00:30:06 +03:00
|
|
|
|
let body = {
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-06-18 21:13:58 +03:00
|
|
|
|
let op = account.operation(envelope.hash());
|
2019-09-27 13:18:59 +03:00
|
|
|
|
match envelope.body(op) {
|
|
|
|
|
Ok(body) => body,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to open e-mail".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open envelope {}: {}",
|
|
|
|
|
envelope.message_id_display(),
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-06 00:30:06 +03:00
|
|
|
|
};
|
2018-08-09 16:50:33 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
2018-09-04 01:49:29 +03:00
|
|
|
|
self.pager = None;
|
2019-04-06 00:30:06 +03:00
|
|
|
|
let attachment = &body.attachments()[aidx];
|
2019-10-03 19:51:34 +03:00
|
|
|
|
self.subview = Some(Box::new(HtmlView::new(&attachment, context)));
|
2018-09-04 01:49:29 +03:00
|
|
|
|
self.mode = ViewMode::Subview;
|
2018-08-11 18:00:21 +03:00
|
|
|
|
}
|
2018-08-09 16:50:33 +03:00
|
|
|
|
ViewMode::Normal if body.is_html() => {
|
2019-10-03 19:51:34 +03:00
|
|
|
|
self.subview = Some(Box::new(HtmlView::new(&body, context)));
|
2018-09-04 01:49:29 +03:00
|
|
|
|
self.pager = None;
|
2018-08-09 16:50:33 +03:00
|
|
|
|
self.mode = ViewMode::Subview;
|
2018-08-11 18:00:21 +03:00
|
|
|
|
}
|
2019-02-26 10:55:22 +02:00
|
|
|
|
ViewMode::Subview | ViewMode::ContactSelector(_) => {}
|
2019-05-07 21:53:36 +03:00
|
|
|
|
ViewMode::Raw => {
|
|
|
|
|
let text = {
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-06-18 21:13:58 +03:00
|
|
|
|
let mut op = account.operation(envelope.hash());
|
2019-05-07 21:53:36 +03:00
|
|
|
|
op.as_bytes()
|
|
|
|
|
.map(|v| String::from_utf8_lossy(v).into_owned())
|
|
|
|
|
.unwrap_or_else(|e| e.to_string())
|
|
|
|
|
};
|
2019-11-09 17:44:22 +02:00
|
|
|
|
self.pager = Some(Pager::from_string(text, Some(context), None, None));
|
2019-05-07 21:53:36 +03:00
|
|
|
|
self.subview = None;
|
|
|
|
|
}
|
2018-08-09 16:50:33 +03:00
|
|
|
|
_ => {
|
2019-03-18 15:41:38 +02:00
|
|
|
|
let text = {
|
2019-04-06 00:30:06 +03:00
|
|
|
|
self.attachment_to_text(&body, context)
|
2019-03-18 15:41:38 +02:00
|
|
|
|
/*
|
2018-08-09 16:50:33 +03:00
|
|
|
|
// URL indexes must be colored (ugh..)
|
|
|
|
|
MailView::plain_text_to_buf(&text, self.mode == ViewMode::Url)
|
2019-03-18 15:41:38 +02:00
|
|
|
|
*/
|
2018-08-09 16:50:33 +03:00
|
|
|
|
};
|
|
|
|
|
let cursor_pos = if self.mode.is_attachment() {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
|
|
|
|
self.pager.as_mut().map(|p| p.cursor_pos())
|
|
|
|
|
};
|
2019-11-09 17:44:22 +02:00
|
|
|
|
self.pager = Some(Pager::from_string(text, Some(context), cursor_pos, None));
|
2018-09-04 01:49:29 +03:00
|
|
|
|
self.subview = None;
|
2018-08-11 18:00:21 +03:00
|
|
|
|
}
|
2018-07-23 15:40:13 +03:00
|
|
|
|
};
|
2019-05-03 23:51:21 +03:00
|
|
|
|
}
|
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Subview if self.subview.is_some() => {
|
|
|
|
|
if let Some(s) = self.subview.as_mut() {
|
2018-09-04 01:49:29 +03:00
|
|
|
|
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
|
|
|
|
}
|
2019-05-03 23:51:21 +03:00
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if let Some(p) = self.pager.as_mut() {
|
|
|
|
|
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
2018-09-04 01:49:29 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-07 15:01:15 +03:00
|
|
|
|
}
|
2019-10-03 01:03:20 +03:00
|
|
|
|
if let ViewMode::ContactSelector(ref mut s) = self.mode {
|
|
|
|
|
s.draw(grid, center_area(area, s.content.size()), context);
|
|
|
|
|
}
|
2019-09-25 23:14:24 +03:00
|
|
|
|
self.dirty = false;
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-26 17:50:47 +02:00
|
|
|
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
2018-09-04 01:49:29 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Subview => {
|
|
|
|
|
if let Some(s) = self.subview.as_mut() {
|
|
|
|
|
if s.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-14 12:19:25 +02:00
|
|
|
|
}
|
2019-02-15 09:06:42 +02:00
|
|
|
|
ViewMode::ContactSelector(ref mut s) => {
|
|
|
|
|
if s.process_event(event, context) {
|
2019-10-03 01:03:20 +03:00
|
|
|
|
if s.is_done() {
|
|
|
|
|
if let ViewMode::ContactSelector(s) =
|
|
|
|
|
std::mem::replace(&mut self.mode, ViewMode::Normal)
|
|
|
|
|
{
|
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
|
|
|
|
{
|
|
|
|
|
for card in s.collect() {
|
|
|
|
|
account.address_book.add_card(card);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.set_dirty();
|
|
|
|
|
}
|
2019-02-15 09:06:42 +02:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-10-03 19:11:02 +03:00
|
|
|
|
if let Some(p) = self.pager.as_mut() {
|
|
|
|
|
if p.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-14 12:19:25 +02:00
|
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
|
_ => {
|
|
|
|
|
if let Some(p) = self.pager.as_mut() {
|
|
|
|
|
if p.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
|
|
2019-11-11 22:20:16 +02:00
|
|
|
|
let shortcuts = &self.get_shortcuts(context)[MailView::DESCRIPTION];
|
2019-04-10 23:37:20 +03:00
|
|
|
|
match *event {
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if !self.mode.is_contact_selector()
|
|
|
|
|
&& *key == shortcuts["add_addresses_to_contacts"] =>
|
|
|
|
|
{
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-03-31 20:08:37 +03:00
|
|
|
|
|
2019-02-15 09:06:42 +02:00
|
|
|
|
let mut entries = Vec::new();
|
2019-10-03 01:03:20 +03:00
|
|
|
|
for addr in envelope.from().iter().chain(envelope.to().iter()) {
|
|
|
|
|
let mut new_card: Card = Card::new();
|
|
|
|
|
new_card.set_email(addr.get_email());
|
|
|
|
|
new_card.set_name(addr.get_display_name());
|
|
|
|
|
entries.push((new_card, format!("{}", addr)));
|
2019-03-31 20:08:37 +03:00
|
|
|
|
}
|
2019-11-02 12:14:31 +02:00
|
|
|
|
drop(envelope);
|
2019-10-03 01:03:20 +03:00
|
|
|
|
self.mode = ViewMode::ContactSelector(Selector::new(
|
|
|
|
|
"select contacts to add",
|
|
|
|
|
entries,
|
|
|
|
|
false,
|
2019-10-06 11:28:12 +03:00
|
|
|
|
context,
|
2019-10-03 01:03:20 +03:00
|
|
|
|
));
|
2019-02-26 10:55:22 +02:00
|
|
|
|
self.dirty = true;
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2019-03-14 12:19:25 +02:00
|
|
|
|
}
|
2019-11-10 13:33:56 +02:00
|
|
|
|
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
|
|
|
|
|
if self.mode.is_contact_selector() =>
|
|
|
|
|
{
|
|
|
|
|
self.mode = ViewMode::Normal;
|
|
|
|
|
self.set_dirty();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) => {
|
2018-07-25 22:37:28 +03:00
|
|
|
|
self.cmd_buf.clear();
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
|
2018-07-24 16:55:00 +03:00
|
|
|
|
self.cmd_buf.push(c);
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
|
|
|
|
|
self.cmd_buf.clone(),
|
|
|
|
|
)));
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview)
|
|
|
|
|
&& *key == shortcuts["view_raw_source"] =>
|
2018-08-07 15:01:15 +03:00
|
|
|
|
{
|
2019-05-07 01:57:44 +03:00
|
|
|
|
self.mode = ViewMode::Raw;
|
|
|
|
|
self.set_dirty();
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-08-05 12:44:31 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode.is_attachment()
|
2019-05-07 01:57:44 +03:00
|
|
|
|
|| self.mode == ViewMode::Subview
|
|
|
|
|
|| self.mode == ViewMode::Url
|
2019-11-11 22:20:16 +02:00
|
|
|
|
|| self.mode == ViewMode::Raw)
|
|
|
|
|
&& *key == shortcuts["return_to_normal_view"] =>
|
2018-08-23 15:36:52 +03:00
|
|
|
|
{
|
2018-07-25 22:37:28 +03:00
|
|
|
|
self.mode = ViewMode::Normal;
|
2019-05-07 01:57:44 +03:00
|
|
|
|
self.set_dirty();
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview)
|
|
|
|
|
&& !self.cmd_buf.is_empty()
|
|
|
|
|
&& *key == shortcuts["open_mailcap"] =>
|
|
|
|
|
{
|
|
|
|
|
let lidx = self.cmd_buf.parse::<usize>().unwrap();
|
|
|
|
|
self.cmd_buf.clear();
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
|
|
|
|
let op = account.operation(envelope.hash());
|
|
|
|
|
|
|
|
|
|
let attachments = match envelope.body(op) {
|
|
|
|
|
Ok(body) => body.attachments(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to open e-mail".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open envelope {}: {}",
|
|
|
|
|
envelope.message_id_display(),
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
drop(envelope);
|
|
|
|
|
drop(account);
|
|
|
|
|
if let Some(u) = attachments.get(lidx) {
|
|
|
|
|
if let Ok(()) = crate::mailcap::MailcapEntry::execute(u, context) {
|
|
|
|
|
self.set_dirty();
|
|
|
|
|
} else {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"no mailcap entry found for {}",
|
|
|
|
|
u.content_type()
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Attachment `{}` not found.",
|
|
|
|
|
lidx
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if *key == shortcuts["open_attachment"]
|
|
|
|
|
&& !self.cmd_buf.is_empty()
|
2019-05-07 01:57:44 +03:00
|
|
|
|
&& (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview) =>
|
2018-07-27 18:01:52 +03:00
|
|
|
|
{
|
2018-07-24 16:55:00 +03:00
|
|
|
|
let lidx = self.cmd_buf.parse::<usize>().unwrap();
|
|
|
|
|
self.cmd_buf.clear();
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
2018-07-24 16:55:00 +03:00
|
|
|
|
|
2018-07-24 20:20:32 +03:00
|
|
|
|
{
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2019-06-18 21:13:58 +03:00
|
|
|
|
let op = account.operation(envelope.hash());
|
2019-09-27 13:18:59 +03:00
|
|
|
|
|
|
|
|
|
let attachments = match envelope.body(op) {
|
|
|
|
|
Ok(body) => body.attachments(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to open e-mail".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open envelope {}: {}",
|
|
|
|
|
envelope.message_id_display(),
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if let Some(u) = attachments.get(lidx) {
|
2018-08-17 13:21:24 +03:00
|
|
|
|
match u.content_type() {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
ContentType::MessageRfc822 => {
|
2019-09-25 22:00:30 +03:00
|
|
|
|
match EnvelopeWrapper::new(u.body().to_vec()) {
|
2018-08-18 23:19:39 +03:00
|
|
|
|
Ok(wrapper) => {
|
2019-09-07 22:07:13 +03:00
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(New(Some(
|
|
|
|
|
Box::new(EnvelopeView::new(
|
|
|
|
|
wrapper,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
self.coordinates.0,
|
|
|
|
|
)),
|
|
|
|
|
)))));
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
2018-08-18 23:19:39 +03:00
|
|
|
|
Err(e) => {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!("{}", e)),
|
|
|
|
|
));
|
2018-08-18 23:19:39 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2019-09-25 23:12:38 +03:00
|
|
|
|
ContentType::Text { .. } | ContentType::PGPSignature => {
|
2018-07-25 22:37:28 +03:00
|
|
|
|
self.mode = ViewMode::Attachment(lidx);
|
|
|
|
|
self.dirty = true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2018-07-25 22:37:28 +03:00
|
|
|
|
ContentType::Multipart { .. } => {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
"Multipart attachments are not supported yet.".to_string(),
|
2018-08-07 15:01:15 +03:00
|
|
|
|
),
|
2019-04-10 23:37:20 +03:00
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-07-31 13:29:55 +03:00
|
|
|
|
ContentType::Other { ref name, .. } => {
|
2018-07-25 22:37:28 +03:00
|
|
|
|
let attachment_type = u.mime_type();
|
2018-07-26 00:07:00 +03:00
|
|
|
|
let binary = query_default_app(&attachment_type);
|
2019-09-27 22:21:35 +03:00
|
|
|
|
let mut name_opt = name.as_ref().and_then(|n| {
|
|
|
|
|
melib::email::parser::phrase(n.as_bytes())
|
|
|
|
|
.to_full_result()
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|n| String::from_utf8(n).ok())
|
|
|
|
|
});
|
|
|
|
|
if name_opt.is_none() {
|
|
|
|
|
name_opt = name.as_ref().map(|n| n.clone());
|
|
|
|
|
}
|
2018-07-26 00:07:00 +03:00
|
|
|
|
if let Ok(binary) = binary {
|
2019-09-27 22:21:35 +03:00
|
|
|
|
let p =
|
|
|
|
|
create_temp_file(&decode(u, None), name_opt, None, true);
|
2018-07-26 00:07:00 +03:00
|
|
|
|
Command::new(&binary)
|
|
|
|
|
.arg(p.path())
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
2018-08-07 15:01:15 +03:00
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
|
panic!("Failed to start {}", binary.display())
|
|
|
|
|
});
|
2018-08-08 20:58:15 +03:00
|
|
|
|
context.temp_files.push(p);
|
2018-07-26 00:07:00 +03:00
|
|
|
|
} else {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
2019-07-31 13:29:55 +03:00
|
|
|
|
StatusEvent::DisplayMessage(if name.is_some() {
|
|
|
|
|
format!(
|
|
|
|
|
"Couldn't find a default application for file {} (type {})",
|
|
|
|
|
name.as_ref().unwrap(), attachment_type
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
format!( "Couldn't find a default application for type {}", attachment_type)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
,
|
|
|
|
|
)));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-07-26 00:07:00 +03:00
|
|
|
|
}
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-07-31 13:29:55 +03:00
|
|
|
|
ContentType::OctetStream { ref name } => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open {}. application/octet-stream isn't supported yet",
|
|
|
|
|
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-25 22:37:28 +03:00
|
|
|
|
}
|
2018-07-24 16:55:00 +03:00
|
|
|
|
} else {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Attachment `{}` not found.",
|
|
|
|
|
lidx
|
2018-07-27 18:01:52 +03:00
|
|
|
|
)),
|
2019-04-10 23:37:20 +03:00
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-07-24 16:55:00 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key) if *key == shortcuts["toggle_expand_headers"] => {
|
2019-06-05 01:27:13 +03:00
|
|
|
|
self.expand_headers = !self.expand_headers;
|
|
|
|
|
self.dirty = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if !self.cmd_buf.is_empty()
|
|
|
|
|
&& self.mode == ViewMode::Url
|
|
|
|
|
&& *key == shortcuts["go_to_url"] =>
|
2018-07-27 18:01:52 +03:00
|
|
|
|
{
|
2018-07-23 15:40:13 +03:00
|
|
|
|
let lidx = self.cmd_buf.parse::<usize>().unwrap();
|
|
|
|
|
self.cmd_buf.clear();
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
2018-07-23 15:40:13 +03:00
|
|
|
|
let url = {
|
2019-05-26 02:34:03 +03:00
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
2018-07-23 15:40:13 +03:00
|
|
|
|
let finder = LinkFinder::new();
|
2019-06-18 21:13:58 +03:00
|
|
|
|
let op = account.operation(envelope.hash());
|
2019-09-27 13:18:59 +03:00
|
|
|
|
let t = match envelope.body(op) {
|
|
|
|
|
Ok(body) => body.text().to_string(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to open e-mail".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open envelope {}: {}",
|
|
|
|
|
envelope.message_id_display(),
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-07-23 15:40:13 +03:00
|
|
|
|
let links: Vec<Link> = finder.links(&t).collect();
|
2018-07-23 16:29:22 +03:00
|
|
|
|
if let Some(u) = links.get(lidx) {
|
|
|
|
|
u.as_str().to_string()
|
|
|
|
|
} else {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!("Link `{}` not found.", lidx)),
|
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-07-23 16:29:22 +03:00
|
|
|
|
}
|
2018-07-23 15:40:13 +03:00
|
|
|
|
};
|
|
|
|
|
|
2019-11-11 22:20:16 +02:00
|
|
|
|
if let Err(e) = Command::new("xdg-open")
|
2018-07-23 15:40:13 +03:00
|
|
|
|
.arg(url)
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
2019-11-11 22:20:16 +02:00
|
|
|
|
{
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to launch xdg-open".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
}
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Url)
|
|
|
|
|
&& *key == shortcuts["toggle_url_mode"] =>
|
|
|
|
|
{
|
2018-07-23 15:40:13 +03:00
|
|
|
|
match self.mode {
|
2018-07-27 18:01:52 +03:00
|
|
|
|
ViewMode::Normal => self.mode = ViewMode::Url,
|
|
|
|
|
ViewMode::Url => self.mode = ViewMode::Normal,
|
|
|
|
|
_ => {}
|
2018-07-23 15:40:13 +03:00
|
|
|
|
}
|
|
|
|
|
self.dirty = true;
|
2019-07-04 15:31:12 +03:00
|
|
|
|
return true;
|
2018-07-27 18:01:52 +03:00
|
|
|
|
}
|
2019-06-18 21:13:58 +03:00
|
|
|
|
UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => {
|
2019-04-01 23:53:06 +03:00
|
|
|
|
self.coordinates.2 = new_hash;
|
|
|
|
|
}
|
2019-11-10 13:33:22 +02:00
|
|
|
|
UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => {
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
let account = &mut context.accounts[self.coordinates.0];
|
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
|
|
|
|
let op = account.operation(envelope.hash());
|
|
|
|
|
|
|
|
|
|
let attachments = match envelope.body(op) {
|
|
|
|
|
Ok(body) => body.attachments(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Failed to open e-mail".to_string()),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to open envelope {}: {}",
|
|
|
|
|
envelope.message_id_display(),
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if let Some(u) = attachments.get(a_i) {
|
|
|
|
|
match u.content_type() {
|
|
|
|
|
ContentType::MessageRfc822
|
|
|
|
|
| ContentType::Text { .. }
|
|
|
|
|
| ContentType::PGPSignature => {
|
|
|
|
|
debug!(path);
|
|
|
|
|
let mut f = match std::fs::File::create(path) {
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to create file at {}", path)),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to create file at {}: {}",
|
|
|
|
|
path,
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
Ok(f) => f,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
f.write_all(&decode(u, None)).unwrap();
|
|
|
|
|
f.flush().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ContentType::Multipart { .. } => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
"Multipart attachments are not supported yet.".to_string(),
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ContentType::OctetStream { name: ref _name }
|
|
|
|
|
| ContentType::Other {
|
|
|
|
|
name: ref _name, ..
|
|
|
|
|
} => {
|
|
|
|
|
let mut f = match std::fs::File::create(path.trim()) {
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to create file at {}", path)),
|
|
|
|
|
e.to_string(),
|
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
|
));
|
|
|
|
|
log(
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to create file at {}: {}",
|
|
|
|
|
path,
|
|
|
|
|
e.to_string()
|
|
|
|
|
),
|
|
|
|
|
ERROR,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
Ok(f) => f,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
f.write_all(&decode(u, None)).unwrap();
|
|
|
|
|
f.flush().unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
None,
|
|
|
|
|
format!("Saved at {}", &path),
|
|
|
|
|
Some(NotificationType::INFO),
|
|
|
|
|
));
|
|
|
|
|
} else {
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Attachment `{}` not found.",
|
|
|
|
|
a_i
|
|
|
|
|
))));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-18 22:13:54 +03:00
|
|
|
|
UIEvent::Action(MailingListAction(ref e)) => {
|
|
|
|
|
let unsafe_context = context as *mut Context;
|
|
|
|
|
let account = &context.accounts[self.coordinates.0];
|
|
|
|
|
if !account.contains_key(self.coordinates.2) {
|
|
|
|
|
/* The envelope has been renamed or removed, so wait for the appropriate event to
|
|
|
|
|
* arrive */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-11-02 12:14:31 +02:00
|
|
|
|
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
|
|
|
|
if let Some(actions) = list_management::detect(&envelope) {
|
2019-06-18 22:13:54 +03:00
|
|
|
|
match e {
|
|
|
|
|
MailingListAction::ListPost if actions.post.is_some() => {
|
|
|
|
|
/* open composer */
|
|
|
|
|
let mut draft = Draft::default();
|
|
|
|
|
draft.set_header("To", actions.post.unwrap().to_string());
|
|
|
|
|
context.replies.push_back(UIEvent::Action(Tab(NewDraft(
|
|
|
|
|
self.coordinates.0,
|
|
|
|
|
Some(draft),
|
|
|
|
|
))));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => {
|
|
|
|
|
/* autosend or open unsubscribe option*/
|
|
|
|
|
let unsubscribe = actions.unsubscribe.unwrap();
|
|
|
|
|
for option in unsubscribe {
|
|
|
|
|
/* TODO: Ask for confirmation before proceding with an action */
|
|
|
|
|
match option {
|
|
|
|
|
list_management::UnsubscribeOption::Email(email) => {
|
|
|
|
|
if let Ok(mailto) = Mailto::try_from(email) {
|
2019-07-22 15:14:39 +03:00
|
|
|
|
let mut draft: Draft = mailto.into();
|
|
|
|
|
draft.headers_mut().insert(
|
|
|
|
|
"From".into(),
|
|
|
|
|
crate::components::mail::get_display_name(
|
|
|
|
|
context,
|
|
|
|
|
self.coordinates.0,
|
|
|
|
|
),
|
|
|
|
|
);
|
2019-06-18 22:13:54 +03:00
|
|
|
|
if super::compose::send_draft(
|
2019-09-28 10:46:49 +03:00
|
|
|
|
ToggleFlag::False,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
/* FIXME: refactor to avoid unsafe.
|
|
|
|
|
*
|
|
|
|
|
* actions contains byte slices from the envelope's
|
|
|
|
|
* headers send_draft only needs a mut ref for
|
|
|
|
|
* context to push back replies and save the sent
|
|
|
|
|
* message */
|
|
|
|
|
unsafe { &mut *(unsafe_context) },
|
|
|
|
|
self.coordinates.0,
|
|
|
|
|
draft,
|
|
|
|
|
) {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some("Sent unsubscribe email.".into()),
|
|
|
|
|
"Sent unsubscribe email".to_string(),
|
2019-09-15 23:35:30 +03:00
|
|
|
|
None,
|
2019-06-18 22:13:54 +03:00
|
|
|
|
));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
list_management::UnsubscribeOption::Url(url) => {
|
|
|
|
|
if let Err(e) = Command::new("xdg-open")
|
|
|
|
|
.arg(String::from_utf8_lossy(url).into_owned())
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
|
|
|
|
{
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Couldn't launch xdg-open: {}",
|
|
|
|
|
e
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MailingListAction::ListArchive if actions.archive.is_some() => {
|
|
|
|
|
/* open archive url with xdg-open */
|
|
|
|
|
if let Err(e) = Command::new("xdg-open")
|
|
|
|
|
.arg(actions.archive.unwrap())
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
|
|
|
|
{
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Couldn't launch xdg-open: {}",
|
|
|
|
|
e
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
_ => { /* error print message to user */ }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-15 14:10:06 +03:00
|
|
|
|
UIEvent::Action(Listing(OpenInNewTab)) => {
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::Action(Tab(New(Some(Box::new(self.clone()))))));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-07-04 15:31:12 +03:00
|
|
|
|
_ => {}
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
2019-07-04 15:31:12 +03:00
|
|
|
|
false
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
|
|
|
|
fn is_dirty(&self) -> bool {
|
2019-03-14 12:19:25 +02:00
|
|
|
|
self.dirty
|
2018-07-27 18:01:52 +03:00
|
|
|
|
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|
|
|
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
2019-02-26 10:55:22 +02:00
|
|
|
|
|| if let ViewMode::ContactSelector(ref s) = self.mode {
|
|
|
|
|
s.is_dirty()
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|
2018-08-11 18:00:21 +03:00
|
|
|
|
fn set_dirty(&mut self) {
|
|
|
|
|
self.dirty = true;
|
2018-09-04 01:49:29 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Normal => {
|
|
|
|
|
if let Some(p) = self.pager.as_mut() {
|
|
|
|
|
p.set_dirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ViewMode::Subview => {
|
|
|
|
|
if let Some(s) = self.subview.as_mut() {
|
|
|
|
|
s.set_dirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2018-08-11 18:00:21 +03:00
|
|
|
|
}
|
2019-05-10 22:00:56 +03:00
|
|
|
|
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
|
2019-05-07 01:57:44 +03:00
|
|
|
|
let mut map = if let Some(ref sbv) = self.subview {
|
|
|
|
|
sbv.get_shortcuts(context)
|
|
|
|
|
} else if let Some(ref pgr) = self.pager {
|
|
|
|
|
pgr.get_shortcuts(context)
|
|
|
|
|
} else {
|
2019-05-10 22:00:56 +03:00
|
|
|
|
Default::default()
|
2019-05-07 01:57:44 +03:00
|
|
|
|
};
|
|
|
|
|
|
2019-05-10 22:00:56 +03:00
|
|
|
|
let mut our_map = FnvHashMap::with_capacity_and_hasher(4, Default::default());
|
|
|
|
|
our_map.insert("add_addresses_to_contacts", Key::Char('c'));
|
|
|
|
|
our_map.insert("view_raw_source", Key::Alt('r'));
|
2019-11-15 20:28:03 +02:00
|
|
|
|
if self.mode.is_attachment()
|
|
|
|
|
|| self.mode == ViewMode::Subview
|
|
|
|
|
|| self.mode == ViewMode::Raw
|
|
|
|
|
|| self.mode == ViewMode::Url
|
2019-05-07 01:57:44 +03:00
|
|
|
|
{
|
2019-05-10 22:00:56 +03:00
|
|
|
|
our_map.insert("return_to_normal_view", Key::Char('r'));
|
2019-05-07 01:57:44 +03:00
|
|
|
|
}
|
2019-05-10 22:00:56 +03:00
|
|
|
|
our_map.insert("open_attachment", Key::Char('a'));
|
2019-11-11 22:20:16 +02:00
|
|
|
|
our_map.insert("open_mailcap", Key::Char('m'));
|
2019-05-07 01:57:44 +03:00
|
|
|
|
if self.mode == ViewMode::Url {
|
2019-05-10 22:00:56 +03:00
|
|
|
|
our_map.insert("go_to_url", Key::Char('g'));
|
2019-05-07 01:57:44 +03:00
|
|
|
|
}
|
|
|
|
|
if self.mode == ViewMode::Normal || self.mode == ViewMode::Url {
|
2019-05-10 22:00:56 +03:00
|
|
|
|
our_map.insert("toggle_url_mode", Key::Char('u'));
|
2019-05-07 01:57:44 +03:00
|
|
|
|
}
|
2019-11-11 22:20:16 +02:00
|
|
|
|
our_map.insert("toggle_expand_headers", Key::Char('h'));
|
2019-05-10 22:00:56 +03:00
|
|
|
|
map.insert(MailView::DESCRIPTION.to_string(), our_map);
|
2019-05-07 01:57:44 +03:00
|
|
|
|
|
|
|
|
|
map
|
|
|
|
|
}
|
2019-04-10 22:01:02 +03:00
|
|
|
|
|
|
|
|
|
fn id(&self) -> ComponentId {
|
|
|
|
|
self.id
|
|
|
|
|
}
|
2019-09-15 14:10:06 +03:00
|
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
|
fn set_id(&mut self, id: ComponentId) {
|
|
|
|
|
self.id = id;
|
|
|
|
|
}
|
2019-09-15 14:10:06 +03:00
|
|
|
|
|
|
|
|
|
fn kill(&mut self, id: ComponentId, context: &mut Context) {
|
|
|
|
|
debug_assert!(self.id == id);
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
|
|
|
|
}
|
2018-07-22 18:02:44 +03:00
|
|
|
|
}
|