2018-08-18 17:50:31 +03:00
|
|
|
|
/*
|
2020-02-04 15:52:12 +02:00
|
|
|
|
* meli
|
2018-08-18 17:50:31 +03:00
|
|
|
|
*
|
|
|
|
|
* Copyright 2017-2018 Manos Pitsidianakis
|
|
|
|
|
*
|
|
|
|
|
* This file is part of meli.
|
|
|
|
|
*
|
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
use linkify::{Link, LinkFinder};
|
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
|
|
2020-02-02 20:40:48 +02:00
|
|
|
|
use xdg_utils::query_default_app;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
|
|
|
enum ViewMode {
|
|
|
|
|
Normal,
|
|
|
|
|
Url,
|
|
|
|
|
Attachment(usize),
|
|
|
|
|
Raw,
|
|
|
|
|
Subview,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ViewMode {
|
|
|
|
|
fn is_attachment(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
ViewMode::Attachment(_) => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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)]
|
2018-08-18 17:50:31 +03:00
|
|
|
|
pub struct EnvelopeView {
|
|
|
|
|
pager: Option<Pager>,
|
2019-09-09 11:54:47 +03:00
|
|
|
|
subview: Option<Box<dyn Component>>,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
dirty: bool,
|
|
|
|
|
mode: ViewMode,
|
2020-09-09 14:24:30 +03:00
|
|
|
|
mail: Mail,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2020-08-17 15:31:30 +03:00
|
|
|
|
account_hash: AccountHash,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
cmd_buf: String,
|
2019-04-10 22:01:02 +03:00
|
|
|
|
id: ComponentId,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for EnvelopeView {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
write!(f, "view mail")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EnvelopeView {
|
|
|
|
|
pub fn new(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
mail: Mail,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
pager: Option<Pager>,
|
2019-09-09 11:54:47 +03:00
|
|
|
|
subview: Option<Box<dyn Component>>,
|
2020-08-17 15:31:30 +03:00
|
|
|
|
account_hash: AccountHash,
|
2018-08-23 15:36:52 +03:00
|
|
|
|
) -> Self {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
EnvelopeView {
|
|
|
|
|
pager,
|
|
|
|
|
subview,
|
|
|
|
|
dirty: true,
|
|
|
|
|
mode: ViewMode::Normal,
|
2020-09-09 14:24:30 +03:00
|
|
|
|
mail,
|
2020-08-17 15:31:30 +03:00
|
|
|
|
account_hash,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
cmd_buf: String::with_capacity(4),
|
2019-04-11 00:04:17 +03:00
|
|
|
|
id: ComponentId::new_v4(),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the string to be displayed in the Viewer
|
2020-09-27 20:55:58 +03:00
|
|
|
|
fn attachment_to_text(&self, body: &Attachment, context: &mut Context) -> String {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
let finder = LinkFinder::new();
|
|
|
|
|
let body_text = String::from_utf8_lossy(&decode_rec(
|
2018-08-23 15:36:52 +03:00
|
|
|
|
&body,
|
|
|
|
|
Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
|
|
|
|
|
if a.content_type().is_text_html() {
|
2019-10-03 19:51:34 +03:00
|
|
|
|
let settings = &context.settings;
|
|
|
|
|
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
|
2020-05-28 16:27:02 +03:00
|
|
|
|
let command_obj = Command::new("sh")
|
|
|
|
|
.args(&["-c", filter_invocation])
|
2019-04-06 00:30:06 +03:00
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn();
|
2020-09-13 15:23:14 +03:00
|
|
|
|
match command_obj {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!(
|
|
|
|
|
"Failed to start html filter process: {}",
|
|
|
|
|
filter_invocation,
|
|
|
|
|
)),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Ok(mut html_filter) => {
|
|
|
|
|
html_filter
|
|
|
|
|
.stdin
|
|
|
|
|
.as_mut()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&v)
|
|
|
|
|
.expect("Failed to write to stdin");
|
|
|
|
|
*v = format!(
|
2019-04-06 01:08:33 +03:00
|
|
|
|
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
|
|
|
|
|
filter_invocation
|
|
|
|
|
)
|
2020-09-13 15:23:14 +03:00
|
|
|
|
.into_bytes();
|
|
|
|
|
v.extend(html_filter.wait_with_output().unwrap().stdout);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-06 00:30:06 +03:00
|
|
|
|
}
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
|
|
|
|
})),
|
2019-03-14 12:19:25 +02:00
|
|
|
|
))
|
|
|
|
|
.into_owned();
|
2018-08-18 17:50:31 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Normal | ViewMode::Subview => {
|
2020-07-05 15:28:55 +03:00
|
|
|
|
let mut t = body_text;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
if body.count_attachments() > 1 {
|
|
|
|
|
t = body
|
|
|
|
|
.attachments()
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.fold(t, |mut s, (idx, a)| {
|
|
|
|
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
|
|
|
|
s
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
t
|
|
|
|
|
}
|
2019-09-25 22:00:30 +03:00
|
|
|
|
ViewMode::Raw => String::from_utf8_lossy(body.body()).into_owned(),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
ViewMode::Url => {
|
2020-07-05 15:28:55 +03:00
|
|
|
|
let mut t = body_text;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
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 {
|
|
|
|
|
panic!("BUG: Message body with more than 100 urls, fix this");
|
|
|
|
|
};
|
|
|
|
|
t.insert_str(l.start() + offset, &format!("[{}]", lidx));
|
|
|
|
|
}
|
|
|
|
|
if body.count_attachments() > 1 {
|
|
|
|
|
t = body
|
|
|
|
|
.attachments()
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.fold(t, |mut s, (idx, a)| {
|
|
|
|
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
|
|
|
|
s
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
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-18 15:41:38 +02:00
|
|
|
|
/*
|
|
|
|
|
* TODO: add recolor changes so that this function returns a vector of required highlights
|
|
|
|
|
* to pass to write_string...
|
2018-08-23 15:36:52 +03:00
|
|
|
|
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
|
2018-08-18 17:50:31 +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-18 17:50:31 +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-03-18 15:41:38 +02:00
|
|
|
|
*/
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Component for EnvelopeView {
|
|
|
|
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
|
|
|
|
let upper_left = upper_left!(area);
|
|
|
|
|
let bottom_right = bottom_right!(area);
|
|
|
|
|
|
2018-08-23 15:36:52 +03:00
|
|
|
|
let y: usize = {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
if self.mode == ViewMode::Raw {
|
2020-02-08 13:40:47 +02:00
|
|
|
|
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
2018-08-18 17:50:31 +03:00
|
|
|
|
context.dirty_areas.push_back(area);
|
2020-06-10 18:07:37 +03:00
|
|
|
|
get_y(upper_left).saturating_sub(1)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
} else {
|
|
|
|
|
let (x, y) = write_string_to_grid(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
&format!("Date: {}", self.mail.date_as_str()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
grid,
|
|
|
|
|
Color::Byte(33),
|
|
|
|
|
Color::Default,
|
2020-04-05 12:04:25 +03:00
|
|
|
|
Attr::DEFAULT,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
area,
|
2019-11-18 13:06:30 +02:00
|
|
|
|
Some(get_x(upper_left)),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
|
|
|
|
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(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
&format!("From: {}", self.mail.field_from_to_string()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
grid,
|
|
|
|
|
Color::Byte(33),
|
|
|
|
|
Color::Default,
|
2020-04-05 12:04:25 +03:00
|
|
|
|
Attr::DEFAULT,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
2019-11-18 13:06:30 +02:00
|
|
|
|
Some(get_x(upper_left)),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
|
|
|
|
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(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
&format!("To: {}", self.mail.field_to_to_string()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
grid,
|
|
|
|
|
Color::Byte(33),
|
|
|
|
|
Color::Default,
|
2020-04-05 12:04:25 +03:00
|
|
|
|
Attr::DEFAULT,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
2019-11-18 13:06:30 +02:00
|
|
|
|
Some(get_x(upper_left)),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
|
|
|
|
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(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
&format!("Subject: {}", self.mail.subject()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
grid,
|
|
|
|
|
Color::Byte(33),
|
|
|
|
|
Color::Default,
|
2020-04-05 12:04:25 +03:00
|
|
|
|
Attr::DEFAULT,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
2019-11-18 13:06:30 +02:00
|
|
|
|
Some(get_x(upper_left)),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
|
|
|
|
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(
|
2020-09-09 14:24:30 +03:00
|
|
|
|
&format!("Message-ID: <{}>", self.mail.message_id_raw()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
grid,
|
|
|
|
|
Color::Byte(33),
|
|
|
|
|
Color::Default,
|
2020-04-05 12:04:25 +03:00
|
|
|
|
Attr::DEFAULT,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
(set_y(upper_left, y + 1), bottom_right),
|
2019-11-18 13:06:30 +02:00
|
|
|
|
Some(get_x(upper_left)),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
|
|
|
|
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);
|
|
|
|
|
}
|
2020-02-08 13:40:47 +02:00
|
|
|
|
clear_area(
|
|
|
|
|
grid,
|
|
|
|
|
(set_y(upper_left, y + 1), set_y(bottom_right, y + 2)),
|
|
|
|
|
crate::conf::value(context, "theme_default"),
|
|
|
|
|
);
|
2018-08-18 17:50:31 +03:00
|
|
|
|
context
|
|
|
|
|
.dirty_areas
|
|
|
|
|
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
|
|
|
|
y + 1
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if self.dirty {
|
2020-09-09 14:24:30 +03:00
|
|
|
|
let body = self.mail.body();
|
2018-08-18 17:50:31 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
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-08-18 17:50:31 +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-08-18 17:50:31 +03:00
|
|
|
|
self.mode = ViewMode::Subview;
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
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-23 15:36:52 +03:00
|
|
|
|
let text = self.attachment_to_text(&body);
|
2018-08-18 17:50:31 +03:00
|
|
|
|
// URL indexes must be colored (ugh..)
|
|
|
|
|
EnvelopeView::plain_text_to_buf(&text, self.mode == ViewMode::Url)
|
2019-03-18 15:41:38 +02:00
|
|
|
|
*/
|
2018-08-18 17:50:31 +03:00
|
|
|
|
};
|
|
|
|
|
let cursor_pos = if self.mode.is_attachment() {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
2019-06-18 21:13:58 +03:00
|
|
|
|
self.pager.as_ref().map(Pager::cursor_pos)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
};
|
2020-01-23 19:52:54 +02:00
|
|
|
|
let colors = crate::conf::value(context, "mail.view.body");
|
2020-01-22 00:05:26 +02:00
|
|
|
|
self.pager = Some(Pager::from_string(
|
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
|
|
|
|
cursor_pos,
|
|
|
|
|
None,
|
|
|
|
|
colors,
|
|
|
|
|
));
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
self.dirty = false;
|
|
|
|
|
}
|
|
|
|
|
if let Some(s) = self.subview.as_mut() {
|
|
|
|
|
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
|
|
|
|
} else if let Some(p) = self.pager.as_mut() {
|
|
|
|
|
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-26 17:50:47 +02:00
|
|
|
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
2018-08-23 14:39:54 +03:00
|
|
|
|
if let Some(ref mut sub) = self.subview {
|
|
|
|
|
if sub.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(ref mut p) = self.pager {
|
|
|
|
|
if p.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
match *event {
|
2020-02-09 23:32:14 +02:00
|
|
|
|
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.cmd_buf.clear();
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.cmd_buf.push(c);
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char('r'))
|
2018-08-18 17:50:31 +03:00
|
|
|
|
if self.mode == ViewMode::Normal || self.mode == ViewMode::Raw =>
|
|
|
|
|
{
|
|
|
|
|
self.mode = if self.mode == ViewMode::Raw {
|
|
|
|
|
ViewMode::Normal
|
|
|
|
|
} else {
|
|
|
|
|
ViewMode::Raw
|
|
|
|
|
};
|
|
|
|
|
self.dirty = true;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char('r'))
|
2018-08-23 15:36:52 +03:00
|
|
|
|
if self.mode.is_attachment() || self.mode == ViewMode::Subview =>
|
|
|
|
|
{
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.mode = ViewMode::Normal;
|
|
|
|
|
self.subview.take();
|
|
|
|
|
self.dirty = true;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char('a'))
|
2018-08-18 17:50:31 +03:00
|
|
|
|
if !self.cmd_buf.is_empty() && self.mode == ViewMode::Normal =>
|
|
|
|
|
{
|
|
|
|
|
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-08-18 17:50:31 +03:00
|
|
|
|
|
|
|
|
|
{
|
2020-09-09 14:24:30 +03:00
|
|
|
|
if let Some(u) = self.mail.body().attachments().get(lidx) {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
match u.content_type() {
|
|
|
|
|
ContentType::MessageRfc822 => {
|
|
|
|
|
self.mode = ViewMode::Subview;
|
2020-01-23 19:52:54 +02:00
|
|
|
|
let colors = crate::conf::value(context, "mail.view.body");
|
2019-03-14 12:00:41 +02:00
|
|
|
|
self.subview = Some(Box::new(Pager::from_string(
|
2019-03-14 12:19:25 +02:00
|
|
|
|
String::from_utf8_lossy(&decode_rec(u, None)).to_string(),
|
2019-03-18 15:41:38 +02:00
|
|
|
|
Some(context),
|
2019-03-14 12:19:25 +02:00
|
|
|
|
None,
|
|
|
|
|
None,
|
2020-01-22 00:05:26 +02:00
|
|
|
|
colors,
|
2018-08-23 15:36:52 +03:00
|
|
|
|
)));
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2020-10-16 12:46:10 +03:00
|
|
|
|
ContentType::Text { .. }
|
|
|
|
|
| ContentType::PGPSignature
|
|
|
|
|
| ContentType::CMSSignature => {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.mode = ViewMode::Attachment(lidx);
|
|
|
|
|
self.dirty = true;
|
|
|
|
|
}
|
|
|
|
|
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-18 17:50:31 +03:00
|
|
|
|
),
|
2019-04-10 23:37:20 +03:00
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-07-31 13:29:55 +03:00
|
|
|
|
ContentType::Other { ref name, .. } => {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
let attachment_type = u.mime_type();
|
|
|
|
|
let binary = query_default_app(&attachment_type);
|
|
|
|
|
if let Ok(binary) = binary {
|
2019-07-31 13:29:55 +03:00
|
|
|
|
let p = create_temp_file(
|
|
|
|
|
&decode(u, None),
|
2019-12-27 15:20:02 +02:00
|
|
|
|
name.as_ref().map(String::as_str),
|
2019-07-31 13:29:55 +03:00
|
|
|
|
None,
|
2019-09-23 09:30:23 +03:00
|
|
|
|
true,
|
2019-07-31 13:29:55 +03:00
|
|
|
|
);
|
2020-03-01 17:56:58 +02:00
|
|
|
|
match Command::new(&binary)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
.arg(p.path())
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
2020-03-01 17:56:58 +02:00
|
|
|
|
{
|
|
|
|
|
Ok(child) => {
|
|
|
|
|
context.children.push(child);
|
|
|
|
|
context.temp_files.push(p);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Failed to start {}: {}",
|
|
|
|
|
binary.display(),
|
|
|
|
|
err
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
} else {
|
2019-04-10 23:37:20 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Couldn't find a default application for type {}",
|
|
|
|
|
attachment_type
|
|
|
|
|
)),
|
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-31 13:29:55 +03:00
|
|
|
|
ContentType::OctetStream { .. } => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
"application/octet-stream isn't supported yet".to_string(),
|
|
|
|
|
),
|
|
|
|
|
));
|
2019-05-12 15:10:08 +03:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-08-18 17:50:31 +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-08-18 17:50:31 +03:00
|
|
|
|
)),
|
2019-04-10 23:37:20 +03:00
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char('g'))
|
2018-08-18 17:50:31 +03:00
|
|
|
|
if !self.cmd_buf.is_empty() && self.mode == ViewMode::Url =>
|
|
|
|
|
{
|
|
|
|
|
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-08-18 17:50:31 +03:00
|
|
|
|
let url = {
|
|
|
|
|
let finder = LinkFinder::new();
|
2020-09-09 14:24:30 +03:00
|
|
|
|
let t = self.mail.body().text();
|
2018-08-18 17:50:31 +03:00
|
|
|
|
let links: Vec<Link> = finder.links(&t).collect();
|
|
|
|
|
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-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-01 17:56:58 +02:00
|
|
|
|
match Command::new("xdg-open")
|
2018-08-18 17:50:31 +03:00
|
|
|
|
.arg(url)
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
2020-03-01 17:56:58 +02:00
|
|
|
|
{
|
|
|
|
|
Ok(child) => context.children.push(child),
|
|
|
|
|
Err(_err) => context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage("Failed to start xdg_open".into()),
|
|
|
|
|
)),
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
|
UIEvent::Input(Key::Char('u')) => {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Normal => self.mode = ViewMode::Url,
|
|
|
|
|
ViewMode::Url => self.mode = ViewMode::Normal,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
self.dirty = true;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
false
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
fn is_dirty(&self) -> bool {
|
|
|
|
|
self.dirty
|
|
|
|
|
|| 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-12-14 18:50:05 +02:00
|
|
|
|
fn set_dirty(&mut self, value: bool) {
|
|
|
|
|
self.dirty = value;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2019-04-10 22:01:02 +03:00
|
|
|
|
|
|
|
|
|
fn id(&self) -> ComponentId {
|
|
|
|
|
self.id
|
|
|
|
|
}
|
2019-09-07 22:07:13 +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))));
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
|
fn set_id(&mut self, id: ComponentId) {
|
|
|
|
|
self.id = id;
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|