mail/compose: add configurable header preample suffix and prefix for editing
This commit adds a new configuration value for the composing section of settings. Quoting the documentation: wrap_header_preamble: Option<(String, String)> optional Wrap header preample when editing a draft in an editor. This allows you to write non-plain text email without the preamble creating syntax errors. They are stripped when you return from the editor. The values should be a two element array of strings, a prefix and suffix. This can be useful when for example you're writing Markdown; you can set the value to ["<!--",\ "-->"] which wraps the headers in an HTML comment.manpage-tutorial
parent
a484b397c6
commit
cbe593cf31
|
@ -512,7 +512,21 @@ Add meli User-Agent header in new drafts
|
|||
.\" default value
|
||||
.Pq Em true
|
||||
.It Ic default_header_values Ar hash table String[String]
|
||||
.Pq Em optional
|
||||
Default header values used when creating a new draft.
|
||||
.\" default value
|
||||
.Pq Em []
|
||||
.It Ic wrap_header_preamble Ar Option<(String, String)>
|
||||
.Pq Em optional
|
||||
Wrap header preample when editing a draft in an editor.
|
||||
This allows you to write non-plain text email without the preamble creating syntax errors.
|
||||
They are stripped when you return from the editor.
|
||||
The values should be a two element array of strings, a prefix and suffix.
|
||||
This can be useful when for example you're writing Markdown; you can set the value to
|
||||
.Em ["<!--",\ "-->"]
|
||||
which wraps the headers in an HTML comment.
|
||||
.\" default value
|
||||
.Pq Em None
|
||||
.It Ic store_sent_mail Ar boolean
|
||||
.Pq Em optional
|
||||
Store sent mail after successful submission.
|
||||
|
|
|
@ -30,7 +30,7 @@ use data_encoding::BASE64_MIME;
|
|||
use std::ffi::OsStr;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str;
|
||||
use std::str::FromStr;
|
||||
use xdg_utils::query_mime_info;
|
||||
|
||||
pub mod mime;
|
||||
|
@ -44,6 +44,7 @@ use super::parser;
|
|||
pub struct Draft {
|
||||
pub headers: HeaderMap,
|
||||
pub body: String,
|
||||
pub wrap_header_preamble: Option<(String, String)>,
|
||||
|
||||
pub attachments: Vec<AttachmentBuilder>,
|
||||
}
|
||||
|
@ -68,13 +69,14 @@ impl Default for Draft {
|
|||
Draft {
|
||||
headers,
|
||||
body: String::new(),
|
||||
wrap_header_preamble: None,
|
||||
|
||||
attachments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Draft {
|
||||
impl FromStr for Draft {
|
||||
type Err = MeliError;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if s.is_empty() {
|
||||
|
@ -114,6 +116,37 @@ impl Draft {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn set_wrap_header_preamble(&mut self, value: Option<(String, String)>) -> &mut Self {
|
||||
self.wrap_header_preamble = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update(&mut self, value: &str) -> Result<bool> {
|
||||
let mut value: std::borrow::Cow<'_, str> = value.into();
|
||||
if let Some((pre, post)) = self.wrap_header_preamble.as_ref() {
|
||||
let mut s = value.as_ref();
|
||||
s = s.strip_prefix(pre).unwrap_or(s);
|
||||
s = s.strip_prefix('\n').unwrap_or(s);
|
||||
|
||||
if let Some(pos) = s.find(post) {
|
||||
let mut headers = &s[..pos];
|
||||
headers = headers.strip_suffix(post).unwrap_or(headers);
|
||||
headers = headers.strip_suffix('\n').unwrap_or(headers);
|
||||
value = format!(
|
||||
"{headers}{body}",
|
||||
headers = headers,
|
||||
body = &s[pos + post.len()..]
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
let new = Draft::from_str(value.as_ref())?;
|
||||
let changes: bool = self.headers != new.headers || self.body != new.body;
|
||||
self.headers = new.headers;
|
||||
self.body = new.body;
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
pub fn new_reply(envelope: &Envelope, bytes: &[u8], reply_to_all: bool) -> Self {
|
||||
let mut ret = Draft::default();
|
||||
ret.headers_mut().insert(
|
||||
|
@ -217,17 +250,35 @@ impl Draft {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> Result<String> {
|
||||
pub fn to_edit_string(&self) -> String {
|
||||
let mut ret = String::new();
|
||||
|
||||
if let Some((pre, _)) = self.wrap_header_preamble.as_ref() {
|
||||
if !pre.is_empty() {
|
||||
ret.push_str(&pre);
|
||||
if !pre.ends_with('\n') {
|
||||
ret.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in self.headers.deref() {
|
||||
ret.push_str(&format!("{}: {}\n", k, v));
|
||||
}
|
||||
|
||||
if let Some((_, post)) = self.wrap_header_preamble.as_ref() {
|
||||
if !post.is_empty() {
|
||||
if !post.starts_with('\n') {
|
||||
ret.push('\n');
|
||||
}
|
||||
ret.push_str(&post);
|
||||
}
|
||||
}
|
||||
|
||||
ret.push('\n');
|
||||
ret.push_str(&self.body);
|
||||
|
||||
Ok(ret)
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn finalise(mut self) -> Result<String> {
|
||||
|
@ -415,27 +466,69 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
fn test_new_draft() {
|
||||
let mut default = Draft::default();
|
||||
assert_eq!(
|
||||
Draft::from_str(&default.to_string().unwrap()).unwrap(),
|
||||
default
|
||||
);
|
||||
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
||||
default.set_body("αδφαφσαφασ".to_string());
|
||||
assert_eq!(
|
||||
Draft::from_str(&default.to_string().unwrap()).unwrap(),
|
||||
default
|
||||
);
|
||||
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
||||
default.set_body("ascii only".to_string());
|
||||
assert_eq!(
|
||||
Draft::from_str(&default.to_string().unwrap()).unwrap(),
|
||||
default
|
||||
);
|
||||
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_draft_update() {
|
||||
let mut default = Draft::default();
|
||||
default
|
||||
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())))
|
||||
.set_body("αδφαφσαφασ".to_string())
|
||||
.set_header("Subject", "test_update()".into())
|
||||
.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
|
||||
|
||||
let original = default.clone();
|
||||
let s = default.to_edit_string();
|
||||
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\n-->\nαδφαφσαφασ");
|
||||
assert!(!default.update(&s).unwrap());
|
||||
assert_eq!(&original, &default);
|
||||
|
||||
default.set_wrap_header_preamble(Some(("".to_string(), "".to_string())));
|
||||
let original = default.clone();
|
||||
let s = default.to_edit_string();
|
||||
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
|
||||
assert!(!default.update(&s).unwrap());
|
||||
assert_eq!(&original, &default);
|
||||
|
||||
default.set_wrap_header_preamble(None);
|
||||
let original = default.clone();
|
||||
let s = default.to_edit_string();
|
||||
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
|
||||
assert!(!default.update(&s).unwrap());
|
||||
assert_eq!(&original, &default);
|
||||
|
||||
default.set_wrap_header_preamble(Some((
|
||||
"{-\n\n\n===========".to_string(),
|
||||
"</mixed>".to_string(),
|
||||
)));
|
||||
let original = default.clone();
|
||||
let s = default.to_edit_string();
|
||||
assert_eq!(s, "{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\n</mixed>\nαδφαφσαφασ");
|
||||
assert!(!default.update(&s).unwrap());
|
||||
assert_eq!(&original, &default);
|
||||
|
||||
default
|
||||
.set_body(
|
||||
"hellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n".to_string(),
|
||||
)
|
||||
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())));
|
||||
let original = default.clone();
|
||||
let s = default.to_edit_string();
|
||||
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\n-->\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n");
|
||||
assert!(!default.update(&s).unwrap());
|
||||
assert_eq!(&original, &default);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn test_attachments() {
|
||||
/*
|
||||
let mut default = Draft::default();
|
||||
default.set_body("αδφαφσαφασ".to_string());
|
||||
|
||||
|
@ -453,8 +546,8 @@ mod tests {
|
|||
.set_content_transfer_encoding(ContentTransferEncoding::Base64);
|
||||
default.attachments_mut().push(attachment);
|
||||
println!("{}", default.finalise().unwrap());
|
||||
*/
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Reads file from given path, and returns an 'application/octet-stream' AttachmentBuilder object
|
||||
|
|
|
@ -33,7 +33,6 @@ use std::convert::TryInto;
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
|
@ -725,13 +724,9 @@ To: {}
|
|||
|
||||
fn update_from_file(&mut self, file: File, context: &mut Context) -> bool {
|
||||
let result = file.read_to_string();
|
||||
match Draft::from_str(result.as_str()) {
|
||||
Ok(mut new_draft) => {
|
||||
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
|
||||
if self.draft != new_draft {
|
||||
self.has_changes = true;
|
||||
}
|
||||
self.draft = new_draft;
|
||||
match self.draft.update(result.as_str()) {
|
||||
Ok(has_changes) => {
|
||||
self.has_changes = has_changes;
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -1697,8 +1692,13 @@ impl Component for Composer {
|
|||
};
|
||||
/* update Draft's headers based on form values */
|
||||
self.update_draft();
|
||||
self.draft.set_wrap_header_preamble(
|
||||
account_settings!(context[self.account_hash].composing.wrap_header_preamble)
|
||||
.clone(),
|
||||
);
|
||||
|
||||
let f = create_temp_file(
|
||||
self.draft.to_string().unwrap().as_str().as_bytes(),
|
||||
self.draft.to_edit_string().as_str().as_bytes(),
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
|
@ -1766,13 +1766,9 @@ impl Component for Composer {
|
|||
}
|
||||
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
||||
let result = f.read_to_string();
|
||||
match Draft::from_str(result.as_str()) {
|
||||
Ok(mut new_draft) => {
|
||||
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
|
||||
if self.draft != new_draft {
|
||||
self.has_changes = true;
|
||||
}
|
||||
self.draft = new_draft;
|
||||
match self.draft.update(result.as_str()) {
|
||||
Ok(has_changes) => {
|
||||
self.has_changes = has_changes;
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
|
|
|
@ -54,6 +54,12 @@ pub struct ComposingSettings {
|
|||
/// Default: empty
|
||||
#[serde(default, alias = "default-header-values")]
|
||||
pub default_header_values: HashMap<String, String>,
|
||||
/// Wrap header preample when editing a draft in an editor. This allows you to write non-plain
|
||||
/// text email without the preamble creating syntax errors. They are stripped when you return
|
||||
/// from the editor. The values should be a two element array of strings, a prefix and suffix.
|
||||
/// Default: None
|
||||
#[serde(default, alias = "wrap-header-preample")]
|
||||
pub wrap_header_preamble: Option<(String, String)>,
|
||||
/// Store sent mail after successful submission. This setting is meant to be disabled for
|
||||
/// non-standard behaviour in gmail, which auto-saves sent mail on its own.
|
||||
/// Default: true
|
||||
|
@ -96,6 +102,7 @@ impl Default for ComposingSettings {
|
|||
insert_user_agent: true,
|
||||
default_header_values: HashMap::default(),
|
||||
store_sent_mail: true,
|
||||
wrap_header_preamble: None,
|
||||
attribution_format_string: None,
|
||||
attribution_use_posix_locale: true,
|
||||
forward_as_attachment: ToggleFlag::Ask,
|
||||
|
|
|
@ -299,6 +299,13 @@ pub struct ComposingSettingsOverride {
|
|||
#[serde(alias = "default-header-values")]
|
||||
#[serde(default)]
|
||||
pub default_header_values: Option<HashMap<String, String>>,
|
||||
#[doc = " Wrap header preample when editing a draft in an editor. This allows you to write non-plain"]
|
||||
#[doc = " text email without the preamble creating syntax errors. They are stripped when you return"]
|
||||
#[doc = " from the editor. The values should be a two element array of strings, a prefix and suffix."]
|
||||
#[doc = " Default: None"]
|
||||
#[serde(alias = "wrap-header-preample")]
|
||||
#[serde(default)]
|
||||
pub wrap_header_preamble: Option<Option<(String, String)>>,
|
||||
#[doc = " Store sent mail after successful submission. This setting is meant to be disabled for"]
|
||||
#[doc = " non-standard behaviour in gmail, which auto-saves sent mail on its own."]
|
||||
#[doc = " Default: true"]
|
||||
|
@ -342,6 +349,7 @@ impl Default for ComposingSettingsOverride {
|
|||
format_flowed: None,
|
||||
insert_user_agent: None,
|
||||
default_header_values: None,
|
||||
wrap_header_preamble: None,
|
||||
store_sent_mail: None,
|
||||
attribution_format_string: None,
|
||||
attribution_use_posix_locale: None,
|
||||
|
|
Loading…
Reference in New Issue