Add forward mail option

Forward email with shortcut 'forward' (default ctrl+f)

This opens a composing tab letting you to select receiver etc.

"composing" config setting "forward_as_attachment" selects the
forwarding behavior:

- "ask" asks you ever time
- true always forwards by attaching the entire email as a single
attachment
- false always forwards by inlining the email, like most email clients
do.

Closes #120
master
Manos Pitsidianakis 2021-10-02 13:38:50 +03:00
parent e090c31f96
commit 505adca54d
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
9 changed files with 183 additions and 11 deletions

View File

@ -520,6 +520,11 @@ with the replied envelope's date.
Whether the strftime call for the attribution string uses the POSIX locale instead of the user's active locale.
.\" default value
.Pq Em true
.It Ic forward_as_attachment Ar boolean or "ask"
.Pq Em optional
Forward emails as attachment? (Alternative is inline).
.\" default value
.Pq Em ask
.El
.Sh SHORTCUTS
Shortcuts can take the following values:
@ -751,6 +756,18 @@ View raw envelope source in a pager.
Reply to envelope.
.\" default value
.Pq Em R
.It Ic reply_to_author
Reply to author.
.\" default value
.Pq Em Ctrl-r
.It Ic reply_to_all
Reply to all/Reply to list/Follow up.
.\" default value
.Pq Em Ctrl-g
.It Ic forward
Forward email.
.\" default value
.Pq Em Ctrl-f
.It Ic edit
Open envelope in composer.
.\" default value

View File

@ -564,3 +564,12 @@ impl From<&[u8]> for ContentDisposition {
.unwrap_or_default()
}
}
impl From<ContentDispositionKind> for ContentDisposition {
fn from(kind: ContentDispositionKind) -> ContentDisposition {
ContentDisposition {
kind,
..ContentDisposition::default()
}
}
}

View File

@ -408,6 +408,55 @@ impl Composer {
Composer::reply_to(coordinates, reply_body, context, true)
}
pub fn forward(
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
bytes: &[u8],
env: &Envelope,
as_attachment: bool,
context: &mut Context,
) -> Self {
let mut composer = Composer::with_account(coordinates.0, context);
let mut draft: Draft = Draft::default();
draft.set_header("Subject", format!("Fwd: {}", env.subject()));
let preamble = format!(
r#"
---------- Forwarded message ---------
From: {}
Date: {}
Subject: {}
To: {}
"#,
env.field_from_to_string(),
env.date_as_str(),
env.subject(),
env.field_to_to_string()
);
if as_attachment {
let mut attachment = AttachmentBuilder::new(b"");
let mut disposition: ContentDisposition = ContentDispositionKind::Attachment.into();
{
disposition.filename = Some(format!("{}.eml", env.message_id_raw()));
}
attachment
.set_raw(bytes.to_vec())
.set_body_to_raw()
.set_content_type(ContentType::MessageRfc822)
.set_content_transfer_encoding(ContentTransferEncoding::_8Bit)
.set_content_disposition(disposition);
draft.attachments.push(attachment);
draft.body = preamble;
} else {
let content_type = ContentType::default();
let preamble: AttachmentBuilder =
Attachment::new(content_type, Default::default(), preamble.into_bytes()).into();
draft.attachments.push(preamble);
draft.attachments.push(env.body_bytes(bytes).into());
}
composer.set_draft(draft);
composer
}
pub fn set_draft(&mut self, draft: Draft) {
self.draft = draft;
self.update_form();

View File

@ -168,11 +168,13 @@ pub struct MailView {
id: ComponentId,
}
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub enum PendingReplyAction {
Reply,
ReplyToAuthor,
ReplyToAll,
ForwardAttachment,
ForwardInline,
}
#[derive(Debug)]
@ -189,6 +191,7 @@ enum MailViewState {
},
Loaded {
bytes: Vec<u8>,
env: Envelope,
body: Attachment,
display: Vec<AttachmentDisplay>,
body_text: String,
@ -310,6 +313,8 @@ impl MailView {
.get_env_mut(self.coordinates.2)
.populate_headers(&bytes);
}
let env =
account.collection.get_env(self.coordinates.2).clone();
let body = AttachmentBuilder::new(&bytes).build();
let display = Self::attachment_to(
&body,
@ -325,6 +330,7 @@ impl MailView {
self.attachment_displays_to_text(&display, context, true);
self.state = MailViewState::Loaded {
display,
env,
body,
bytes,
body_text,
@ -388,7 +394,7 @@ impl MailView {
}
fn perform_action(&mut self, action: PendingReplyAction, context: &mut Context) {
let reply_body = match self.state {
let (bytes, reply_body, env) = match self.state {
MailViewState::Init {
ref mut pending_action,
..
@ -402,9 +408,16 @@ impl MailView {
}
return;
}
MailViewState::Loaded { ref display, .. } => {
self.attachment_displays_to_text(&display, context, false)
}
MailViewState::Loaded {
ref bytes,
ref display,
ref env,
..
} => (
bytes,
self.attachment_displays_to_text(&display, context, false),
env,
),
MailViewState::Error { .. } => {
return;
}
@ -425,6 +438,20 @@ impl MailView {
reply_body,
context,
)),
PendingReplyAction::ForwardAttachment => Box::new(Composer::forward(
self.coordinates,
bytes,
env,
true,
context,
)),
PendingReplyAction::ForwardInline => Box::new(Composer::forward(
self.coordinates,
bytes,
env,
false,
context,
)),
};
context
@ -1737,6 +1764,10 @@ impl Component for MailView {
.get_env_mut(self.coordinates.2)
.populate_headers(&bytes);
}
let env = context.accounts[&self.coordinates.0]
.collection
.get_env(self.coordinates.2)
.clone();
let body = AttachmentBuilder::new(&bytes).build();
let display = Self::attachment_to(
&body,
@ -1752,6 +1783,7 @@ impl Component for MailView {
self.attachment_displays_to_text(&display, context, true);
self.state = MailViewState::Loaded {
bytes,
env,
body,
display,
links: vec![],
@ -1907,6 +1939,53 @@ impl Component for MailView {
self.perform_action(PendingReplyAction::ReplyToAuthor, context);
return true;
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["forward"]) =>
{
match mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1]
.composing
.forward_as_attachment
) {
f if f.is_ask() => {
let id = self.id;
context.replies.push_back(UIEvent::GlobalUIDialog(Box::new(
UIConfirmationDialog::new(
"How do you want the email to be forwarded?",
vec![
(true, "inline".to_string()),
(false, "as attachment".to_string()),
],
true,
Some(Box::new(move |_: ComponentId, result: bool| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(if result {
PendingReplyAction::ForwardInline
} else {
PendingReplyAction::ForwardAttachment
}),
))
})),
context,
),
)));
}
f if f.is_true() => {
self.perform_action(PendingReplyAction::ForwardAttachment, context);
}
_ => {
self.perform_action(PendingReplyAction::ForwardInline, context);
}
}
return true;
}
UIEvent::FinishedUIDialog(id, ref result) if id == self.id() => {
if let Some(result) = result.downcast_ref::<PendingReplyAction>() {
self.perform_action(*result, context);
}
return true;
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["edit"]) =>
{
@ -2227,6 +2306,7 @@ impl Component for MailView {
body: _,
bytes: _,
display: _,
env: _,
ref body_text,
ref links,
} => {

View File

@ -593,6 +593,10 @@ mod default_vals {
pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T {
super::ToggleFlag::InternalVal(true).into()
}
pub(in crate::conf) fn ask<T: std::convert::From<super::ToggleFlag>>() -> T {
super::ToggleFlag::Ask.into()
}
}
mod deserializers {

View File

@ -20,7 +20,8 @@
*/
//! Configuration for composing email.
use super::default_vals::{false_val, none, true_val};
use super::default_vals::{ask, false_val, none, true_val};
use melib::ToggleFlag;
use std::collections::HashMap;
/// Settings for writing and sending new e-mail
@ -72,6 +73,10 @@ pub struct ComposingSettings {
/// Default: true
#[serde(default = "true_val")]
pub attribution_use_posix_locale: bool,
/// Forward emails as attachment? (Alternative is inline)
/// Default: ask
#[serde(default = "ask", alias = "forward-as-attachment")]
pub forward_as_attachment: ToggleFlag,
}
impl Default for ComposingSettings {
@ -86,6 +91,7 @@ impl Default for ComposingSettings {
store_sent_mail: true,
attribution_format_string: None,
attribution_use_posix_locale: true,
forward_as_attachment: ToggleFlag::Ask,
}
}
}

View File

@ -312,6 +312,11 @@ pub struct ComposingSettingsOverride {
#[doc = " Default: true"]
#[serde(default)]
pub attribution_use_posix_locale: Option<bool>,
#[doc = " Forward emails as attachment? (Alternative is inline)"]
#[doc = " Default: ask"]
#[serde(alias = "forward-as-attachment")]
#[serde(default)]
pub forward_as_attachment: Option<ToggleFlag>,
}
impl Default for ComposingSettingsOverride {
fn default() -> Self {
@ -325,6 +330,7 @@ impl Default for ComposingSettingsOverride {
store_sent_mail: None,
attribution_format_string: None,
attribution_use_posix_locale: None,
forward_as_attachment: None,
}
}
}

View File

@ -247,6 +247,7 @@ shortcut_key_values! { "envelope-view",
reply |> "Reply to envelope." |> Key::Char('R'),
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
reply_to_all |> "Reply to all/Reply to list/Follow up." |> Key::Ctrl('g'),
forward |> "Forward email." |> Key::Ctrl('f'),
return_to_normal_view |> "Return to envelope if viewing raw source or attachment." |> Key::Char('r'),
toggle_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),

View File

@ -1167,15 +1167,15 @@ impl State {
}
Some(false)
}
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn switch_to_main_screen(&mut self) {
self.screen.switch_to_main_screen();
}
pub fn switch_to_alternate_screen(&mut self){
self.screen.switch_to_alternate_screen(&mut self.context);
}
pub fn switch_to_alternate_screen(&mut self) {
self.screen.switch_to_alternate_screen(&mut self.context);
}
fn flush(&mut self) {
self.screen.flush();