Add Reply{ToAuthor,ToAll} actions
- previous Reply action now lets you select recipients by default - ReplyToAuthor selects the Envelope author as recipient - ReplyToAll selects all addressesmemfd
parent
d95aae1987
commit
9928ee78e7
|
@ -947,6 +947,7 @@ dependencies = [
|
|||
"encoding",
|
||||
"flate2",
|
||||
"futures",
|
||||
"indexmap",
|
||||
"isahc",
|
||||
"libc",
|
||||
"libloading",
|
||||
|
|
|
@ -25,6 +25,7 @@ encoding = "0.2.33"
|
|||
memmap = { version = "0.5.2", optional = true }
|
||||
nom = { version = "5.1.1" }
|
||||
|
||||
indexmap = { version = "^1.5", features = ["serde-1", ] }
|
||||
notify = { version = "4.0.1", optional = true }
|
||||
xdg = "2.1.0"
|
||||
native-tls = { version ="0.2.3", optional=true }
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GroupAddress {
|
||||
|
@ -116,6 +118,12 @@ impl Address {
|
|||
.map(str::to_string)
|
||||
.collect::<_>()
|
||||
}
|
||||
|
||||
pub fn list_try_from(val: &str) -> Result<Vec<Address>> {
|
||||
Ok(parser::address::rfc2822address_list(val.as_bytes())?
|
||||
.1
|
||||
.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Address {}
|
||||
|
@ -139,6 +147,22 @@ impl PartialEq for Address {
|
|||
}
|
||||
}
|
||||
|
||||
impl Hash for Address {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Address::Mailbox(s) => {
|
||||
s.address_spec.display_bytes(&s.raw).hash(state);
|
||||
}
|
||||
Address::Group(s) => {
|
||||
s.display_name.display_bytes(&s.raw).hash(state);
|
||||
for sub in &s.mailbox_list {
|
||||
sub.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
|
@ -169,6 +193,13 @@ impl fmt::Debug for Address {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Address {
|
||||
type Error = MeliError;
|
||||
fn try_from(val: &str) -> Result<Address> {
|
||||
Ok(parser::address::address(val.as_bytes())?.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to return slices from a struct field on demand.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Copy)]
|
||||
pub struct StrBuilder {
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::backends::BackendOp;
|
|||
use crate::email::attachments::AttachmentBuilder;
|
||||
use crate::shellexpand::ShellExpandTrait;
|
||||
use data_encoding::BASE64_MIME;
|
||||
use std::collections::HashMap;
|
||||
use indexmap::IndexMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -39,8 +39,7 @@ use super::parser;
|
|||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Draft {
|
||||
pub headers: HashMap<String, String>,
|
||||
pub header_order: Vec<String>,
|
||||
pub headers: IndexMap<String, String>,
|
||||
pub body: String,
|
||||
|
||||
pub attachments: Vec<AttachmentBuilder>,
|
||||
|
@ -48,28 +47,19 @@ pub struct Draft {
|
|||
|
||||
impl Default for Draft {
|
||||
fn default() -> Self {
|
||||
let mut headers = HashMap::with_capacity_and_hasher(8, Default::default());
|
||||
let mut header_order = Vec::with_capacity(8);
|
||||
let mut headers = IndexMap::with_capacity_and_hasher(8, Default::default());
|
||||
headers.insert(
|
||||
"Date".into(),
|
||||
crate::datetime::timestamp_to_string(crate::datetime::now(), None),
|
||||
);
|
||||
headers.insert("From".into(), "".into());
|
||||
headers.insert("To".into(), "".into());
|
||||
headers.insert("Cc".into(), "".into());
|
||||
headers.insert("Bcc".into(), "".into());
|
||||
|
||||
headers.insert(
|
||||
"Date".into(),
|
||||
crate::datetime::timestamp_to_string(crate::datetime::now(), None),
|
||||
);
|
||||
headers.insert("Subject".into(), "".into());
|
||||
header_order.push("Date".into());
|
||||
header_order.push("From".into());
|
||||
header_order.push("To".into());
|
||||
header_order.push("Cc".into());
|
||||
header_order.push("Bcc".into());
|
||||
header_order.push("Subject".into());
|
||||
header_order.push("User-Agent".into());
|
||||
Draft {
|
||||
headers,
|
||||
header_order,
|
||||
body: String::new(),
|
||||
|
||||
attachments: Vec::new(),
|
||||
|
@ -88,35 +78,16 @@ impl str::FromStr for Draft {
|
|||
let mut ret = Draft::default();
|
||||
|
||||
for (k, v) in headers {
|
||||
if ignore_header(k) {
|
||||
continue;
|
||||
}
|
||||
if ret
|
||||
.headers
|
||||
.insert(
|
||||
String::from_utf8(k.to_vec())?,
|
||||
String::from_utf8(v.to_vec())?,
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
ret.header_order.push(String::from_utf8(k.to_vec())?);
|
||||
}
|
||||
ret.headers.insert(
|
||||
String::from_utf8(k.to_vec())?,
|
||||
String::from_utf8(v.to_vec())?,
|
||||
);
|
||||
}
|
||||
if ret.headers.contains_key("From") && !ret.headers.contains_key("Message-ID") {
|
||||
if let Ok((_, addr)) = super::parser::address::mailbox(ret.headers["From"].as_bytes()) {
|
||||
if let Some(fqdn) = addr.get_fqdn() {
|
||||
if ret
|
||||
.headers
|
||||
.insert("Message-ID".into(), random::gen_message_id(&fqdn))
|
||||
.is_none()
|
||||
{
|
||||
let pos = ret
|
||||
.header_order
|
||||
.iter()
|
||||
.position(|h| h == "Subject")
|
||||
.unwrap();
|
||||
ret.header_order.insert(pos, "Message-ID".into());
|
||||
}
|
||||
ret.headers
|
||||
.insert("Message-ID".into(), random::gen_message_id(&fqdn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,12 +107,7 @@ impl Draft {
|
|||
{
|
||||
let bytes = futures::executor::block_on(op.as_bytes()?)?;
|
||||
for (k, v) in envelope.headers(&bytes).unwrap_or_else(|_| Vec::new()) {
|
||||
if ignore_header(k.as_bytes()) {
|
||||
continue;
|
||||
}
|
||||
if ret.headers.insert(k.into(), v.into()).is_none() {
|
||||
ret.header_order.push(k.into());
|
||||
}
|
||||
ret.headers.insert(k.into(), v.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,12 +116,12 @@ impl Draft {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn set_header(&mut self, header: &str, value: String) {
|
||||
if self.headers.insert(header.to_string(), value).is_none() {
|
||||
self.header_order.push(header.to_string());
|
||||
}
|
||||
pub fn set_header(&mut self, header: &str, value: String) -> &mut Self {
|
||||
self.headers.insert(header.to_string(), value);
|
||||
self
|
||||
}
|
||||
pub fn new_reply(envelope: &Envelope, bytes: &[u8]) -> Self {
|
||||
|
||||
pub fn new_reply(envelope: &Envelope, bytes: &[u8], reply_to_all: bool) -> Self {
|
||||
let mut ret = Draft::default();
|
||||
ret.headers_mut().insert(
|
||||
"References".into(),
|
||||
|
@ -174,15 +140,32 @@ impl Draft {
|
|||
envelope.message_id_display()
|
||||
),
|
||||
);
|
||||
ret.header_order.push("References".into());
|
||||
ret.headers_mut()
|
||||
.insert("In-Reply-To".into(), envelope.message_id_display().into());
|
||||
ret.header_order.push("In-Reply-To".into());
|
||||
if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
||||
ret.headers_mut().insert("To".into(), reply_to.to_string());
|
||||
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
|
||||
// Mail-Reply-To/Reply-To/From for reply-to-author."
|
||||
// source: https://cr.yp.to/proto/replyto.html
|
||||
if reply_to_all {
|
||||
if let Some(reply_to) = envelope.other_headers().get("Mail-Followup-To") {
|
||||
ret.headers_mut().insert("To".into(), reply_to.to_string());
|
||||
} else {
|
||||
if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
||||
ret.headers_mut().insert("To".into(), reply_to.to_string());
|
||||
} else {
|
||||
ret.headers_mut()
|
||||
.insert("To".into(), envelope.field_from_to_string());
|
||||
}
|
||||
// FIXME: add To/Cc
|
||||
}
|
||||
} else {
|
||||
ret.headers_mut()
|
||||
.insert("To".into(), envelope.field_from_to_string());
|
||||
if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
|
||||
ret.headers_mut().insert("To".into(), reply_to.to_string());
|
||||
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
||||
ret.headers_mut().insert("To".into(), reply_to.to_string());
|
||||
} else {
|
||||
ret.headers_mut()
|
||||
.insert("To".into(), envelope.field_from_to_string());
|
||||
}
|
||||
}
|
||||
ret.headers_mut()
|
||||
.insert("Cc".into(), envelope.field_cc_to_string());
|
||||
|
@ -208,11 +191,11 @@ impl Draft {
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
|
||||
pub fn headers_mut(&mut self) -> &mut IndexMap<String, String> {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
pub fn headers(&self) -> &HashMap<String, String> {
|
||||
pub fn headers(&self) -> &IndexMap<String, String> {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
|
@ -228,15 +211,15 @@ impl Draft {
|
|||
&self.body
|
||||
}
|
||||
|
||||
pub fn set_body(&mut self, s: String) {
|
||||
pub fn set_body(&mut self, s: String) -> &mut Self {
|
||||
self.body = s;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> Result<String> {
|
||||
let mut ret = String::new();
|
||||
|
||||
for k in &self.header_order {
|
||||
let v = &self.headers[k];
|
||||
for (k, v) in &self.headers {
|
||||
ret.extend(format!("{}: {}\n", k, v).chars());
|
||||
}
|
||||
|
||||
|
@ -253,23 +236,12 @@ impl Draft {
|
|||
if let Ok((_, addr)) = super::parser::address::mailbox(self.headers["From"].as_bytes())
|
||||
{
|
||||
if let Some(fqdn) = addr.get_fqdn() {
|
||||
if self
|
||||
.headers
|
||||
.insert("Message-ID".into(), random::gen_message_id(&fqdn))
|
||||
.is_none()
|
||||
{
|
||||
let pos = self
|
||||
.header_order
|
||||
.iter()
|
||||
.position(|h| h == "Subject")
|
||||
.unwrap();
|
||||
self.header_order.insert(pos, "Message-ID".into());
|
||||
}
|
||||
self.headers
|
||||
.insert("Message-ID".into(), random::gen_message_id(&fqdn));
|
||||
}
|
||||
}
|
||||
}
|
||||
for k in &self.header_order {
|
||||
let v = &self.headers[k];
|
||||
for (k, v) in &self.headers {
|
||||
if v.is_ascii() {
|
||||
ret.extend(format!("{}: {}\n", k, v).chars());
|
||||
} else {
|
||||
|
@ -306,25 +278,6 @@ impl Draft {
|
|||
}
|
||||
}
|
||||
|
||||
fn ignore_header(header: &[u8]) -> bool {
|
||||
match header {
|
||||
b"From" => false,
|
||||
b"To" => false,
|
||||
b"Date" => false,
|
||||
b"Message-ID" => false,
|
||||
b"User-Agent" => false,
|
||||
b"Subject" => false,
|
||||
b"Reply-to" => false,
|
||||
b"Cc" => false,
|
||||
b"Bcc" => false,
|
||||
b"In-Reply-To" => false,
|
||||
b"References" => false,
|
||||
b"MIME-Version" => true,
|
||||
h if h.starts_with(b"X-") => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentBuilder>) {
|
||||
let boundary = ContentType::make_boundary(&parts);
|
||||
ret.extend(
|
||||
|
|
|
@ -132,6 +132,7 @@ pub use nom;
|
|||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
pub extern crate indexmap;
|
||||
extern crate uuid;
|
||||
pub use smallvec;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
|
||||
use crate::components::Component;
|
||||
use melib::backends::{AccountHash, MailboxHash};
|
||||
use melib::backends::AccountHash;
|
||||
pub use melib::thread::{SortField, SortOrder};
|
||||
use melib::{Draft, EnvelopeHash};
|
||||
|
||||
|
@ -60,7 +60,6 @@ pub enum ListingAction {
|
|||
pub enum TabAction {
|
||||
New(Option<Box<dyn Component>>),
|
||||
NewDraft(AccountHash, Option<Draft>),
|
||||
Reply((AccountHash, MailboxHash), EnvelopeHash), // thread coordinates (account, mailbox) and envelope
|
||||
Close,
|
||||
Edit(AccountHash, EnvelopeHash), // account_position, envelope hash
|
||||
Kill(Uuid),
|
||||
|
|
|
@ -26,7 +26,9 @@ use melib::Draft;
|
|||
use crate::conf::accounts::JobRequest;
|
||||
use crate::jobs::{JobChannel, JobId, JoinHandle};
|
||||
use crate::terminal::embed::EmbedGrid;
|
||||
use indexmap::IndexSet;
|
||||
use nix::sys::wait::WaitStatus;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use xdg_utils::query_mime_info;
|
||||
|
@ -66,7 +68,6 @@ impl std::ops::DerefMut for EmbedStatus {
|
|||
#[derive(Debug)]
|
||||
pub struct Composer {
|
||||
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
||||
reply_bytes_request: Option<(JobId, JobChannel<Vec<u8>>)>,
|
||||
account_hash: AccountHash,
|
||||
|
||||
cursor: Cursor,
|
||||
|
@ -92,7 +93,6 @@ impl Default for Composer {
|
|||
pager.set_reflow(text_processing::Reflow::FormatFlowed);
|
||||
Composer {
|
||||
reply_context: None,
|
||||
reply_bytes_request: None,
|
||||
account_hash: 0,
|
||||
|
||||
cursor: Cursor::Headers,
|
||||
|
@ -167,7 +167,6 @@ impl Composer {
|
|||
let _k = k.clone();
|
||||
ret.draft.headers_mut().insert(_k, v.into());
|
||||
} else {
|
||||
/* set_header() also updates draft's header_order field */
|
||||
ret.draft.set_header(h, v.into());
|
||||
}
|
||||
}
|
||||
|
@ -193,16 +192,148 @@ impl Composer {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn with_context(
|
||||
coordinates: (AccountHash, MailboxHash),
|
||||
msg: EnvelopeHash,
|
||||
pub fn reply_to(
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
bytes: &[u8],
|
||||
context: &mut Context,
|
||||
reply_to_all: bool,
|
||||
) -> Self {
|
||||
let mut ret = Composer::new(coordinates.0, context);
|
||||
let account = &context.accounts[&coordinates.0];
|
||||
let envelope = account.collection.get_env(coordinates.2);
|
||||
let subject = envelope.subject();
|
||||
ret.draft.headers_mut().insert(
|
||||
"Subject".into(),
|
||||
if !subject.starts_with("Re: ") {
|
||||
format!("Re: {}", subject)
|
||||
} else {
|
||||
subject.into()
|
||||
},
|
||||
);
|
||||
ret.draft.headers_mut().insert(
|
||||
"References".into(),
|
||||
format!(
|
||||
"{} {}",
|
||||
envelope
|
||||
.references()
|
||||
.iter()
|
||||
.fold(String::new(), |mut acc, x| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(' ');
|
||||
}
|
||||
acc.push_str(&x.to_string());
|
||||
acc
|
||||
}),
|
||||
envelope.message_id_display()
|
||||
),
|
||||
);
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("In-Reply-To".into(), envelope.message_id_display().into());
|
||||
|
||||
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
|
||||
// Mail-Reply-To/Reply-To/From for reply-to-author."
|
||||
// source: https://cr.yp.to/proto/replyto.html
|
||||
if reply_to_all {
|
||||
let mut to = IndexSet::new();
|
||||
|
||||
if let Some(actions) = list_management::ListActions::detect(&envelope) {
|
||||
if let Some(post) = actions.post {
|
||||
if let list_management::ListAction::Email(list_post_addr) = post[0] {
|
||||
if let Ok(list_address) =
|
||||
melib::email::parser::generic::mailto(list_post_addr)
|
||||
.map(|(_, m)| m.address)
|
||||
{
|
||||
to.insert(list_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(reply_to) = envelope
|
||||
.other_headers()
|
||||
.get("Mail-Followup-To")
|
||||
.and_then(|v| v.as_str().try_into().ok())
|
||||
{
|
||||
to.insert(reply_to);
|
||||
} else {
|
||||
if let Some(reply_to) = envelope
|
||||
.other_headers()
|
||||
.get("Reply-To")
|
||||
.and_then(|v| v.as_str().try_into().ok())
|
||||
{
|
||||
to.insert(reply_to);
|
||||
} else {
|
||||
to.extend(envelope.from().iter().cloned());
|
||||
}
|
||||
}
|
||||
to.extend(envelope.to().iter().cloned());
|
||||
if let Some(ours) = TryInto::<Address>::try_into(
|
||||
crate::components::mail::get_display_name(context, coordinates.0).as_str(),
|
||||
)
|
||||
.ok()
|
||||
{
|
||||
to.remove(&ours);
|
||||
}
|
||||
ret.draft.headers_mut().insert("To".into(), {
|
||||
let mut ret: String =
|
||||
to.into_iter()
|
||||
.fold(String::new(), |mut s: String, n: Address| {
|
||||
s.extend(n.to_string().chars());
|
||||
s.push_str(", ");
|
||||
s
|
||||
});
|
||||
ret.pop();
|
||||
ret.pop();
|
||||
ret
|
||||
});
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("Cc".into(), envelope.field_cc_to_string());
|
||||
} else {
|
||||
if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("To".into(), reply_to.to_string());
|
||||
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("To".into(), reply_to.to_string());
|
||||
} else {
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("To".into(), envelope.field_from_to_string());
|
||||
}
|
||||
}
|
||||
let body = envelope.body_bytes(bytes);
|
||||
ret.draft.body = {
|
||||
let reply_body_bytes = decode_rec(&body, None);
|
||||
let reply_body = String::from_utf8_lossy(&reply_body_bytes);
|
||||
let mut ret = format!(
|
||||
"On {} {} wrote:\n",
|
||||
envelope.date_as_str(),
|
||||
envelope.from()[0],
|
||||
);
|
||||
for l in reply_body.lines() {
|
||||
ret.push('>');
|
||||
ret.push_str(l);
|
||||
ret.push('\n');
|
||||
}
|
||||
ret
|
||||
};
|
||||
|
||||
ret.account_hash = coordinates.0;
|
||||
ret.reply_context = Some((coordinates.1, coordinates.2));
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn reply_to_select(
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
bytes: &[u8],
|
||||
context: &mut Context,
|
||||
) -> Self {
|
||||
let mut ret = Composer::reply_to(coordinates, bytes, context, false);
|
||||
let account = &context.accounts[&coordinates.0];
|
||||
let mut ret = Composer::default();
|
||||
ret.pager
|
||||
.set_colors(crate::conf::value(context, "theme_default"));
|
||||
let parent_message = account.collection.get_env(msg);
|
||||
let parent_message = account.collection.get_env(coordinates.2);
|
||||
/* If message is from a mailing list and we detect a List-Post header, ask user if they
|
||||
* want to reply to the mailing list or the submitter of the message */
|
||||
if let Some(actions) = list_management::ListActions::detect(&parent_message) {
|
||||
|
@ -240,69 +371,25 @@ impl Composer {
|
|||
}
|
||||
}
|
||||
}
|
||||
let subject = parent_message.subject();
|
||||
ret.draft.headers_mut().insert(
|
||||
"Subject".into(),
|
||||
if !subject.starts_with("Re: ") {
|
||||
format!("Re: {}", subject)
|
||||
} else {
|
||||
subject.into()
|
||||
},
|
||||
);
|
||||
|
||||
drop(parent_message);
|
||||
match context.accounts[&coordinates.0]
|
||||
.operation(msg)
|
||||
.and_then(|mut op| op.as_bytes())
|
||||
{
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
None,
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
Ok(fut) => {
|
||||
let (mut rcvr, handle, job_id) = context.accounts[&coordinates.0]
|
||||
.job_executor
|
||||
.spawn_specialized(fut);
|
||||
context.accounts[&coordinates.0]
|
||||
.active_jobs
|
||||
.insert(job_id, JobRequest::AsBytes(handle));
|
||||
if let Ok(Some(parent_bytes)) = try_recv_timeout!(&mut rcvr) {
|
||||
match parent_bytes {
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
None,
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
Ok(parent_bytes) => {
|
||||
let env_hash = msg;
|
||||
let parent_message = context.accounts[&coordinates.0]
|
||||
.collection
|
||||
.get_env(env_hash);
|
||||
let mut new_draft = Draft::new_reply(&parent_message, &parent_bytes);
|
||||
new_draft
|
||||
.headers_mut()
|
||||
.extend(ret.draft.headers_mut().drain());
|
||||
new_draft
|
||||
.attachments_mut()
|
||||
.extend(ret.draft.attachments_mut().drain(..));
|
||||
ret.set_draft(new_draft);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret.reply_bytes_request = Some((job_id, rcvr));
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.account_hash = coordinates.0;
|
||||
ret.reply_context = Some((coordinates.1, msg));
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn reply_to_author(
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
bytes: &[u8],
|
||||
context: &mut Context,
|
||||
) -> Self {
|
||||
Composer::reply_to(coordinates, bytes, context, false)
|
||||
}
|
||||
|
||||
pub fn reply_to_all(
|
||||
coordinates: (AccountHash, MailboxHash, EnvelopeHash),
|
||||
bytes: &[u8],
|
||||
context: &mut Context,
|
||||
) -> Self {
|
||||
Composer::reply_to(coordinates, bytes, context, true)
|
||||
}
|
||||
|
||||
pub fn set_draft(&mut self, draft: Draft) {
|
||||
self.draft = draft;
|
||||
self.update_form();
|
||||
|
@ -633,48 +720,6 @@ impl Component for Composer {
|
|||
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
let shortcuts = self.get_shortcuts(context);
|
||||
match (&mut self.mode, &mut event) {
|
||||
(_, UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)))
|
||||
if self
|
||||
.reply_bytes_request
|
||||
.as_ref()
|
||||
.map(|(j, _)| j == job_id)
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
let bytes = self
|
||||
.reply_bytes_request
|
||||
.take()
|
||||
.unwrap()
|
||||
.1
|
||||
.try_recv()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
match bytes {
|
||||
Ok(parent_bytes) => {
|
||||
let env_hash = self.reply_context.unwrap().1;
|
||||
let parent_message = context.accounts[&self.account_hash]
|
||||
.collection
|
||||
.get_env(env_hash);
|
||||
let mut new_draft = Draft::new_reply(&parent_message, &parent_bytes);
|
||||
new_draft
|
||||
.headers_mut()
|
||||
.extend(self.draft.headers_mut().drain());
|
||||
new_draft
|
||||
.attachments_mut()
|
||||
.extend(self.draft.attachments_mut().drain(..));
|
||||
self.set_draft(new_draft);
|
||||
self.set_dirty(true);
|
||||
self.initialized = false;
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some(format!("Failed to load parent envelope")),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
(ViewMode::Edit, _) => {
|
||||
if self.pager.process_event(event, context) {
|
||||
return true;
|
||||
|
|
|
@ -109,12 +109,22 @@ pub struct MailView {
|
|||
id: ComponentId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PendingReplyAction {
|
||||
Reply,
|
||||
ReplyToAuthor,
|
||||
ReplyToAll,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MailViewState {
|
||||
Init,
|
||||
Init {
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
LoadingBody {
|
||||
job_id: JobId,
|
||||
chan: oneshot::Receiver<Result<Vec<u8>>>,
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
Loaded {
|
||||
body: Result<Vec<u8>>,
|
||||
|
@ -123,7 +133,9 @@ pub enum MailViewState {
|
|||
|
||||
impl Default for MailViewState {
|
||||
fn default() -> Self {
|
||||
MailViewState::Init
|
||||
MailViewState::Init {
|
||||
pending_action: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,6 +197,7 @@ impl MailView {
|
|||
|
||||
fn init_futures(&mut self, context: &mut Context) {
|
||||
debug!("init_futures");
|
||||
let mut pending_action = None;
|
||||
let account = &mut context.accounts[&self.coordinates.0];
|
||||
if debug!(account.contains_key(self.coordinates.2)) {
|
||||
{
|
||||
|
@ -195,10 +208,22 @@ impl MailView {
|
|||
Ok(fut) => {
|
||||
let (mut chan, handle, job_id) =
|
||||
account.job_executor.spawn_specialized(fut);
|
||||
pending_action = if let MailViewState::Init {
|
||||
ref mut pending_action,
|
||||
} = self.state
|
||||
{
|
||||
pending_action.take()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Ok(Some(bytes_result)) = try_recv_timeout!(&mut chan) {
|
||||
self.state = MailViewState::Loaded { body: bytes_result };
|
||||
} else {
|
||||
self.state = MailViewState::LoadingBody { job_id, chan };
|
||||
self.state = MailViewState::LoadingBody {
|
||||
job_id,
|
||||
chan,
|
||||
pending_action: pending_action.take(),
|
||||
};
|
||||
self.active_jobs.insert(job_id);
|
||||
account.insert_job(job_id, JobRequest::AsBytes(handle));
|
||||
context
|
||||
|
@ -238,6 +263,46 @@ impl MailView {
|
|||
};
|
||||
}
|
||||
}
|
||||
if let Some(p) = pending_action {
|
||||
self.perform_action(p, context);
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_action(&mut self, action: PendingReplyAction, context: &mut Context) {
|
||||
let bytes = match self.state {
|
||||
MailViewState::Init {
|
||||
ref mut pending_action,
|
||||
..
|
||||
}
|
||||
| MailViewState::LoadingBody {
|
||||
ref mut pending_action,
|
||||
..
|
||||
} => {
|
||||
if pending_action.is_none() {
|
||||
*pending_action = Some(action);
|
||||
}
|
||||
return;
|
||||
}
|
||||
MailViewState::Loaded { body: Ok(ref b) } => b,
|
||||
MailViewState::Loaded { body: Err(_) } => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let composer = match action {
|
||||
PendingReplyAction::Reply => {
|
||||
Box::new(Composer::reply_to_select(self.coordinates, bytes, context))
|
||||
}
|
||||
PendingReplyAction::ReplyToAuthor => {
|
||||
Box::new(Composer::reply_to_author(self.coordinates, bytes, context))
|
||||
}
|
||||
PendingReplyAction::ReplyToAll => {
|
||||
Box::new(Composer::reply_to_all(self.coordinates, bytes, context))
|
||||
}
|
||||
};
|
||||
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::Action(Tab(New(Some(composer)))));
|
||||
}
|
||||
|
||||
/// Returns the string to be displayed in the Viewer
|
||||
|
@ -1027,12 +1092,13 @@ impl Component for MailView {
|
|||
MailViewState::LoadingBody {
|
||||
job_id: ref id,
|
||||
ref mut chan,
|
||||
pending_action: _,
|
||||
} if job_id == id => {
|
||||
let bytes_result = chan.try_recv().unwrap().unwrap();
|
||||
debug!("bytes_result");
|
||||
self.state = MailViewState::Loaded { body: bytes_result };
|
||||
}
|
||||
MailViewState::Init => {
|
||||
MailViewState::Init { .. } => {
|
||||
self.init_futures(context);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1053,10 +1119,19 @@ impl Component for MailView {
|
|||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["reply"]) =>
|
||||
{
|
||||
context.replies.push_back(UIEvent::Action(Tab(Reply(
|
||||
(self.coordinates.0, self.coordinates.1),
|
||||
self.coordinates.2,
|
||||
))));
|
||||
self.perform_action(PendingReplyAction::Reply, context);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["reply_to_all"]) =>
|
||||
{
|
||||
self.perform_action(PendingReplyAction::ReplyToAll, context);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["reply_to_author"]) =>
|
||||
{
|
||||
self.perform_action(PendingReplyAction::ReplyToAuthor, context);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
|
@ -1168,7 +1243,7 @@ impl Component for MailView {
|
|||
MailViewState::Loaded { .. } => {
|
||||
self.open_attachment(lidx, context, true);
|
||||
}
|
||||
MailViewState::Init => {
|
||||
MailViewState::Init { .. } => {
|
||||
self.init_futures(context);
|
||||
}
|
||||
}
|
||||
|
@ -1189,7 +1264,7 @@ impl Component for MailView {
|
|||
MailViewState::Loaded { .. } => {
|
||||
self.open_attachment(lidx, context, false);
|
||||
}
|
||||
MailViewState::Init => {
|
||||
MailViewState::Init { .. } => {
|
||||
self.init_futures(context);
|
||||
}
|
||||
}
|
||||
|
@ -1213,7 +1288,7 @@ impl Component for MailView {
|
|||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
||||
match self.state {
|
||||
MailViewState::Init => {
|
||||
MailViewState::Init { .. } => {
|
||||
self.init_futures(context);
|
||||
}
|
||||
MailViewState::LoadingBody { .. } => {}
|
||||
|
|
|
@ -1675,19 +1675,6 @@ impl Component for Tabbed {
|
|||
self.help_curr_views = children_maps;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(Tab(Reply(coordinates, msg))) => {
|
||||
self.add_component(Box::new(Composer::with_context(
|
||||
*coordinates,
|
||||
*msg,
|
||||
context,
|
||||
)));
|
||||
self.cursor_pos = self.children.len() - 1;
|
||||
self.children[self.cursor_pos].set_dirty(true);
|
||||
let mut children_maps = self.children[self.cursor_pos].get_shortcuts(context);
|
||||
children_maps.extend(self.get_shortcuts(context));
|
||||
self.help_curr_views = children_maps;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(Tab(Edit(account_hash, msg))) => {
|
||||
let composer = match Composer::edit(*account_hash, *msg, context) {
|
||||
Ok(c) => c,
|
||||
|
|
|
@ -238,6 +238,8 @@ shortcut_key_values! { "envelope-view",
|
|||
open_attachment |> "Opens selected attachment with xdg-open." |> Key::Char('a'),
|
||||
open_mailcap |> "Opens selected attachment according to its mailcap entry." |> Key::Char('m'),
|
||||
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'),
|
||||
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'),
|
||||
|
|
Loading…
Reference in New Issue