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 std::process::{Command, Stdio};
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
use linkify::LinkFinder;
|
2023-09-02 22:38:21 +03:00
|
|
|
|
use melib::utils::xdg::query_default_app;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2023-04-30 19:39:41 +03:00
|
|
|
|
use super::*;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
use crate::ThreadEvent;
|
2023-04-30 19:39:41 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
/// Envelope view, with sticky headers, a pager for the body, and
|
|
|
|
|
/// subviews for more menus.
|
|
|
|
|
///
|
|
|
|
|
/// Doesn't have a concept of accounts, mailboxes or mail backends.
|
2023-06-16 20:20:12 +03:00
|
|
|
|
/// Therefore all settings it needs need to be provided through the
|
|
|
|
|
/// `view_settings` field of type [`ViewSettings`].
|
2023-06-14 12:24:20 +03:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct EnvelopeView {
|
|
|
|
|
pub pager: Pager,
|
|
|
|
|
pub subview: Option<Box<dyn Component>>,
|
|
|
|
|
pub dirty: bool,
|
|
|
|
|
pub initialised: bool,
|
|
|
|
|
pub force_draw_headers: bool,
|
|
|
|
|
pub mode: ViewMode,
|
|
|
|
|
pub mail: Mail,
|
|
|
|
|
pub body: Box<Attachment>,
|
|
|
|
|
pub display: Vec<AttachmentDisplay>,
|
|
|
|
|
pub body_text: String,
|
|
|
|
|
pub links: Vec<Link>,
|
|
|
|
|
pub attachment_tree: String,
|
|
|
|
|
pub attachment_paths: Vec<Vec<usize>>,
|
|
|
|
|
pub headers_no: usize,
|
|
|
|
|
pub headers_cursor: usize,
|
|
|
|
|
pub force_charset: ForceCharset,
|
|
|
|
|
pub view_settings: ViewSettings,
|
|
|
|
|
pub cmd_buf: String,
|
|
|
|
|
pub active_jobs: HashSet<JobId>,
|
|
|
|
|
pub main_loop_handler: MainLoopHandler,
|
|
|
|
|
pub id: ComponentId,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
impl Clone for EnvelopeView {
|
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
|
Self::new(
|
|
|
|
|
self.mail.clone(),
|
|
|
|
|
Some(self.pager.clone()),
|
|
|
|
|
None,
|
|
|
|
|
Some(self.view_settings.clone()),
|
|
|
|
|
self.main_loop_handler.clone(),
|
|
|
|
|
)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 13:01:32 +03:00
|
|
|
|
impl std::fmt::Display for EnvelopeView {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
2018-08-18 17:50:31 +03:00
|
|
|
|
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>>,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
view_settings: Option<ViewSettings>,
|
|
|
|
|
main_loop_handler: MainLoopHandler,
|
2018-08-23 15:36:52 +03:00
|
|
|
|
) -> Self {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let view_settings = view_settings.unwrap_or_default();
|
|
|
|
|
let body = Box::new(AttachmentBuilder::new(&mail.bytes).build());
|
|
|
|
|
let mut ret = EnvelopeView {
|
|
|
|
|
pager: pager.unwrap_or_default(),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
subview,
|
|
|
|
|
dirty: true,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
initialised: false,
|
|
|
|
|
force_draw_headers: false,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
mode: ViewMode::Normal,
|
2023-04-10 11:42:50 +03:00
|
|
|
|
force_charset: ForceCharset::None,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
attachment_tree: String::new(),
|
|
|
|
|
attachment_paths: vec![],
|
|
|
|
|
body,
|
|
|
|
|
display: vec![],
|
|
|
|
|
links: vec![],
|
|
|
|
|
body_text: String::new(),
|
|
|
|
|
view_settings,
|
|
|
|
|
headers_no: 5,
|
|
|
|
|
headers_cursor: 0,
|
2020-09-09 14:24:30 +03:00
|
|
|
|
mail,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs: HashSet::default(),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
cmd_buf: String::with_capacity(4),
|
2023-06-13 17:45:48 +03:00
|
|
|
|
id: ComponentId::default(),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ret.parse_attachments();
|
|
|
|
|
|
|
|
|
|
ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn attachment_to_display_helper(
|
|
|
|
|
a: &Attachment,
|
|
|
|
|
main_loop_handler: &MainLoopHandler,
|
|
|
|
|
active_jobs: &mut HashSet<JobId>,
|
|
|
|
|
acc: &mut Vec<AttachmentDisplay>,
|
|
|
|
|
view_settings: &ViewSettings,
|
|
|
|
|
force_charset: Option<Charset>,
|
|
|
|
|
) {
|
|
|
|
|
if a.content_disposition.kind.is_attachment() || a.content_type == "message/rfc822" {
|
|
|
|
|
acc.push(AttachmentDisplay::Attachment {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
});
|
|
|
|
|
} else if a.content_type().is_text_html() {
|
|
|
|
|
let bytes = a.decode(force_charset.into());
|
|
|
|
|
let filter_invocation = view_settings
|
|
|
|
|
.html_filter
|
|
|
|
|
.as_deref()
|
|
|
|
|
.unwrap_or("w3m -I utf-8 -T text/html");
|
|
|
|
|
let command_obj = Command::new("sh")
|
|
|
|
|
.args(["-c", filter_invocation])
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()
|
|
|
|
|
.and_then(|mut cmd| {
|
|
|
|
|
cmd.stdin.as_mut().unwrap().write_all(&bytes)?;
|
|
|
|
|
Ok(String::from_utf8_lossy(&cmd.wait_with_output()?.stdout).to_string())
|
|
|
|
|
});
|
|
|
|
|
match command_obj {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
|
|
|
|
Some(format!(
|
|
|
|
|
"Failed to start html filter process: {}",
|
|
|
|
|
filter_invocation,
|
|
|
|
|
)),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
)));
|
2023-07-13 16:47:11 +03:00
|
|
|
|
// [ref:FIXME]: add `v` configurable shortcut
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let comment = Some(format!(
|
|
|
|
|
"Failed to start html filter process: `{}`. Press `v` to open in web \
|
|
|
|
|
browser. \n\n",
|
|
|
|
|
filter_invocation
|
|
|
|
|
));
|
|
|
|
|
let text = String::from_utf8_lossy(&bytes).to_string();
|
|
|
|
|
acc.push(AttachmentDisplay::InlineText {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
comment,
|
|
|
|
|
text,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Ok(text) => {
|
2023-07-13 16:47:11 +03:00
|
|
|
|
// [ref:FIXME]: add `v` configurable shortcut
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let comment = Some(format!(
|
|
|
|
|
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
|
|
|
|
|
filter_invocation
|
|
|
|
|
));
|
|
|
|
|
acc.push(AttachmentDisplay::InlineText {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
comment,
|
|
|
|
|
text,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if a.is_text() {
|
|
|
|
|
let bytes = a.decode(force_charset.into());
|
|
|
|
|
acc.push(AttachmentDisplay::InlineText {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
comment: None,
|
|
|
|
|
text: String::from_utf8_lossy(&bytes).to_string(),
|
|
|
|
|
});
|
|
|
|
|
} else if let ContentType::Multipart {
|
|
|
|
|
ref kind,
|
|
|
|
|
ref parts,
|
|
|
|
|
..
|
|
|
|
|
} = a.content_type
|
|
|
|
|
{
|
|
|
|
|
match kind {
|
|
|
|
|
MultipartType::Alternative => {
|
|
|
|
|
if parts.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let mut display = vec![];
|
|
|
|
|
let mut chosen_attachment_idx = 0;
|
|
|
|
|
if let Some(text_attachment_pos) =
|
|
|
|
|
parts.iter().position(|a| a.content_type == "text/plain")
|
|
|
|
|
{
|
|
|
|
|
let bytes = &parts[text_attachment_pos].decode(force_charset.into());
|
|
|
|
|
if bytes.trim().is_empty()
|
|
|
|
|
&& view_settings.auto_choose_multipart_alternative
|
|
|
|
|
{
|
|
|
|
|
if let Some(text_attachment_pos) =
|
|
|
|
|
parts.iter().position(|a| a.content_type == "text/html")
|
|
|
|
|
{
|
|
|
|
|
/* Select html alternative since text/plain is empty */
|
|
|
|
|
chosen_attachment_idx = text_attachment_pos;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* Select text/plain alternative */
|
|
|
|
|
chosen_attachment_idx = text_attachment_pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for a in parts {
|
|
|
|
|
EnvelopeView::attachment_to_display_helper(
|
|
|
|
|
a,
|
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs,
|
|
|
|
|
&mut display,
|
|
|
|
|
view_settings,
|
|
|
|
|
force_charset,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
acc.push(AttachmentDisplay::Alternative {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
shown_display: chosen_attachment_idx,
|
|
|
|
|
display,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
MultipartType::Signed => {
|
|
|
|
|
#[cfg(not(feature = "gpgme"))]
|
|
|
|
|
{
|
|
|
|
|
acc.push(AttachmentDisplay::SignedUnverified {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
display: {
|
|
|
|
|
let mut v = vec![];
|
|
|
|
|
EnvelopeView::attachment_to_display_helper(
|
|
|
|
|
&parts[0],
|
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs,
|
|
|
|
|
&mut v,
|
|
|
|
|
view_settings,
|
|
|
|
|
force_charset,
|
|
|
|
|
);
|
|
|
|
|
v
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
|
{
|
|
|
|
|
if view_settings.auto_verify_signatures {
|
2023-08-11 13:01:32 +03:00
|
|
|
|
let verify_fut = crate::mail::pgp::verify(a.clone());
|
2023-07-14 00:23:24 +03:00
|
|
|
|
let handle = main_loop_handler
|
|
|
|
|
.job_executor
|
|
|
|
|
.spawn_specialized("gpg::verify_sig".into(), verify_fut);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
active_jobs.insert(handle.job_id);
|
|
|
|
|
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::NewJob(handle.job_id),
|
|
|
|
|
)));
|
|
|
|
|
acc.push(AttachmentDisplay::SignedPending {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
job_id: handle.job_id,
|
|
|
|
|
display: {
|
|
|
|
|
let mut v = vec![];
|
|
|
|
|
EnvelopeView::attachment_to_display_helper(
|
|
|
|
|
&parts[0],
|
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs,
|
|
|
|
|
&mut v,
|
|
|
|
|
view_settings,
|
|
|
|
|
force_charset,
|
|
|
|
|
);
|
|
|
|
|
v
|
|
|
|
|
},
|
|
|
|
|
handle,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
acc.push(AttachmentDisplay::SignedUnverified {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
display: {
|
|
|
|
|
let mut v = vec![];
|
|
|
|
|
EnvelopeView::attachment_to_display_helper(
|
|
|
|
|
&parts[0],
|
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs,
|
|
|
|
|
&mut v,
|
|
|
|
|
view_settings,
|
|
|
|
|
force_charset,
|
|
|
|
|
);
|
|
|
|
|
v
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MultipartType::Encrypted => {
|
|
|
|
|
for a in parts {
|
|
|
|
|
if a.content_type == "application/octet-stream" {
|
|
|
|
|
#[cfg(not(feature = "gpgme"))]
|
|
|
|
|
{
|
|
|
|
|
acc.push(AttachmentDisplay::EncryptedFailed {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
error: Error::new(
|
|
|
|
|
"Cannot decrypt: meli must be compiled with libgpgme \
|
|
|
|
|
support.",
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
#[cfg(feature = "gpgme")]
|
|
|
|
|
{
|
|
|
|
|
if view_settings.auto_decrypt {
|
2023-08-11 13:01:32 +03:00
|
|
|
|
let decrypt_fut = crate::mail::pgp::decrypt(a.raw().to_vec());
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let handle = main_loop_handler
|
|
|
|
|
.job_executor
|
2023-07-14 00:23:24 +03:00
|
|
|
|
.spawn_specialized("gpg::decrypt".into(), decrypt_fut);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
active_jobs.insert(handle.job_id);
|
|
|
|
|
main_loop_handler.send(ThreadEvent::UIEvent(
|
|
|
|
|
UIEvent::StatusEvent(StatusEvent::NewJob(handle.job_id)),
|
|
|
|
|
));
|
|
|
|
|
acc.push(AttachmentDisplay::EncryptedPending {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
handle,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
acc.push(AttachmentDisplay::EncryptedFailed {
|
|
|
|
|
inner: Box::new(a.clone()),
|
|
|
|
|
error: Error::new("Undecrypted."),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
for a in parts {
|
|
|
|
|
EnvelopeView::attachment_to_display_helper(
|
|
|
|
|
a,
|
|
|
|
|
main_loop_handler,
|
|
|
|
|
active_jobs,
|
|
|
|
|
acc,
|
|
|
|
|
view_settings,
|
|
|
|
|
force_charset,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse_attachments(&mut self) {
|
|
|
|
|
let mut display = vec![];
|
|
|
|
|
Self::attachment_to_display_helper(
|
|
|
|
|
&self.body,
|
|
|
|
|
&self.main_loop_handler,
|
|
|
|
|
&mut self.active_jobs,
|
|
|
|
|
&mut display,
|
|
|
|
|
&self.view_settings,
|
|
|
|
|
(&self.force_charset).into(),
|
|
|
|
|
);
|
|
|
|
|
let (attachment_paths, attachment_tree) = self.attachment_displays_to_tree(&display);
|
|
|
|
|
self.display = display;
|
|
|
|
|
self.attachment_tree = attachment_tree;
|
|
|
|
|
self.attachment_paths = attachment_paths;
|
2023-07-13 16:51:37 +03:00
|
|
|
|
self.regenerate_body_text();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn regenerate_body_text(&mut self) {
|
|
|
|
|
self.body_text = Self::attachment_displays_to_text(&self.display, true);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn attachment_displays_to_text(
|
|
|
|
|
displays: &[AttachmentDisplay],
|
|
|
|
|
show_comments: bool,
|
|
|
|
|
) -> String {
|
|
|
|
|
let mut acc = String::new();
|
|
|
|
|
for d in displays {
|
|
|
|
|
use AttachmentDisplay::*;
|
|
|
|
|
match d {
|
|
|
|
|
Alternative {
|
|
|
|
|
inner: _,
|
|
|
|
|
shown_display,
|
|
|
|
|
display,
|
|
|
|
|
} => {
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
&display[*shown_display..(*shown_display + 1)],
|
|
|
|
|
show_comments,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
InlineText {
|
|
|
|
|
inner: _,
|
|
|
|
|
text,
|
|
|
|
|
comment: Some(comment),
|
|
|
|
|
} if show_comments => {
|
|
|
|
|
acc.push_str(comment);
|
|
|
|
|
if !acc.ends_with("\n\n") {
|
|
|
|
|
acc.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
acc.push_str(text);
|
|
|
|
|
}
|
|
|
|
|
InlineText {
|
|
|
|
|
inner: _,
|
|
|
|
|
text,
|
|
|
|
|
comment: _,
|
|
|
|
|
} => acc.push_str(text),
|
|
|
|
|
InlineOther { inner } => {
|
|
|
|
|
if !acc.ends_with("\n\n") {
|
|
|
|
|
acc.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
acc.push_str(&inner.to_string());
|
|
|
|
|
if !acc.ends_with("\n\n") {
|
|
|
|
|
acc.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Attachment { inner: _ } => {}
|
|
|
|
|
SignedPending {
|
|
|
|
|
inner: _,
|
|
|
|
|
display,
|
|
|
|
|
handle: _,
|
|
|
|
|
job_id: _,
|
|
|
|
|
} => {
|
|
|
|
|
if show_comments {
|
|
|
|
|
acc.push_str("Waiting for signature verification.\n\n");
|
|
|
|
|
}
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(display, show_comments));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
SignedUnverified { inner: _, display } => {
|
|
|
|
|
if show_comments {
|
|
|
|
|
acc.push_str("Unverified signature.\n\n");
|
|
|
|
|
}
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(display, show_comments))
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
SignedFailed {
|
|
|
|
|
inner: _,
|
|
|
|
|
display,
|
|
|
|
|
error,
|
|
|
|
|
} => {
|
|
|
|
|
if show_comments {
|
|
|
|
|
let _ = writeln!(acc, "Failed to verify signature: {}.\n", error);
|
|
|
|
|
}
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(display, show_comments));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
SignedVerified {
|
|
|
|
|
inner: _,
|
|
|
|
|
display,
|
|
|
|
|
description,
|
|
|
|
|
} => {
|
|
|
|
|
if show_comments {
|
|
|
|
|
if description.is_empty() {
|
|
|
|
|
acc.push_str("Verified signature.\n\n");
|
|
|
|
|
} else {
|
|
|
|
|
acc.push_str(description);
|
|
|
|
|
acc.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(display, show_comments));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
EncryptedPending { .. } => acc.push_str("Waiting for decryption result."),
|
|
|
|
|
EncryptedFailed { inner: _, error } => {
|
|
|
|
|
let _ = write!(acc, "Decryption failed: {}.", &error);
|
|
|
|
|
}
|
|
|
|
|
EncryptedSuccess {
|
|
|
|
|
inner: _,
|
|
|
|
|
plaintext: _,
|
|
|
|
|
plaintext_display,
|
|
|
|
|
description,
|
|
|
|
|
} => {
|
|
|
|
|
if show_comments {
|
|
|
|
|
if description.is_empty() {
|
|
|
|
|
acc.push_str("Succesfully decrypted.\n\n");
|
|
|
|
|
} else {
|
|
|
|
|
acc.push_str(description);
|
|
|
|
|
acc.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-01 20:11:22 +03:00
|
|
|
|
acc.push_str(&Self::attachment_displays_to_text(
|
|
|
|
|
plaintext_display,
|
|
|
|
|
show_comments,
|
|
|
|
|
));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
acc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn attachment_displays_to_tree(
|
|
|
|
|
&self,
|
|
|
|
|
displays: &[AttachmentDisplay],
|
|
|
|
|
) -> (Vec<Vec<usize>>, String) {
|
|
|
|
|
let mut acc = String::new();
|
|
|
|
|
let mut branches = SmallVec::new();
|
|
|
|
|
let mut paths = Vec::with_capacity(displays.len());
|
|
|
|
|
let mut cur_path = vec![];
|
|
|
|
|
let mut idx = 0;
|
|
|
|
|
|
|
|
|
|
fn append_entry(
|
|
|
|
|
(idx, (depth, att_display)): (&mut usize, (usize, &AttachmentDisplay)),
|
|
|
|
|
branches: &mut SmallVec<[bool; 8]>,
|
|
|
|
|
paths: &mut Vec<Vec<usize>>,
|
|
|
|
|
cur_path: &mut Vec<usize>,
|
|
|
|
|
has_sibling: bool,
|
|
|
|
|
s: &mut String,
|
|
|
|
|
) {
|
|
|
|
|
use AttachmentDisplay::*;
|
|
|
|
|
let mut default_alternative: Option<usize> = None;
|
|
|
|
|
let (att, sub_att_display_vec) = match att_display {
|
|
|
|
|
Alternative {
|
|
|
|
|
inner,
|
|
|
|
|
shown_display,
|
|
|
|
|
display,
|
|
|
|
|
} => {
|
|
|
|
|
default_alternative = Some(*shown_display);
|
|
|
|
|
(inner, display.as_slice())
|
|
|
|
|
}
|
|
|
|
|
InlineText {
|
|
|
|
|
inner,
|
|
|
|
|
text: _,
|
|
|
|
|
comment: _,
|
|
|
|
|
}
|
|
|
|
|
| InlineOther { inner }
|
|
|
|
|
| Attachment { inner }
|
|
|
|
|
| EncryptedPending { inner, handle: _ }
|
|
|
|
|
| EncryptedFailed { inner, error: _ } => (inner, &[][..]),
|
|
|
|
|
SignedPending {
|
|
|
|
|
inner,
|
|
|
|
|
display,
|
|
|
|
|
handle: _,
|
|
|
|
|
job_id: _,
|
|
|
|
|
}
|
|
|
|
|
| SignedUnverified { inner, display }
|
|
|
|
|
| SignedFailed {
|
|
|
|
|
inner,
|
|
|
|
|
display,
|
|
|
|
|
error: _,
|
|
|
|
|
}
|
|
|
|
|
| SignedVerified {
|
|
|
|
|
inner,
|
|
|
|
|
display,
|
|
|
|
|
description: _,
|
|
|
|
|
}
|
|
|
|
|
| EncryptedSuccess {
|
|
|
|
|
inner: _,
|
|
|
|
|
plaintext: inner,
|
|
|
|
|
plaintext_display: display,
|
|
|
|
|
description: _,
|
|
|
|
|
} => (inner, display.as_slice()),
|
|
|
|
|
};
|
|
|
|
|
s.extend(format!("\n[{}]", idx).chars());
|
|
|
|
|
for &b in branches.iter() {
|
|
|
|
|
if b {
|
|
|
|
|
s.push('|');
|
|
|
|
|
} else {
|
|
|
|
|
s.push(' ');
|
|
|
|
|
}
|
|
|
|
|
s.push(' ');
|
|
|
|
|
}
|
|
|
|
|
if depth > 0 {
|
|
|
|
|
if has_sibling {
|
|
|
|
|
s.push('|');
|
|
|
|
|
} else {
|
|
|
|
|
s.push(' ');
|
|
|
|
|
}
|
|
|
|
|
s.push_str("\\_ ");
|
|
|
|
|
} else {
|
|
|
|
|
s.push(' ');
|
|
|
|
|
s.push(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.push_str(&att.to_string());
|
|
|
|
|
paths.push(cur_path.clone());
|
|
|
|
|
if matches!(att.content_type, ContentType::Multipart { .. }) {
|
|
|
|
|
let mut iter = (0..sub_att_display_vec.len()).peekable();
|
|
|
|
|
if has_sibling {
|
|
|
|
|
branches.push(true);
|
|
|
|
|
} else {
|
|
|
|
|
branches.push(false);
|
|
|
|
|
}
|
|
|
|
|
while let Some(i) = iter.next() {
|
|
|
|
|
*idx += 1;
|
|
|
|
|
cur_path.push(i);
|
|
|
|
|
append_entry(
|
|
|
|
|
(idx, (depth + 1, &sub_att_display_vec[i])),
|
|
|
|
|
branches,
|
|
|
|
|
paths,
|
|
|
|
|
cur_path,
|
|
|
|
|
iter.peek().is_some(),
|
|
|
|
|
s,
|
|
|
|
|
);
|
|
|
|
|
if Some(i) == default_alternative {
|
|
|
|
|
s.push_str(" (displayed by default)");
|
|
|
|
|
}
|
|
|
|
|
cur_path.pop();
|
|
|
|
|
}
|
|
|
|
|
branches.pop();
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
|
|
|
|
|
for (i, d) in displays.iter().enumerate() {
|
|
|
|
|
cur_path.push(i);
|
|
|
|
|
append_entry(
|
|
|
|
|
(&mut idx, (0, d)),
|
|
|
|
|
&mut branches,
|
|
|
|
|
&mut paths,
|
|
|
|
|
&mut cur_path,
|
|
|
|
|
i + 1 < displays.len(),
|
|
|
|
|
&mut acc,
|
|
|
|
|
);
|
|
|
|
|
cur_path.pop();
|
|
|
|
|
idx += 1;
|
|
|
|
|
}
|
|
|
|
|
(paths, acc)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
fn open_attachment(
|
|
|
|
|
&'_ self,
|
|
|
|
|
lidx: usize,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> Option<&'_ melib::Attachment> {
|
|
|
|
|
if let Some(path) = self.attachment_paths.get(lidx).and_then(|path| {
|
|
|
|
|
if !path.is_empty() {
|
|
|
|
|
Some(path)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
let first = path[0];
|
|
|
|
|
use AttachmentDisplay::*;
|
|
|
|
|
let root_attachment = match &self.display[first] {
|
|
|
|
|
Alternative {
|
|
|
|
|
inner,
|
|
|
|
|
shown_display: _,
|
|
|
|
|
display: _,
|
|
|
|
|
}
|
|
|
|
|
| InlineText {
|
|
|
|
|
inner,
|
|
|
|
|
text: _,
|
|
|
|
|
comment: _,
|
|
|
|
|
}
|
|
|
|
|
| InlineOther { inner }
|
|
|
|
|
| Attachment { inner }
|
|
|
|
|
| SignedPending {
|
|
|
|
|
inner,
|
|
|
|
|
display: _,
|
|
|
|
|
handle: _,
|
|
|
|
|
job_id: _,
|
|
|
|
|
}
|
|
|
|
|
| SignedFailed {
|
|
|
|
|
inner,
|
|
|
|
|
display: _,
|
|
|
|
|
error: _,
|
|
|
|
|
}
|
|
|
|
|
| SignedVerified {
|
|
|
|
|
inner,
|
|
|
|
|
display: _,
|
|
|
|
|
description: _,
|
|
|
|
|
}
|
|
|
|
|
| SignedUnverified { inner, display: _ }
|
|
|
|
|
| EncryptedPending { inner, handle: _ }
|
|
|
|
|
| EncryptedFailed { inner, error: _ }
|
|
|
|
|
| EncryptedSuccess {
|
|
|
|
|
inner: _,
|
|
|
|
|
plaintext: inner,
|
|
|
|
|
plaintext_display: _,
|
|
|
|
|
description: _,
|
|
|
|
|
} => inner,
|
|
|
|
|
};
|
|
|
|
|
fn find_attachment<'a>(
|
|
|
|
|
a: &'a melib::Attachment,
|
|
|
|
|
path: &[usize],
|
|
|
|
|
) -> Option<&'a melib::Attachment> {
|
|
|
|
|
if path.is_empty() {
|
|
|
|
|
return Some(a);
|
|
|
|
|
}
|
|
|
|
|
if let ContentType::Multipart { ref parts, .. } = a.content_type {
|
|
|
|
|
let first = path[0];
|
|
|
|
|
if first < parts.len() {
|
|
|
|
|
return find_attachment(&parts[first], &path[1..]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ret = find_attachment(root_attachment, &path[1..]);
|
|
|
|
|
if lidx == 0 {
|
|
|
|
|
return ret.and_then(|a| {
|
|
|
|
|
if a.content_disposition.kind.is_attachment()
|
|
|
|
|
|| a.content_type == "message/rfc822"
|
|
|
|
|
{
|
|
|
|
|
Some(a)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Attachment `{}` not found.",
|
|
|
|
|
lidx
|
|
|
|
|
))));
|
|
|
|
|
None
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Component for EnvelopeView {
|
|
|
|
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.view_settings.theme_default = crate::conf::value(context, "theme_default");
|
|
|
|
|
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let hdr_theme = crate::conf::value(context, "mail.view.headers");
|
|
|
|
|
let hdr_name_theme = crate::conf::value(context, "mail.view.headers_names");
|
|
|
|
|
let hdr_area_theme = crate::conf::value(context, "mail.view.headers_area");
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2018-08-23 15:36:52 +03:00
|
|
|
|
let y: usize = {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if self.mode.is_source() {
|
2023-10-27 12:05:36 +03:00
|
|
|
|
grid.clear_area(area, self.view_settings.theme_default);
|
2018-08-18 17:50:31 +03:00
|
|
|
|
context.dirty_areas.push_back(area);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
0
|
2018-08-18 17:50:31 +03:00
|
|
|
|
} else {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let envelope = &self.mail;
|
|
|
|
|
let height_p = self.pager.size().1;
|
|
|
|
|
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let height = area
|
|
|
|
|
.height()
|
2023-07-19 10:19:11 +03:00
|
|
|
|
.saturating_sub(self.headers_no)
|
|
|
|
|
.saturating_sub(1);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
|
|
|
|
|
self.headers_no = 0;
|
|
|
|
|
let mut skip_header_ctr = self.headers_cursor;
|
|
|
|
|
let sticky = self.view_settings.sticky_headers || height_p < height;
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let mut y = 0;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
macro_rules! print_header {
|
|
|
|
|
($(($header:path, $string:expr)),*$(,)?) => {
|
|
|
|
|
$({
|
|
|
|
|
if sticky || skip_header_ctr == 0 {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
if y <= area.height() {
|
|
|
|
|
grid.clear_area(
|
|
|
|
|
area.skip_rows(y),
|
|
|
|
|
hdr_area_theme,
|
|
|
|
|
);
|
2023-10-27 11:37:19 +03:00
|
|
|
|
let (_x, _y) =
|
2023-10-27 12:11:45 +03:00
|
|
|
|
grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
&format!("{}:", $header),
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_name_theme.fg,
|
|
|
|
|
hdr_name_theme.bg,
|
|
|
|
|
hdr_name_theme.attrs,
|
|
|
|
|
area.skip_rows(y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let (__x, __y) =
|
2023-10-27 12:11:45 +03:00
|
|
|
|
grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
&$string,
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_theme.fg,
|
|
|
|
|
hdr_theme.bg,
|
|
|
|
|
hdr_theme.attrs,
|
|
|
|
|
area.skip(_x+1, y+ _y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
y += _y +__y + 1;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
skip_header_ctr = skip_header_ctr.saturating_sub(1);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
self.headers_no += 1;
|
|
|
|
|
})+
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let find_offset = |s: &str| -> (bool, (i64, i64)) {
|
|
|
|
|
let mut diff = (true, (0, 0));
|
|
|
|
|
if let Some(pos) = s.as_bytes().iter().position(|b| *b == b'+' || *b == b'-') {
|
|
|
|
|
let offset = &s[pos..];
|
|
|
|
|
diff.0 = offset.starts_with('+');
|
|
|
|
|
if let (Ok(hr_offset), Ok(min_offset)) =
|
|
|
|
|
(offset[1..3].parse::<i64>(), offset[3..5].parse::<i64>())
|
|
|
|
|
{
|
|
|
|
|
diff.1 .0 = hr_offset;
|
|
|
|
|
diff.1 .1 = min_offset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
diff
|
|
|
|
|
};
|
|
|
|
|
let orig_date = envelope.date_as_str();
|
|
|
|
|
let date_str: std::borrow::Cow<str> = if self.view_settings.show_date_in_my_timezone
|
|
|
|
|
{
|
|
|
|
|
let local_date = datetime::timestamp_to_string(
|
|
|
|
|
envelope.timestamp,
|
|
|
|
|
Some(datetime::formats::RFC822_DATE),
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
let orig_offset = find_offset(orig_date);
|
|
|
|
|
let local_offset = find_offset(&local_date);
|
|
|
|
|
if orig_offset == local_offset {
|
|
|
|
|
orig_date.into()
|
|
|
|
|
} else {
|
|
|
|
|
format!(
|
|
|
|
|
"{} [actual timezone: {}{:02}{:02}]",
|
|
|
|
|
local_date,
|
|
|
|
|
if orig_offset.0 { '+' } else { '-' },
|
|
|
|
|
orig_offset.1 .0,
|
|
|
|
|
orig_offset.1 .1
|
|
|
|
|
)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
orig_date.into()
|
|
|
|
|
};
|
|
|
|
|
print_header!(
|
|
|
|
|
(HeaderName::DATE, date_str),
|
|
|
|
|
(HeaderName::FROM, envelope.field_from_to_string()),
|
|
|
|
|
(HeaderName::TO, envelope.field_to_to_string()),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if envelope.other_headers().contains_key(HeaderName::CC)
|
|
|
|
|
&& !envelope.other_headers()[HeaderName::CC].is_empty()
|
|
|
|
|
{
|
|
|
|
|
print_header!((HeaderName::CC, envelope.field_cc_to_string()));
|
|
|
|
|
}
|
|
|
|
|
print_header!(
|
|
|
|
|
(HeaderName::SUBJECT, envelope.subject()),
|
|
|
|
|
(
|
|
|
|
|
HeaderName::MESSAGE_ID,
|
|
|
|
|
format!("<{}>", envelope.message_id_raw())
|
|
|
|
|
)
|
2018-08-18 17:50:31 +03:00
|
|
|
|
);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if self.view_settings.expand_headers {
|
|
|
|
|
if let Some(val) = envelope.in_reply_to_display() {
|
|
|
|
|
print_header!(
|
|
|
|
|
(HeaderName::IN_REPLY_TO, val),
|
|
|
|
|
(
|
|
|
|
|
HeaderName::REFERENCES,
|
|
|
|
|
envelope
|
|
|
|
|
.references()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(std::string::ToString::to_string)
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
for hdr in &self.view_settings.show_extra_headers {
|
|
|
|
|
if let Some((val, hdr)) = HeaderName::try_from(hdr)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|hdr| Some((envelope.other_headers().get(&hdr)?, hdr)))
|
|
|
|
|
{
|
|
|
|
|
print_header!((hdr, val));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(list_management::ListActions {
|
|
|
|
|
ref id,
|
|
|
|
|
ref archive,
|
|
|
|
|
ref post,
|
|
|
|
|
ref unsubscribe,
|
2023-07-01 20:11:22 +03:00
|
|
|
|
}) = list_management::ListActions::detect(envelope)
|
2023-06-14 12:24:20 +03:00
|
|
|
|
{
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let mut x = 0;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if let Some(id) = id {
|
|
|
|
|
if sticky || skip_header_ctr == 0 {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
grid.clear_area(area.nth_row(y), hdr_area_theme);
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
"List-ID: ",
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_name_theme.fg,
|
|
|
|
|
hdr_name_theme.bg,
|
|
|
|
|
hdr_name_theme.attrs,
|
|
|
|
|
area.nth_row(y),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
None,
|
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x = _x;
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _y) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
id,
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_theme.fg,
|
|
|
|
|
hdr_theme.bg,
|
|
|
|
|
hdr_theme.attrs,
|
|
|
|
|
area.nth_row(y).skip_cols(_x),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
None,
|
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x += _x;
|
|
|
|
|
if _y != 0 {
|
|
|
|
|
x = 0;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
2023-10-23 13:56:13 +03:00
|
|
|
|
y += _y;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
self.headers_no += 1;
|
|
|
|
|
}
|
|
|
|
|
if sticky || skip_header_ctr == 0 {
|
|
|
|
|
if archive.is_some() || post.is_some() || unsubscribe.is_some() {
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _y) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
" Available actions: [ ",
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_name_theme.fg,
|
|
|
|
|
hdr_name_theme.bg,
|
|
|
|
|
hdr_name_theme.attrs,
|
|
|
|
|
area.skip(x, y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x += _x;
|
|
|
|
|
y += _y;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
if archive.is_some() {
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _y) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
"list-archive, ",
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_theme.fg,
|
|
|
|
|
hdr_theme.bg,
|
|
|
|
|
hdr_theme.attrs,
|
|
|
|
|
area.skip(x, y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x += _x;
|
|
|
|
|
y += _y;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
if post.is_some() {
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _y) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
"list-post, ",
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_theme.fg,
|
|
|
|
|
hdr_theme.bg,
|
|
|
|
|
hdr_theme.attrs,
|
|
|
|
|
area.skip(x, y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x += _x;
|
|
|
|
|
y += _y;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
if unsubscribe.is_some() {
|
2023-10-27 12:11:45 +03:00
|
|
|
|
let (_x, _y) = grid.write_string(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
"list-unsubscribe, ",
|
2023-10-23 13:56:13 +03:00
|
|
|
|
hdr_theme.fg,
|
|
|
|
|
hdr_theme.bg,
|
|
|
|
|
hdr_theme.attrs,
|
|
|
|
|
area.skip(x, y),
|
|
|
|
|
Some(0),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
);
|
2023-10-23 13:56:13 +03:00
|
|
|
|
x += _x;
|
|
|
|
|
y += _y;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
if archive.is_some() || post.is_some() || unsubscribe.is_some() {
|
|
|
|
|
if x >= 2 {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
for c in grid.row_iter(area, (x - 2)..(x - 1), y) {
|
|
|
|
|
grid[c].set_ch(' ');
|
2023-09-09 12:54:59 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
if x > 0 {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
for c in grid.row_iter(area, (x - 1)..x, y) {
|
|
|
|
|
grid[c]
|
|
|
|
|
.set_ch(']')
|
|
|
|
|
.set_fg(hdr_name_theme.fg)
|
|
|
|
|
.set_bg(hdr_name_theme.bg)
|
|
|
|
|
.set_attrs(hdr_name_theme.attrs);
|
2023-09-09 12:54:59 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-23 13:56:13 +03:00
|
|
|
|
for c in grid.row_iter(area, (x + 1)..area.width(), y) {
|
|
|
|
|
grid[c]
|
2023-06-14 12:24:20 +03:00
|
|
|
|
.set_ch(' ')
|
2023-10-23 13:56:13 +03:00
|
|
|
|
.set_fg(hdr_area_theme.fg)
|
|
|
|
|
.set_bg(hdr_area_theme.bg);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
y += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.force_draw_headers = false;
|
2023-10-27 12:05:36 +03:00
|
|
|
|
|
2023-10-23 13:56:13 +03:00
|
|
|
|
grid.clear_area(area.nth_row(y), hdr_area_theme);
|
|
|
|
|
context.dirty_areas.push_back(area.take_rows(y + 3));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if !self.view_settings.sticky_headers {
|
|
|
|
|
let height_p = self.pager.size().1;
|
|
|
|
|
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let height = area.height().saturating_sub(y).saturating_sub(1);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if self.pager.cursor_pos() >= self.headers_no {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
0
|
2023-06-14 12:24:20 +03:00
|
|
|
|
} else if (height_p > height && self.headers_cursor < self.headers_no + 1)
|
|
|
|
|
|| self.headers_cursor == 0
|
|
|
|
|
|| height_p < height
|
|
|
|
|
{
|
|
|
|
|
y + 1
|
|
|
|
|
} else {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
0
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
y + 1
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if !self.initialised {
|
|
|
|
|
self.initialised = true;
|
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];
|
2022-08-25 15:17:18 +03:00
|
|
|
|
self.subview = Some(Box::new(HtmlView::new(attachment, context)));
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
ViewMode::Attachment(aidx) => {
|
2023-07-07 13:50:46 +03:00
|
|
|
|
let mut text = format!(
|
|
|
|
|
"Viewing attachment. Press {} to return \n",
|
|
|
|
|
self.shortcuts(context)
|
|
|
|
|
.get(Shortcuts::ENVELOPE_VIEW)
|
|
|
|
|
.and_then(|m| m.get("return_to_normal_view"))
|
|
|
|
|
.unwrap_or(
|
|
|
|
|
&context
|
|
|
|
|
.settings
|
|
|
|
|
.shortcuts
|
|
|
|
|
.envelope_view
|
|
|
|
|
.return_to_normal_view
|
|
|
|
|
)
|
|
|
|
|
);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let attachment = &body.attachments()[aidx];
|
|
|
|
|
text.push_str(&attachment.text());
|
|
|
|
|
self.pager = Pager::from_string(
|
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
|
|
|
|
Some(0),
|
|
|
|
|
None,
|
|
|
|
|
self.view_settings.theme_default,
|
|
|
|
|
);
|
|
|
|
|
if let Some(ref filter) = self.view_settings.pager_filter {
|
|
|
|
|
self.pager.filter(filter);
|
|
|
|
|
}
|
|
|
|
|
self.subview = None;
|
|
|
|
|
}
|
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;
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
ViewMode::Normal
|
|
|
|
|
if self.view_settings.auto_choose_multipart_alternative
|
|
|
|
|
&& match body.content_type {
|
|
|
|
|
ContentType::Multipart {
|
|
|
|
|
kind: MultipartType::Alternative,
|
|
|
|
|
ref parts,
|
|
|
|
|
..
|
|
|
|
|
} => parts.iter().all(|p| {
|
|
|
|
|
p.is_html() || (p.is_text() && p.body().trim().is_empty())
|
|
|
|
|
}),
|
|
|
|
|
_ => false,
|
|
|
|
|
} =>
|
|
|
|
|
{
|
|
|
|
|
let subview = Box::new(HtmlView::new(
|
|
|
|
|
body.content_type
|
|
|
|
|
.parts()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|a| a.is_html())
|
|
|
|
|
.unwrap_or(&body),
|
|
|
|
|
context,
|
|
|
|
|
));
|
|
|
|
|
self.subview = Some(subview);
|
|
|
|
|
self.mode = ViewMode::Subview;
|
|
|
|
|
}
|
|
|
|
|
ViewMode::Subview => {}
|
|
|
|
|
ViewMode::Source(Source::Raw) => {
|
2023-07-07 13:50:46 +03:00
|
|
|
|
let text = String::from_utf8_lossy(self.mail.bytes()).into_owned();
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.view_settings.body_theme = crate::conf::value(context, "mail.view.body");
|
|
|
|
|
self.pager = Pager::from_string(
|
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
self.view_settings.body_theme,
|
|
|
|
|
);
|
|
|
|
|
if let Some(ref filter) = self.view_settings.pager_filter {
|
|
|
|
|
self.pager.filter(filter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ViewMode::Source(Source::Decoded) => {
|
|
|
|
|
let text = {
|
|
|
|
|
/* Decode each header value */
|
2023-08-24 11:32:21 +03:00
|
|
|
|
let mut ret = String::new();
|
|
|
|
|
match melib::email::parser::headers::headers(self.mail.bytes())
|
2023-06-14 12:24:20 +03:00
|
|
|
|
.map(|(_, v)| v)
|
2023-08-24 11:32:21 +03:00
|
|
|
|
{
|
|
|
|
|
Ok(headers) => {
|
|
|
|
|
for (h, v) in headers {
|
|
|
|
|
_ = match melib::email::parser::encodings::phrase(v, true) {
|
|
|
|
|
Ok((_, v)) => ret.write_fmt(format_args!(
|
|
|
|
|
"{h}: {}\n",
|
|
|
|
|
String::from_utf8_lossy(&v)
|
|
|
|
|
)),
|
|
|
|
|
Err(err) => ret.write_fmt(format_args!("{h}: {err}\n")),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
_ = write!(&mut ret, "{err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if !ret.ends_with("\n\n") {
|
2023-08-24 11:32:21 +03:00
|
|
|
|
if ret.ends_with('\n') {
|
|
|
|
|
ret.pop();
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
ret.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
ret.push_str(&self.body_text);
|
|
|
|
|
if !ret.ends_with("\n\n") {
|
2023-08-24 11:32:21 +03:00
|
|
|
|
if ret.ends_with('\n') {
|
|
|
|
|
ret.pop();
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
ret.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
// ret.push_str(&self.attachment_tree);
|
|
|
|
|
ret
|
|
|
|
|
};
|
|
|
|
|
self.view_settings.body_theme = crate::conf::value(context, "mail.view.body");
|
|
|
|
|
self.pager = Pager::from_string(
|
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
self.view_settings.body_theme,
|
|
|
|
|
);
|
|
|
|
|
if let Some(ref filter) = self.view_settings.pager_filter {
|
|
|
|
|
self.pager.filter(filter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ViewMode::Url => {
|
|
|
|
|
let mut text = self.body_text.clone();
|
|
|
|
|
if self.links.is_empty() {
|
|
|
|
|
let finder = LinkFinder::new();
|
|
|
|
|
self.links = finder
|
|
|
|
|
.links(&text)
|
|
|
|
|
.filter_map(|l| {
|
|
|
|
|
if *l.kind() == linkify::LinkKind::Url {
|
|
|
|
|
Some(Link {
|
|
|
|
|
start: l.start(),
|
|
|
|
|
end: l.end(),
|
|
|
|
|
kind: LinkKind::Url,
|
|
|
|
|
})
|
|
|
|
|
} else if *l.kind() == linkify::LinkKind::Email {
|
|
|
|
|
Some(Link {
|
|
|
|
|
start: l.start(),
|
|
|
|
|
end: l.end(),
|
|
|
|
|
kind: LinkKind::Email,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<Link>>();
|
|
|
|
|
}
|
|
|
|
|
for (lidx, l) in self.links.iter().enumerate().rev() {
|
|
|
|
|
text.insert_str(l.start, &format!("[{}]", lidx));
|
|
|
|
|
}
|
|
|
|
|
if !text.ends_with("\n\n") {
|
|
|
|
|
text.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
text.push_str(&self.attachment_tree);
|
|
|
|
|
|
|
|
|
|
let cursor_pos = self.pager.cursor_pos();
|
|
|
|
|
self.view_settings.body_theme = crate::conf::value(context, "mail.view.body");
|
|
|
|
|
self.pager = Pager::from_string(
|
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
|
|
|
|
Some(cursor_pos),
|
|
|
|
|
None,
|
|
|
|
|
self.view_settings.body_theme,
|
|
|
|
|
);
|
|
|
|
|
if let Some(ref filter) = self.view_settings.pager_filter {
|
|
|
|
|
self.pager.filter(filter);
|
|
|
|
|
}
|
|
|
|
|
self.subview = None;
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
_ => {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let mut text = self.body_text.clone();
|
|
|
|
|
if !text.ends_with("\n\n") {
|
|
|
|
|
text.push_str("\n\n");
|
|
|
|
|
}
|
|
|
|
|
text.push_str(&self.attachment_tree);
|
2018-08-18 17:50:31 +03:00
|
|
|
|
let cursor_pos = if self.mode.is_attachment() {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
0
|
2018-08-18 17:50:31 +03:00
|
|
|
|
} else {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.pager.cursor_pos()
|
2018-08-18 17:50:31 +03:00
|
|
|
|
};
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.view_settings.body_theme = crate::conf::value(context, "mail.view.body");
|
|
|
|
|
self.pager = Pager::from_string(
|
2020-01-22 00:05:26 +02:00
|
|
|
|
text,
|
|
|
|
|
Some(context),
|
2023-06-14 12:24:20 +03:00
|
|
|
|
Some(cursor_pos),
|
2020-01-22 00:05:26 +02:00
|
|
|
|
None,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.view_settings.body_theme,
|
|
|
|
|
);
|
|
|
|
|
if let Some(ref filter) = self.view_settings.pager_filter {
|
|
|
|
|
self.pager.filter(filter);
|
|
|
|
|
}
|
|
|
|
|
self.subview = None;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
self.dirty = false;
|
|
|
|
|
}
|
2023-04-10 11:42:50 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Subview if self.subview.is_some() => {
|
|
|
|
|
if let Some(s) = self.subview.as_mut() {
|
|
|
|
|
if !s.is_dirty() {
|
|
|
|
|
s.set_dirty(true);
|
|
|
|
|
}
|
2023-10-23 13:56:13 +03:00
|
|
|
|
s.draw(grid, area.skip_rows(y), context);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
self.pager.draw(grid, area.skip_rows(y), context);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-10 11:42:50 +03:00
|
|
|
|
if let ForceCharset::Dialog(ref mut s) = self.force_charset {
|
|
|
|
|
s.draw(grid, area, context);
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
|
2023-09-13 15:29:06 +03:00
|
|
|
|
// Draw number command buffer at the bottom right corner:
|
|
|
|
|
|
2023-10-23 13:56:13 +03:00
|
|
|
|
let l = area.nth_row(area.height());
|
2023-09-13 15:29:06 +03:00
|
|
|
|
if self.cmd_buf.is_empty() {
|
2023-10-23 13:56:13 +03:00
|
|
|
|
grid.clear_area(l.skip_cols_from_end(8), self.view_settings.theme_default);
|
2023-09-13 15:29:06 +03:00
|
|
|
|
} else {
|
|
|
|
|
let s = self.cmd_buf.to_string();
|
2023-10-27 12:11:45 +03:00
|
|
|
|
grid.write_string(
|
2023-09-13 15:29:06 +03:00
|
|
|
|
&s,
|
|
|
|
|
self.view_settings.theme_default.fg,
|
|
|
|
|
self.view_settings.theme_default.bg,
|
|
|
|
|
self.view_settings.theme_default.attrs,
|
2023-10-23 13:56:13 +03:00
|
|
|
|
l.skip_cols_from_end(8),
|
2023-09-13 15:29:06 +03:00
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.dirty = false;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-26 17:50:47 +02:00
|
|
|
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
2023-04-10 11:42:50 +03:00
|
|
|
|
match (&mut self.force_charset, &event) {
|
|
|
|
|
(ForceCharset::Dialog(selector), UIEvent::FinishedUIDialog(id, results))
|
|
|
|
|
if *id == selector.id() =>
|
|
|
|
|
{
|
|
|
|
|
if let Some(results) = results.downcast_ref::<Vec<Option<Charset>>>() {
|
|
|
|
|
if results.len() != 1 {
|
|
|
|
|
self.force_charset = ForceCharset::None;
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if let Some(charset) = results[0] {
|
|
|
|
|
self.force_charset = ForceCharset::Forced(charset);
|
|
|
|
|
} else {
|
|
|
|
|
self.force_charset = ForceCharset::None;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.force_charset = ForceCharset::None;
|
|
|
|
|
}
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
(ForceCharset::Dialog(selector), _) => {
|
|
|
|
|
if selector.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-23 14:39:54 +03:00
|
|
|
|
if let Some(ref mut sub) = self.subview {
|
|
|
|
|
if sub.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2023-10-23 13:56:13 +03:00
|
|
|
|
} else {
|
|
|
|
|
if !self.view_settings.sticky_headers {
|
|
|
|
|
let shortcuts = self.pager.shortcuts(context);
|
|
|
|
|
match event {
|
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_up"])
|
|
|
|
|
&& self.pager.cursor_pos() == 0
|
|
|
|
|
&& self.headers_cursor > 0 =>
|
|
|
|
|
{
|
|
|
|
|
self.headers_cursor -= 1;
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if shortcut!(key == shortcuts[Shortcuts::PAGER]["scroll_down"])
|
|
|
|
|
&& self.headers_cursor < self.headers_no =>
|
|
|
|
|
{
|
|
|
|
|
self.headers_cursor += 1;
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.pager.process_event(event, context) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
}
|
2023-04-10 11:42:50 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let shortcuts = &self.shortcuts(context);
|
|
|
|
|
|
2019-04-10 23:37:20 +03:00
|
|
|
|
match *event {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Resize | UIEvent::VisibilityChange(true) => {
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
}
|
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
|
|
|
|
}
|
2023-04-30 19:39:41 +03:00
|
|
|
|
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
|
2023-09-13 15:29:06 +03:00
|
|
|
|
if self.cmd_buf.len() < 9 {
|
|
|
|
|
self.cmd_buf.push(c);
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if matches!(
|
|
|
|
|
self.mode,
|
2018-08-18 17:50:31 +03:00
|
|
|
|
ViewMode::Normal
|
2023-06-14 12:24:20 +03:00
|
|
|
|
| ViewMode::Subview
|
|
|
|
|
| ViewMode::Source(Source::Decoded)
|
|
|
|
|
| ViewMode::Source(Source::Raw)
|
|
|
|
|
) && shortcut!(
|
|
|
|
|
key == shortcuts[Shortcuts::ENVELOPE_VIEW]["view_raw_source"]
|
|
|
|
|
) =>
|
|
|
|
|
{
|
|
|
|
|
self.mode = match self.mode {
|
|
|
|
|
ViewMode::Source(Source::Decoded) => ViewMode::Source(Source::Raw),
|
|
|
|
|
_ => ViewMode::Source(Source::Decoded),
|
2018-08-18 17:50:31 +03:00
|
|
|
|
};
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
self.initialised = false;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if matches!(
|
|
|
|
|
self.mode,
|
|
|
|
|
ViewMode::Attachment(_)
|
|
|
|
|
| ViewMode::Subview
|
|
|
|
|
| ViewMode::Url
|
|
|
|
|
| ViewMode::Source(Source::Decoded)
|
|
|
|
|
| ViewMode::Source(Source::Raw)
|
|
|
|
|
) && shortcut!(
|
|
|
|
|
key == shortcuts[Shortcuts::ENVELOPE_VIEW]["return_to_normal_view"]
|
|
|
|
|
) =>
|
2018-08-23 15:36:52 +03:00
|
|
|
|
{
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.mode = ViewMode::Normal;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
self.initialised = false;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview)
|
|
|
|
|
&& !self.cmd_buf.is_empty()
|
|
|
|
|
&& shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["open_mailcap"]) =>
|
2018-08-18 17:50:31 +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));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if let Some(attachment) = self.open_attachment(lidx, context) {
|
|
|
|
|
if let Ok(()) = crate::mailcap::MailcapEntry::execute(attachment, context) {
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
} else {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"no mailcap entry found for {}",
|
|
|
|
|
attachment.content_type()
|
|
|
|
|
)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Action(View(ViewAction::ExportMail(ref path))) => {
|
|
|
|
|
// Save entire message as eml
|
|
|
|
|
let mut path = std::path::Path::new(path).to_path_buf();
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
if path.is_dir() {
|
|
|
|
|
path.push(format!("{}.eml", self.mail.message_id_raw()));
|
|
|
|
|
}
|
2023-09-09 19:05:46 +03:00
|
|
|
|
if path.is_relative() {
|
|
|
|
|
path = context.current_dir().join(&path);
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
match save_attachment(&path, &self.mail.bytes) {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to create file at {}", path.display())),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
));
|
|
|
|
|
log::error!("Failed to create file at {}: {err}", path.display());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
None,
|
|
|
|
|
format!("Saved at {}", &path.display()),
|
|
|
|
|
Some(NotificationType::Info),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => {
|
|
|
|
|
let mut path = std::path::Path::new(path).to_path_buf();
|
|
|
|
|
|
|
|
|
|
if let Some(u) = self.open_attachment(a_i, context) {
|
|
|
|
|
if path.is_dir() {
|
|
|
|
|
if let Some(filename) = u.filename() {
|
|
|
|
|
path.push(filename);
|
|
|
|
|
} else {
|
|
|
|
|
path.push(format!(
|
|
|
|
|
"meli_attachment_{a_i}_{}",
|
|
|
|
|
Uuid::new_v4().as_simple()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-09 19:05:46 +03:00
|
|
|
|
if path.is_relative() {
|
|
|
|
|
path = context.current_dir().join(&path);
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
match save_attachment(&path, &u.decode(Default::default())) {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to create file at {}", path.display())),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
));
|
|
|
|
|
log::error!("Failed to create file at {}: {err}", path.display());
|
|
|
|
|
}
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2022-08-25 15:17:18 +03:00
|
|
|
|
None,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
format!("Saved at {}", path.display()),
|
|
|
|
|
Some(NotificationType::Info),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if a_i == 0 {
|
|
|
|
|
// Save entire message as eml
|
|
|
|
|
if path.is_dir() {
|
|
|
|
|
path.push(format!("{}.eml", self.mail.message_id_raw()));
|
|
|
|
|
}
|
2023-09-09 19:05:46 +03:00
|
|
|
|
if path.is_relative() {
|
|
|
|
|
path = context.current_dir().join(&path);
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
match save_attachment(&path, &self.mail.bytes) {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to create file at {}", path.display())),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
));
|
|
|
|
|
log::error!("Failed to create file at {}: {err}", path.display());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
2022-08-25 15:17:18 +03:00
|
|
|
|
None,
|
2023-06-14 12:24:20 +03:00
|
|
|
|
format!("Saved at {}", &path.display()),
|
|
|
|
|
Some(NotificationType::Info),
|
|
|
|
|
));
|
2022-08-25 15:17:18 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Attachment `{}` not found.",
|
|
|
|
|
a_i
|
|
|
|
|
))));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["open_attachment"])
|
|
|
|
|
&& !self.cmd_buf.is_empty()
|
|
|
|
|
&& (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview) =>
|
|
|
|
|
{
|
|
|
|
|
let lidx = self.cmd_buf.parse::<usize>().unwrap();
|
|
|
|
|
self.cmd_buf.clear();
|
|
|
|
|
context
|
|
|
|
|
.replies
|
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
|
|
|
|
if let Some(attachment) = self.open_attachment(lidx, context) {
|
|
|
|
|
match attachment.content_type() {
|
|
|
|
|
ContentType::MessageRfc822 => {
|
|
|
|
|
match Mail::new(attachment.body().to_vec(), Some(Flag::SEEN)) {
|
|
|
|
|
Ok(wrapper) => {
|
2023-07-10 08:34:35 +03:00
|
|
|
|
self.mode = ViewMode::Subview;
|
|
|
|
|
self.subview = Some(Box::new(EnvelopeView::new(
|
|
|
|
|
wrapper,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(self.view_settings.clone()),
|
|
|
|
|
context.main_loop_handler.clone(),
|
|
|
|
|
)));
|
|
|
|
|
self.set_dirty(true);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!("{}", e)),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-25 15:17:18 +03:00
|
|
|
|
ContentType::Text { .. }
|
|
|
|
|
| ContentType::PGPSignature
|
|
|
|
|
| ContentType::CMSSignature => {
|
|
|
|
|
self.mode = ViewMode::Attachment(lidx);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.initialised = false;
|
2022-08-25 15:17:18 +03:00
|
|
|
|
self.dirty = true;
|
|
|
|
|
}
|
|
|
|
|
ContentType::Multipart { .. } => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
"Multipart attachments are not supported yet.".to_string(),
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
ContentType::Other { .. } => {
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let attachment_type = attachment.mime_type();
|
|
|
|
|
let filename = attachment.filename();
|
2022-08-25 15:17:18 +03:00
|
|
|
|
if let Ok(command) = query_default_app(&attachment_type) {
|
2023-11-26 19:28:11 +02:00
|
|
|
|
match File::create_temp_file(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
&attachment.decode(Default::default()),
|
2022-08-25 15:17:18 +03:00
|
|
|
|
filename.as_deref(),
|
|
|
|
|
None,
|
2023-05-30 21:36:24 +03:00
|
|
|
|
None,
|
2022-08-25 15:17:18 +03:00
|
|
|
|
true,
|
2023-11-26 19:28:11 +02:00
|
|
|
|
)
|
|
|
|
|
.and_then(|p| {
|
|
|
|
|
let exec_cmd = desktop_exec_to_command(
|
|
|
|
|
&command,
|
|
|
|
|
p.path().display().to_string(),
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
Ok((
|
|
|
|
|
p,
|
|
|
|
|
Command::new("sh")
|
|
|
|
|
.args(["-c", &exec_cmd])
|
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.spawn()?,
|
|
|
|
|
))
|
|
|
|
|
}) {
|
|
|
|
|
Ok((p, child)) => {
|
2022-08-25 15:17:18 +03:00
|
|
|
|
context.temp_files.push(p);
|
|
|
|
|
context.children.push(child);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
2023-11-26 19:28:11 +02:00
|
|
|
|
"Failed to execute command: {err}"
|
2022-08-25 15:17:18 +03:00
|
|
|
|
)),
|
|
|
|
|
));
|
2020-03-01 17:56:58 +02:00
|
|
|
|
}
|
2022-08-25 15:17:18 +03:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
2023-04-30 19:39:41 +03:00
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
|
if let Some(filename) = filename.as_ref() {
|
2020-11-28 14:44:50 +02:00
|
|
|
|
format!(
|
2023-04-30 19:39:41 +03:00
|
|
|
|
"Couldn't find a default application for file {} \
|
|
|
|
|
(type {})",
|
|
|
|
|
filename, attachment_type
|
2020-11-28 14:44:50 +02:00
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
format!(
|
|
|
|
|
"Couldn't find a default application for type {}",
|
|
|
|
|
attachment_type
|
|
|
|
|
)
|
2023-04-30 19:39:41 +03:00
|
|
|
|
},
|
|
|
|
|
),
|
2019-07-31 13:29:55 +03:00
|
|
|
|
));
|
2019-05-12 15:10:08 +03:00
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
ContentType::OctetStream {
|
|
|
|
|
ref name,
|
|
|
|
|
parameters: _,
|
|
|
|
|
} => {
|
2022-08-25 15:17:18 +03:00
|
|
|
|
context.replies.push_back(UIEvent::StatusEvent(
|
2023-06-14 12:24:20 +03:00
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
|
"Failed to open {}. application/octet-stream isn't supported \
|
|
|
|
|
yet",
|
|
|
|
|
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
|
|
|
|
|
)),
|
2022-08-25 15:17:18 +03:00
|
|
|
|
));
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2022-08-25 15:17:18 +03:00
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-23 15:36:52 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Url)
|
|
|
|
|
&& shortcut!(
|
|
|
|
|
key == shortcuts[Shortcuts::ENVELOPE_VIEW]["toggle_expand_headers"]
|
|
|
|
|
) =>
|
|
|
|
|
{
|
|
|
|
|
self.view_settings.expand_headers = !self.view_settings.expand_headers;
|
|
|
|
|
self.set_dirty(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if !self.cmd_buf.is_empty()
|
|
|
|
|
&& self.mode == ViewMode::Url
|
|
|
|
|
&& shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["go_to_url"]) =>
|
2018-08-18 17:50:31 +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));
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let body_text = &self.body_text;
|
|
|
|
|
let links = &self.links;
|
|
|
|
|
let (_kind, url) = {
|
|
|
|
|
if let Some(l) = links
|
|
|
|
|
.get(lidx)
|
|
|
|
|
.and_then(|l| Some((l.kind, body_text.get(l.start..l.end)?)))
|
|
|
|
|
{
|
|
|
|
|
l
|
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!("Link `{}` not found.", lidx)),
|
|
|
|
|
));
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
let url_launcher = self.view_settings.url_launcher.as_deref().unwrap_or(
|
2022-08-25 15:17:18 +03:00
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
"open"
|
|
|
|
|
},
|
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
|
|
|
{
|
|
|
|
|
"xdg-open"
|
|
|
|
|
},
|
|
|
|
|
);
|
2021-09-16 16:43:43 +03:00
|
|
|
|
match Command::new(url_launcher)
|
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
|
|
|
|
{
|
2023-06-14 12:24:20 +03:00
|
|
|
|
Ok(child) => {
|
|
|
|
|
context.children.push(child);
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
context.replies.push_back(UIEvent::Notification(
|
|
|
|
|
Some(format!("Failed to launch {:?}", url_launcher)),
|
|
|
|
|
err.to_string(),
|
|
|
|
|
Some(NotificationType::Error(melib::ErrorKind::External)),
|
|
|
|
|
));
|
|
|
|
|
}
|
2020-03-01 17:56:58 +02:00
|
|
|
|
}
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Url)
|
|
|
|
|
&& shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["toggle_url_mode"]) =>
|
|
|
|
|
{
|
2018-08-18 17:50:31 +03:00
|
|
|
|
match self.mode {
|
|
|
|
|
ViewMode::Normal => self.mode = ViewMode::Url,
|
|
|
|
|
ViewMode::Url => self.mode = ViewMode::Normal,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.initialised = false;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
self.dirty = true;
|
2018-08-23 14:39:54 +03:00
|
|
|
|
return true;
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::Input(ref key)
|
|
|
|
|
if shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["change_charset"]) =>
|
|
|
|
|
{
|
2023-04-10 11:42:50 +03:00
|
|
|
|
let entries = vec![
|
|
|
|
|
(None, "default".to_string()),
|
|
|
|
|
(Some(Charset::Ascii), Charset::Ascii.to_string()),
|
|
|
|
|
(Some(Charset::UTF8), Charset::UTF8.to_string()),
|
|
|
|
|
(Some(Charset::UTF16), Charset::UTF16.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_1), Charset::ISO8859_1.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_2), Charset::ISO8859_2.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_3), Charset::ISO8859_3.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_4), Charset::ISO8859_4.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_5), Charset::ISO8859_5.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_6), Charset::ISO8859_6.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_7), Charset::ISO8859_7.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_8), Charset::ISO8859_8.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_10), Charset::ISO8859_10.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_13), Charset::ISO8859_13.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_14), Charset::ISO8859_14.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_15), Charset::ISO8859_15.to_string()),
|
|
|
|
|
(Some(Charset::ISO8859_16), Charset::ISO8859_16.to_string()),
|
|
|
|
|
(Some(Charset::Windows1250), Charset::Windows1250.to_string()),
|
|
|
|
|
(Some(Charset::Windows1251), Charset::Windows1251.to_string()),
|
|
|
|
|
(Some(Charset::Windows1252), Charset::Windows1252.to_string()),
|
|
|
|
|
(Some(Charset::Windows1253), Charset::Windows1253.to_string()),
|
|
|
|
|
(Some(Charset::GBK), Charset::GBK.to_string()),
|
|
|
|
|
(Some(Charset::GB2312), Charset::GB2312.to_string()),
|
|
|
|
|
(Some(Charset::GB18030), Charset::GB18030.to_string()),
|
|
|
|
|
(Some(Charset::BIG5), Charset::BIG5.to_string()),
|
|
|
|
|
(Some(Charset::ISO2022JP), Charset::ISO2022JP.to_string()),
|
|
|
|
|
(Some(Charset::EUCJP), Charset::EUCJP.to_string()),
|
|
|
|
|
(Some(Charset::KOI8R), Charset::KOI8R.to_string()),
|
|
|
|
|
(Some(Charset::KOI8U), Charset::KOI8U.to_string()),
|
|
|
|
|
];
|
|
|
|
|
self.force_charset = ForceCharset::Dialog(Box::new(Selector::new(
|
|
|
|
|
"select charset to force",
|
|
|
|
|
entries,
|
|
|
|
|
true,
|
|
|
|
|
Some(Box::new(
|
|
|
|
|
move |id: ComponentId, results: &[Option<Charset>]| {
|
|
|
|
|
Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec())))
|
|
|
|
|
},
|
|
|
|
|
)),
|
|
|
|
|
context,
|
|
|
|
|
)));
|
|
|
|
|
self.dirty = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2023-06-14 12:24:20 +03:00
|
|
|
|
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
|
|
|
|
|
if self.active_jobs.contains(job_id) =>
|
|
|
|
|
{
|
|
|
|
|
let mut caught = false;
|
|
|
|
|
for d in self.display.iter_mut() {
|
|
|
|
|
match d {
|
|
|
|
|
AttachmentDisplay::SignedPending {
|
|
|
|
|
ref mut inner,
|
|
|
|
|
handle,
|
|
|
|
|
display,
|
|
|
|
|
job_id: our_job_id,
|
|
|
|
|
} if *our_job_id == *job_id => {
|
|
|
|
|
caught = true;
|
|
|
|
|
match handle.chan.try_recv() {
|
|
|
|
|
Err(_) => { /* Job was canceled */ }
|
|
|
|
|
Ok(None) => { /* something happened,
|
|
|
|
|
* perhaps a worker thread
|
|
|
|
|
* panicked */
|
|
|
|
|
}
|
|
|
|
|
Ok(Some(Ok(()))) => {
|
|
|
|
|
*d = AttachmentDisplay::SignedVerified {
|
|
|
|
|
inner: std::mem::replace(
|
|
|
|
|
inner,
|
|
|
|
|
Box::new(AttachmentBuilder::new(&[]).build()),
|
|
|
|
|
),
|
|
|
|
|
display: std::mem::take(display),
|
|
|
|
|
description: String::new(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
Ok(Some(Err(error))) => {
|
|
|
|
|
*d = AttachmentDisplay::SignedFailed {
|
|
|
|
|
inner: std::mem::replace(
|
|
|
|
|
inner,
|
|
|
|
|
Box::new(AttachmentBuilder::new(&[]).build()),
|
|
|
|
|
),
|
|
|
|
|
display: std::mem::take(display),
|
|
|
|
|
error,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AttachmentDisplay::EncryptedPending {
|
|
|
|
|
ref mut inner,
|
|
|
|
|
handle,
|
|
|
|
|
} if handle.job_id == *job_id => {
|
|
|
|
|
caught = true;
|
|
|
|
|
match handle.chan.try_recv() {
|
|
|
|
|
Err(_) => { /* Job was canceled */ }
|
|
|
|
|
Ok(None) => { /* something happened,
|
|
|
|
|
* perhaps a worker thread
|
|
|
|
|
* panicked */
|
|
|
|
|
}
|
|
|
|
|
Ok(Some(Ok((metadata, decrypted_bytes)))) => {
|
|
|
|
|
let plaintext =
|
|
|
|
|
Box::new(AttachmentBuilder::new(&decrypted_bytes).build());
|
|
|
|
|
let mut plaintext_display = vec![];
|
|
|
|
|
Self::attachment_to_display_helper(
|
|
|
|
|
&plaintext,
|
|
|
|
|
&self.main_loop_handler,
|
|
|
|
|
&mut self.active_jobs,
|
|
|
|
|
&mut plaintext_display,
|
|
|
|
|
&self.view_settings,
|
|
|
|
|
(&self.force_charset).into(),
|
|
|
|
|
);
|
|
|
|
|
*d = AttachmentDisplay::EncryptedSuccess {
|
|
|
|
|
inner: std::mem::replace(
|
|
|
|
|
inner,
|
|
|
|
|
Box::new(AttachmentBuilder::new(&[]).build()),
|
|
|
|
|
),
|
|
|
|
|
plaintext,
|
|
|
|
|
plaintext_display,
|
|
|
|
|
description: format!("{:?}", metadata),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
Ok(Some(Err(error))) => {
|
|
|
|
|
*d = AttachmentDisplay::EncryptedFailed {
|
|
|
|
|
inner: std::mem::replace(
|
|
|
|
|
inner,
|
|
|
|
|
Box::new(AttachmentBuilder::new(&[]).build()),
|
|
|
|
|
),
|
|
|
|
|
error,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if caught {
|
|
|
|
|
self.links.clear();
|
2023-07-13 16:51:37 +03:00
|
|
|
|
self.regenerate_body_text();
|
|
|
|
|
self.initialised = false;
|
|
|
|
|
self.set_dirty(true);
|
2023-06-14 12:24:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.active_jobs.remove(job_id);
|
|
|
|
|
}
|
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
|
|
|
|
}
|
2023-04-10 11:42:50 +03:00
|
|
|
|
|
2023-06-14 12:24:20 +03:00
|
|
|
|
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
|
|
|
|
let mut map = if let Some(ref sbv) = self.subview {
|
|
|
|
|
sbv.shortcuts(context)
|
|
|
|
|
} else {
|
|
|
|
|
self.pager.shortcuts(context)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut our_map = self.view_settings.env_view_shortcuts.clone();
|
|
|
|
|
|
|
|
|
|
if !(self.mode.is_attachment()
|
|
|
|
|
|| self.mode == ViewMode::Subview
|
|
|
|
|
|| self.mode == ViewMode::Source(Source::Decoded)
|
|
|
|
|
|| self.mode == ViewMode::Source(Source::Raw)
|
|
|
|
|
|| self.mode == ViewMode::Url)
|
|
|
|
|
{
|
|
|
|
|
our_map.remove("return_to_normal_view");
|
|
|
|
|
}
|
|
|
|
|
if self.mode != ViewMode::Url {
|
|
|
|
|
our_map.remove("go_to_url");
|
|
|
|
|
}
|
|
|
|
|
if !(self.mode == ViewMode::Normal || self.mode == ViewMode::Url) {
|
|
|
|
|
our_map.remove("toggle_url_mode");
|
|
|
|
|
}
|
|
|
|
|
map.insert(Shortcuts::ENVELOPE_VIEW, our_map);
|
|
|
|
|
|
|
|
|
|
map
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-18 17:50:31 +03:00
|
|
|
|
fn is_dirty(&self) -> bool {
|
|
|
|
|
self.dirty
|
2023-06-14 12:24:20 +03:00
|
|
|
|
|| self.pager.is_dirty()
|
2018-08-18 17:50:31 +03:00
|
|
|
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
2023-04-10 11:42:50 +03:00
|
|
|
|
|| matches!(self.force_charset, ForceCharset::Dialog(ref s) if s.is_dirty())
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|
2023-04-10 11:42:50 +03:00
|
|
|
|
|
2019-12-14 18:50:05 +02:00
|
|
|
|
fn set_dirty(&mut self, value: bool) {
|
|
|
|
|
self.dirty = value;
|
2023-06-14 12:24:20 +03:00
|
|
|
|
self.pager.set_dirty(value);
|
|
|
|
|
if let Some(ref mut s) = self.subview {
|
|
|
|
|
s.set_dirty(value);
|
|
|
|
|
}
|
|
|
|
|
if let ForceCharset::Dialog(ref mut s) = self.force_charset {
|
|
|
|
|
s.set_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))));
|
|
|
|
|
}
|
2018-08-18 17:50:31 +03:00
|
|
|
|
}
|