diff --git a/docs/meli.conf.5 b/docs/meli.conf.5 index fb9aa087..36bf04f7 100644 --- a/docs/meli.conf.5 +++ b/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 diff --git a/melib/src/email/attachment_types.rs b/melib/src/email/attachment_types.rs index be1df1d7..5bf7cb0d 100644 --- a/melib/src/email/attachment_types.rs +++ b/melib/src/email/attachment_types.rs @@ -564,3 +564,12 @@ impl From<&[u8]> for ContentDisposition { .unwrap_or_default() } } + +impl From for ContentDisposition { + fn from(kind: ContentDispositionKind) -> ContentDisposition { + ContentDisposition { + kind, + ..ContentDisposition::default() + } + } +} diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index a163e879..0dcc2a58 100644 --- a/src/components/mail/compose.rs +++ b/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(); diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 2cc9c679..2a964334 100644 --- a/src/components/mail/view.rs +++ b/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, + env: Envelope, body: Attachment, display: Vec, 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::() { + 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, } => { diff --git a/src/conf.rs b/src/conf.rs index 2c4760ca..81d0fdfb 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -593,6 +593,10 @@ mod default_vals { pub(in crate::conf) fn internal_value_true>() -> T { super::ToggleFlag::InternalVal(true).into() } + + pub(in crate::conf) fn ask>() -> T { + super::ToggleFlag::Ask.into() + } } mod deserializers { diff --git a/src/conf/composing.rs b/src/conf/composing.rs index 970075fc..10408682 100644 --- a/src/conf/composing.rs +++ b/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, } } } diff --git a/src/conf/overrides.rs b/src/conf/overrides.rs index 61822b22..24336b76 100644 --- a/src/conf/overrides.rs +++ b/src/conf/overrides.rs @@ -312,6 +312,11 @@ pub struct ComposingSettingsOverride { #[doc = " Default: true"] #[serde(default)] pub attribution_use_posix_locale: Option, + #[doc = " Forward emails as attachment? (Alternative is inline)"] + #[doc = " Default: ask"] + #[serde(alias = "forward-as-attachment")] + #[serde(default)] + pub forward_as_attachment: Option, } 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, } } } diff --git a/src/conf/shortcuts.rs b/src/conf/shortcuts.rs index 6189d8d1..245ab318 100644 --- a/src/conf/shortcuts.rs +++ b/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'), diff --git a/src/state.rs b/src/state.rs index f3e17fb5..b58d3c1f 100644 --- a/src/state.rs +++ b/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();