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 #120pull/144/head
parent
e090c31f96
commit
505adca54d
|
@ -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.
|
Whether the strftime call for the attribution string uses the POSIX locale instead of the user's active locale.
|
||||||
.\" default value
|
.\" default value
|
||||||
.Pq Em true
|
.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
|
.El
|
||||||
.Sh SHORTCUTS
|
.Sh SHORTCUTS
|
||||||
Shortcuts can take the following values:
|
Shortcuts can take the following values:
|
||||||
|
@ -751,6 +756,18 @@ View raw envelope source in a pager.
|
||||||
Reply to envelope.
|
Reply to envelope.
|
||||||
.\" default value
|
.\" default value
|
||||||
.Pq Em R
|
.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
|
.It Ic edit
|
||||||
Open envelope in composer.
|
Open envelope in composer.
|
||||||
.\" default value
|
.\" default value
|
||||||
|
|
|
@ -564,3 +564,12 @@ impl From<&[u8]> for ContentDisposition {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ContentDispositionKind> for ContentDisposition {
|
||||||
|
fn from(kind: ContentDispositionKind) -> ContentDisposition {
|
||||||
|
ContentDisposition {
|
||||||
|
kind,
|
||||||
|
..ContentDisposition::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -408,6 +408,55 @@ impl Composer {
|
||||||
Composer::reply_to(coordinates, reply_body, context, true)
|
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) {
|
pub fn set_draft(&mut self, draft: Draft) {
|
||||||
self.draft = draft;
|
self.draft = draft;
|
||||||
self.update_form();
|
self.update_form();
|
||||||
|
|
|
@ -168,11 +168,13 @@ pub struct MailView {
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum PendingReplyAction {
|
pub enum PendingReplyAction {
|
||||||
Reply,
|
Reply,
|
||||||
ReplyToAuthor,
|
ReplyToAuthor,
|
||||||
ReplyToAll,
|
ReplyToAll,
|
||||||
|
ForwardAttachment,
|
||||||
|
ForwardInline,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -189,6 +191,7 @@ enum MailViewState {
|
||||||
},
|
},
|
||||||
Loaded {
|
Loaded {
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
|
env: Envelope,
|
||||||
body: Attachment,
|
body: Attachment,
|
||||||
display: Vec<AttachmentDisplay>,
|
display: Vec<AttachmentDisplay>,
|
||||||
body_text: String,
|
body_text: String,
|
||||||
|
@ -310,6 +313,8 @@ impl MailView {
|
||||||
.get_env_mut(self.coordinates.2)
|
.get_env_mut(self.coordinates.2)
|
||||||
.populate_headers(&bytes);
|
.populate_headers(&bytes);
|
||||||
}
|
}
|
||||||
|
let env =
|
||||||
|
account.collection.get_env(self.coordinates.2).clone();
|
||||||
let body = AttachmentBuilder::new(&bytes).build();
|
let body = AttachmentBuilder::new(&bytes).build();
|
||||||
let display = Self::attachment_to(
|
let display = Self::attachment_to(
|
||||||
&body,
|
&body,
|
||||||
|
@ -325,6 +330,7 @@ impl MailView {
|
||||||
self.attachment_displays_to_text(&display, context, true);
|
self.attachment_displays_to_text(&display, context, true);
|
||||||
self.state = MailViewState::Loaded {
|
self.state = MailViewState::Loaded {
|
||||||
display,
|
display,
|
||||||
|
env,
|
||||||
body,
|
body,
|
||||||
bytes,
|
bytes,
|
||||||
body_text,
|
body_text,
|
||||||
|
@ -388,7 +394,7 @@ impl MailView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_action(&mut self, action: PendingReplyAction, context: &mut Context) {
|
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 {
|
MailViewState::Init {
|
||||||
ref mut pending_action,
|
ref mut pending_action,
|
||||||
..
|
..
|
||||||
|
@ -402,9 +408,16 @@ impl MailView {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MailViewState::Loaded { ref display, .. } => {
|
MailViewState::Loaded {
|
||||||
self.attachment_displays_to_text(&display, context, false)
|
ref bytes,
|
||||||
}
|
ref display,
|
||||||
|
ref env,
|
||||||
|
..
|
||||||
|
} => (
|
||||||
|
bytes,
|
||||||
|
self.attachment_displays_to_text(&display, context, false),
|
||||||
|
env,
|
||||||
|
),
|
||||||
MailViewState::Error { .. } => {
|
MailViewState::Error { .. } => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -425,6 +438,20 @@ impl MailView {
|
||||||
reply_body,
|
reply_body,
|
||||||
context,
|
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
|
context
|
||||||
|
@ -1737,6 +1764,10 @@ impl Component for MailView {
|
||||||
.get_env_mut(self.coordinates.2)
|
.get_env_mut(self.coordinates.2)
|
||||||
.populate_headers(&bytes);
|
.populate_headers(&bytes);
|
||||||
}
|
}
|
||||||
|
let env = context.accounts[&self.coordinates.0]
|
||||||
|
.collection
|
||||||
|
.get_env(self.coordinates.2)
|
||||||
|
.clone();
|
||||||
let body = AttachmentBuilder::new(&bytes).build();
|
let body = AttachmentBuilder::new(&bytes).build();
|
||||||
let display = Self::attachment_to(
|
let display = Self::attachment_to(
|
||||||
&body,
|
&body,
|
||||||
|
@ -1752,6 +1783,7 @@ impl Component for MailView {
|
||||||
self.attachment_displays_to_text(&display, context, true);
|
self.attachment_displays_to_text(&display, context, true);
|
||||||
self.state = MailViewState::Loaded {
|
self.state = MailViewState::Loaded {
|
||||||
bytes,
|
bytes,
|
||||||
|
env,
|
||||||
body,
|
body,
|
||||||
display,
|
display,
|
||||||
links: vec![],
|
links: vec![],
|
||||||
|
@ -1907,6 +1939,53 @@ impl Component for MailView {
|
||||||
self.perform_action(PendingReplyAction::ReplyToAuthor, context);
|
self.perform_action(PendingReplyAction::ReplyToAuthor, context);
|
||||||
return true;
|
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)
|
UIEvent::Input(ref key)
|
||||||
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["edit"]) =>
|
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["edit"]) =>
|
||||||
{
|
{
|
||||||
|
@ -2227,6 +2306,7 @@ impl Component for MailView {
|
||||||
body: _,
|
body: _,
|
||||||
bytes: _,
|
bytes: _,
|
||||||
display: _,
|
display: _,
|
||||||
|
env: _,
|
||||||
ref body_text,
|
ref body_text,
|
||||||
ref links,
|
ref links,
|
||||||
} => {
|
} => {
|
||||||
|
|
|
@ -593,6 +593,10 @@ mod default_vals {
|
||||||
pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T {
|
pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T {
|
||||||
super::ToggleFlag::InternalVal(true).into()
|
super::ToggleFlag::InternalVal(true).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::conf) fn ask<T: std::convert::From<super::ToggleFlag>>() -> T {
|
||||||
|
super::ToggleFlag::Ask.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod deserializers {
|
mod deserializers {
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//! Configuration for composing email.
|
//! 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;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Settings for writing and sending new e-mail
|
/// Settings for writing and sending new e-mail
|
||||||
|
@ -72,6 +73,10 @@ pub struct ComposingSettings {
|
||||||
/// Default: true
|
/// Default: true
|
||||||
#[serde(default = "true_val")]
|
#[serde(default = "true_val")]
|
||||||
pub attribution_use_posix_locale: bool,
|
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 {
|
impl Default for ComposingSettings {
|
||||||
|
@ -86,6 +91,7 @@ impl Default for ComposingSettings {
|
||||||
store_sent_mail: true,
|
store_sent_mail: true,
|
||||||
attribution_format_string: None,
|
attribution_format_string: None,
|
||||||
attribution_use_posix_locale: true,
|
attribution_use_posix_locale: true,
|
||||||
|
forward_as_attachment: ToggleFlag::Ask,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,6 +312,11 @@ pub struct ComposingSettingsOverride {
|
||||||
#[doc = " Default: true"]
|
#[doc = " Default: true"]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub attribution_use_posix_locale: Option<bool>,
|
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 {
|
impl Default for ComposingSettingsOverride {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -325,6 +330,7 @@ impl Default for ComposingSettingsOverride {
|
||||||
store_sent_mail: None,
|
store_sent_mail: None,
|
||||||
attribution_format_string: None,
|
attribution_format_string: None,
|
||||||
attribution_use_posix_locale: None,
|
attribution_use_posix_locale: None,
|
||||||
|
forward_as_attachment: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,6 +247,7 @@ shortcut_key_values! { "envelope-view",
|
||||||
reply |> "Reply to envelope." |> Key::Char('R'),
|
reply |> "Reply to envelope." |> Key::Char('R'),
|
||||||
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
|
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
|
||||||
reply_to_all |> "Reply to all/Reply to list/Follow up." |> Key::Ctrl('g'),
|
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'),
|
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_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
|
||||||
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
||||||
|
|
10
src/state.rs
10
src/state.rs
|
@ -1167,15 +1167,15 @@ impl State {
|
||||||
}
|
}
|
||||||
Some(false)
|
Some(false)
|
||||||
}
|
}
|
||||||
/// Switch back to the terminal's main screen (The command line the user sees before opening
|
/// Switch back to the terminal's main screen (The command line the user sees before opening
|
||||||
/// the application)
|
/// the application)
|
||||||
pub fn switch_to_main_screen(&mut self) {
|
pub fn switch_to_main_screen(&mut self) {
|
||||||
self.screen.switch_to_main_screen();
|
self.screen.switch_to_main_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch_to_alternate_screen(&mut self){
|
pub fn switch_to_alternate_screen(&mut self) {
|
||||||
self.screen.switch_to_alternate_screen(&mut self.context);
|
self.screen.switch_to_alternate_screen(&mut self.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
self.screen.flush();
|
self.screen.flush();
|
||||||
|
|
Loading…
Reference in New Issue