Browse Source

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
tags/alpha-0.7.2
Manos Pitsidianakis 3 weeks ago
parent
commit
505adca54d
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 17
      docs/meli.conf.5
  2. 9
      melib/src/email/attachment_types.rs
  3. 49
      src/components/mail/compose.rs
  4. 90
      src/components/mail/view.rs
  5. 4
      src/conf.rs
  6. 8
      src/conf/composing.rs
  7. 6
      src/conf/overrides.rs
  8. 1
      src/conf/shortcuts.rs
  9. 10
      src/state.rs

17
docs/meli.conf.5

@ -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

9
melib/src/email/attachment_types.rs

@ -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()
}
}
}

49
src/components/mail/compose.rs

@ -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();

90
src/components/mail/view.rs

@ -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![],
@ -1908,6 +1940,53 @@ impl Component for MailView {
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"]) =>
{
let account_hash = self.coordinates.0;
@ -2227,6 +2306,7 @@ impl Component for MailView {
body: _,
bytes: _,
display: _,
env: _,
ref body_text,
ref links,
} => {

4
src/conf.rs

@ -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 {

8
src/conf/composing.rs

@ -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,
}
}
}

6
src/conf/overrides.rs

@ -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,
}
}
}

1
src/conf/shortcuts.rs

@ -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'),

10
src/state.rs

@ -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();

Loading…
Cancel
Save