mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-24 20:10:23 +00:00
release v0.5.4 (#285)
* replace bsd3 license by bsd4 * add attachments with save and send commands (#284) * set up tpl save and send commands * improve msg save and send handlers * add vim msg#add_attachment fn * improve vim logs * update changelog * add attachment keybind vim doc * reverse range order fetch envelopes (#276) * bump version v0.5.4
This commit is contained in:
parent
0e452d8a47
commit
e33a9a72e9
13 changed files with 243 additions and 91 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.4] - 2022-02-05
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add attachments with save and send commands [#47] [#259]
|
||||
- Invalid sequence set [#276]
|
||||
|
||||
## [0.5.3] - 2022-02-03
|
||||
|
||||
### Added
|
||||
|
@ -273,7 +280,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Password from command [#22]
|
||||
- Set up README [#20]
|
||||
|
||||
[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.3...HEAD
|
||||
[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.4...HEAD
|
||||
[0.5.4]: https://github.com/soywod/himalaya/compare/v0.5.3...v0.5.4
|
||||
[0.5.3]: https://github.com/soywod/himalaya/compare/v0.5.2...v0.5.3
|
||||
[0.5.2]: https://github.com/soywod/himalaya/compare/v0.5.1...v0.5.2
|
||||
[0.5.1]: https://github.com/soywod/himalaya/compare/v0.5.0...v0.5.1
|
||||
|
@ -325,6 +333,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#39]: https://github.com/soywod/himalaya/issues/39
|
||||
[#40]: https://github.com/soywod/himalaya/issues/40
|
||||
[#41]: https://github.com/soywod/himalaya/issues/41
|
||||
[#47]: https://github.com/soywod/himalaya/issues/47
|
||||
[#48]: https://github.com/soywod/himalaya/issues/48
|
||||
[#50]: https://github.com/soywod/himalaya/issues/50
|
||||
[#58]: https://github.com/soywod/himalaya/issues/58
|
||||
|
@ -383,9 +392,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#228]: https://github.com/soywod/himalaya/issues/228
|
||||
[#229]: https://github.com/soywod/himalaya/issues/229
|
||||
[#249]: https://github.com/soywod/himalaya/issues/249
|
||||
[#259]: https://github.com/soywod/himalaya/issues/259
|
||||
[#268]: https://github.com/soywod/himalaya/issues/268
|
||||
[#272]: https://github.com/soywod/himalaya/issues/272
|
||||
[#273]: https://github.com/soywod/himalaya/issues/273
|
||||
[#276]: https://github.com/soywod/himalaya/issues/276
|
||||
[#271]: https://github.com/soywod/himalaya/issues/271
|
||||
[#276]: https://github.com/soywod/himalaya/issues/276
|
||||
[#280]: https://github.com/soywod/himalaya/issues/280
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -361,7 +361,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "himalaya"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "himalaya"
|
||||
description = "Command-line interface for email management"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
authors = ["soywod <clement.douin@posteo.net>"]
|
||||
edition = "2018"
|
||||
license-file = "LICENSE"
|
||||
|
|
44
LICENSE
44
LICENSE
|
@ -1,30 +1,32 @@
|
|||
Copyright © 2020,2021 soywod <clement.douin@posteo.net>
|
||||
Copyright (c) 2020-2021, soywod (Clément DOUIN) <clement.douin@posteo.net>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Author name here nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
3. All advertising materials mentioning features or use of this software must
|
||||
display the following acknowledgement:
|
||||
This product includes software developed by Clément DOUIN.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
4. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
@ -144,11 +144,11 @@ impl<'a> ImapServiceInterface<'a> for ImapService<'a> {
|
|||
let cursor = (page * page_size) as i64;
|
||||
let begin = 1.max(last_seq - cursor);
|
||||
let end = begin - begin.min(*page_size as i64) + 1;
|
||||
format!("{}:{}", begin, end)
|
||||
format!("{}:{}", end, begin)
|
||||
} else {
|
||||
String::from("1:*")
|
||||
};
|
||||
debug!("range: {:?}", range);
|
||||
debug!("range: {}", range);
|
||||
|
||||
let fetches = self
|
||||
.sess()?
|
||||
|
|
|
@ -23,7 +23,7 @@ type Raw = bool;
|
|||
type All = bool;
|
||||
type RawMsg<'a> = &'a str;
|
||||
type Query = String;
|
||||
type AttachmentsPaths<'a> = Vec<&'a str>;
|
||||
type AttachmentPaths<'a> = Vec<&'a str>;
|
||||
type MaxTableWidth = Option<usize>;
|
||||
|
||||
/// Message commands.
|
||||
|
@ -31,15 +31,15 @@ pub enum Command<'a> {
|
|||
Attachments(Seq<'a>),
|
||||
Copy(Seq<'a>, Mbox<'a>),
|
||||
Delete(Seq<'a>),
|
||||
Forward(Seq<'a>, AttachmentsPaths<'a>),
|
||||
Forward(Seq<'a>, AttachmentPaths<'a>),
|
||||
List(MaxTableWidth, Option<PageSize>, Page),
|
||||
Move(Seq<'a>, Mbox<'a>),
|
||||
Read(Seq<'a>, TextMime<'a>, Raw),
|
||||
Reply(Seq<'a>, All, AttachmentsPaths<'a>),
|
||||
Reply(Seq<'a>, All, AttachmentPaths<'a>),
|
||||
Save(RawMsg<'a>),
|
||||
Search(Query, MaxTableWidth, Option<PageSize>, Page),
|
||||
Send(RawMsg<'a>),
|
||||
Write(AttachmentsPaths<'a>),
|
||||
Write(AttachmentPaths<'a>),
|
||||
|
||||
Flag(Option<flag_arg::Command<'a>>),
|
||||
Tpl(Option<tpl_arg::Command<'a>>),
|
||||
|
@ -256,7 +256,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
|
|||
}
|
||||
|
||||
/// Message attachment argument.
|
||||
fn attachment_arg<'a>() -> Arg<'a, 'a> {
|
||||
pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("attachments")
|
||||
.help("Adds attachment to the message")
|
||||
.short("a")
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use anyhow::{Context, Result};
|
||||
use atty::Stream;
|
||||
use imap::types::Flag;
|
||||
use log::{debug, trace};
|
||||
use log::{debug, info, trace};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::{TryFrom, TryInto},
|
||||
|
@ -244,14 +244,25 @@ pub fn reply<
|
|||
imap.add_flags(seq, &flags)
|
||||
}
|
||||
|
||||
/// Save a raw message to the targetted mailbox.
|
||||
/// Saves a raw message to the targetted mailbox.
|
||||
pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
|
||||
mbox: &Mbox,
|
||||
raw_msg: &str,
|
||||
printer: &mut Printer,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() {
|
||||
info!("entering save message handler");
|
||||
|
||||
debug!("mailbox: {}", mbox);
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
debug!("flags: {}", flags);
|
||||
|
||||
let is_tty = atty::is(Stream::Stdin);
|
||||
debug!("is tty: {}", is_tty);
|
||||
let is_json = printer.is_json();
|
||||
debug!("is json: {}", is_json);
|
||||
|
||||
let raw_msg = if is_tty || is_json {
|
||||
raw_msg.replace("\r", "").replace("\n", "\r\n")
|
||||
} else {
|
||||
io::stdin()
|
||||
|
@ -261,8 +272,6 @@ pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
|
|||
.collect::<Vec<String>>()
|
||||
.join("\r\n")
|
||||
};
|
||||
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
imap.append_raw_msg_with_flags(mbox, raw_msg.as_bytes(), flags)
|
||||
}
|
||||
|
||||
|
@ -297,7 +306,19 @@ pub fn send<
|
|||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() {
|
||||
info!("entering send message handler");
|
||||
|
||||
let mbox = Mbox::new(&account.sent_folder);
|
||||
debug!("mailbox: {}", mbox);
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
debug!("flags: {}", flags);
|
||||
|
||||
let is_tty = atty::is(Stream::Stdin);
|
||||
debug!("is tty: {}", is_tty);
|
||||
let is_json = printer.is_json();
|
||||
debug!("is json: {}", is_json);
|
||||
|
||||
let raw_msg = if is_tty || is_json {
|
||||
raw_msg.replace("\r", "").replace("\n", "\r\n")
|
||||
} else {
|
||||
io::stdin()
|
||||
|
@ -307,15 +328,11 @@ pub fn send<
|
|||
.collect::<Vec<String>>()
|
||||
.join("\r\n")
|
||||
};
|
||||
trace!("raw message: {:?}", raw_msg);
|
||||
let envelope: lettre::address::Envelope = Msg::from_tpl(&raw_msg)?.try_into()?;
|
||||
trace!("envelope: {:?}", envelope);
|
||||
|
||||
let msg = Msg::from_tpl(&raw_msg)?;
|
||||
let envelope: lettre::address::Envelope = msg.try_into()?;
|
||||
smtp.send_raw_msg(&envelope, raw_msg.as_bytes())?;
|
||||
debug!("message sent!");
|
||||
|
||||
// Save message to sent folder
|
||||
let mbox = Mbox::new(&account.sent_folder);
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
use anyhow::Result;
|
||||
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use log::{debug, trace};
|
||||
use log::{debug, info, trace};
|
||||
|
||||
use crate::domain::msg::msg_arg;
|
||||
|
||||
type Seq<'a> = &'a str;
|
||||
type All = bool;
|
||||
type ReplyAll = bool;
|
||||
type AttachmentPaths<'a> = Vec<&'a str>;
|
||||
type Tpl<'a> = &'a str;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TplOverride<'a> {
|
||||
|
@ -23,69 +25,77 @@ pub struct TplOverride<'a> {
|
|||
pub sig: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ArgMatches<'a>> for TplOverride<'a> {
|
||||
fn from(matches: &'a ArgMatches<'a>) -> Self {
|
||||
Self {
|
||||
subject: matches.value_of("subject"),
|
||||
from: matches.values_of("from").map(|v| v.collect()),
|
||||
to: matches.values_of("to").map(|v| v.collect()),
|
||||
cc: matches.values_of("cc").map(|v| v.collect()),
|
||||
bcc: matches.values_of("bcc").map(|v| v.collect()),
|
||||
headers: matches.values_of("headers").map(|v| v.collect()),
|
||||
body: matches.value_of("body"),
|
||||
sig: matches.value_of("signature"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message template commands.
|
||||
pub enum Command<'a> {
|
||||
New(TplOverride<'a>),
|
||||
Reply(Seq<'a>, All, TplOverride<'a>),
|
||||
Reply(Seq<'a>, ReplyAll, TplOverride<'a>),
|
||||
Forward(Seq<'a>, TplOverride<'a>),
|
||||
Save(AttachmentPaths<'a>, Tpl<'a>),
|
||||
Send(AttachmentPaths<'a>, Tpl<'a>),
|
||||
}
|
||||
|
||||
/// Message template command matcher.
|
||||
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
||||
if let Some(m) = m.subcommand_matches("new") {
|
||||
debug!("new command matched");
|
||||
let tpl = TplOverride {
|
||||
subject: m.value_of("subject"),
|
||||
from: m.values_of("from").map(|v| v.collect()),
|
||||
to: m.values_of("to").map(|v| v.collect()),
|
||||
cc: m.values_of("cc").map(|v| v.collect()),
|
||||
bcc: m.values_of("bcc").map(|v| v.collect()),
|
||||
headers: m.values_of("headers").map(|v| v.collect()),
|
||||
body: m.value_of("body"),
|
||||
sig: m.value_of("signature"),
|
||||
};
|
||||
trace!(r#"template args: "{:?}""#, tpl);
|
||||
info!("new command matched");
|
||||
let tpl = TplOverride::from(m);
|
||||
trace!("template override: {:?}", tpl);
|
||||
return Ok(Some(Command::New(tpl)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("reply") {
|
||||
debug!("reply command matched");
|
||||
info!("reply command matched");
|
||||
let seq = m.value_of("seq").unwrap();
|
||||
trace!(r#"seq: "{}""#, seq);
|
||||
debug!("sequence: {}", seq);
|
||||
let all = m.is_present("reply-all");
|
||||
trace!("reply all: {}", all);
|
||||
let tpl = TplOverride {
|
||||
subject: m.value_of("subject"),
|
||||
from: m.values_of("from").map(|v| v.collect()),
|
||||
to: m.values_of("to").map(|v| v.collect()),
|
||||
cc: m.values_of("cc").map(|v| v.collect()),
|
||||
bcc: m.values_of("bcc").map(|v| v.collect()),
|
||||
headers: m.values_of("headers").map(|v| v.collect()),
|
||||
body: m.value_of("body"),
|
||||
sig: m.value_of("signature"),
|
||||
};
|
||||
trace!(r#"template args: "{:?}""#, tpl);
|
||||
debug!("reply all: {}", all);
|
||||
let tpl = TplOverride::from(m);
|
||||
trace!("template override: {:?}", tpl);
|
||||
return Ok(Some(Command::Reply(seq, all, tpl)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("forward") {
|
||||
debug!("forward command matched");
|
||||
info!("forward command matched");
|
||||
let seq = m.value_of("seq").unwrap();
|
||||
trace!(r#"seq: "{}""#, seq);
|
||||
let tpl = TplOverride {
|
||||
subject: m.value_of("subject"),
|
||||
from: m.values_of("from").map(|v| v.collect()),
|
||||
to: m.values_of("to").map(|v| v.collect()),
|
||||
cc: m.values_of("cc").map(|v| v.collect()),
|
||||
bcc: m.values_of("bcc").map(|v| v.collect()),
|
||||
headers: m.values_of("headers").map(|v| v.collect()),
|
||||
body: m.value_of("body"),
|
||||
sig: m.value_of("signature"),
|
||||
};
|
||||
trace!(r#"template args: "{:?}""#, tpl);
|
||||
debug!("sequence: {}", seq);
|
||||
let tpl = TplOverride::from(m);
|
||||
trace!("template args: {:?}", tpl);
|
||||
return Ok(Some(Command::Forward(seq, tpl)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("save") {
|
||||
info!("save command matched");
|
||||
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
|
||||
trace!("attachments paths: {:?}", attachment_paths);
|
||||
let tpl = m.value_of("template").unwrap_or_default();
|
||||
trace!("template: {}", tpl);
|
||||
return Ok(Some(Command::Save(attachment_paths, tpl)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("send") {
|
||||
info!("send command matched");
|
||||
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
|
||||
trace!("attachments paths: {:?}", attachment_paths);
|
||||
let tpl = m.value_of("template").unwrap_or_default();
|
||||
trace!("template: {}", tpl);
|
||||
return Ok(Some(Command::Send(attachment_paths, tpl)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -154,7 +164,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("reply")
|
||||
.aliases(&["rep", "r"])
|
||||
.aliases(&["rep", "re", "r"])
|
||||
.about("Generates a reply message template")
|
||||
.arg(msg_arg::seq_arg())
|
||||
.arg(msg_arg::reply_all_arg())
|
||||
|
@ -166,5 +176,17 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
.about("Generates a forward message template")
|
||||
.arg(msg_arg::seq_arg())
|
||||
.args(&tpl_args()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("save")
|
||||
.about("Saves a message based on the given template")
|
||||
.arg(&msg_arg::attachment_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send")
|
||||
.about("Sends a message based on the given template")
|
||||
.arg(&msg_arg::attachment_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)]
|
||||
}
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
//! This module gathers all message template commands.
|
||||
|
||||
use anyhow::Result;
|
||||
use atty::Stream;
|
||||
use imap::types::Flag;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, BufRead},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::Account,
|
||||
domain::{
|
||||
imap::ImapServiceInterface,
|
||||
msg::{Msg, TplOverride},
|
||||
Flags, Mbox, SmtpServiceInterface,
|
||||
},
|
||||
output::PrinterService,
|
||||
};
|
||||
|
@ -53,3 +60,59 @@ pub fn forward<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a
|
|||
.to_tpl(opts, account);
|
||||
printer.print(tpl)
|
||||
}
|
||||
|
||||
/// Saves a message based on a template.
|
||||
pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
|
||||
mbox: &Mbox,
|
||||
attachments_paths: Vec<&str>,
|
||||
tpl: &str,
|
||||
printer: &mut Printer,
|
||||
imap: &mut ImapService,
|
||||
) -> Result<()> {
|
||||
let tpl = if atty::is(Stream::Stdin) || printer.is_json() {
|
||||
tpl.replace("\r", "")
|
||||
} else {
|
||||
io::stdin()
|
||||
.lock()
|
||||
.lines()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
};
|
||||
let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?;
|
||||
let raw_msg: Vec<u8> = TryInto::try_into(&msg)?;
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
imap.append_raw_msg_with_flags(mbox, &raw_msg, flags)?;
|
||||
printer.print("Template successfully saved")
|
||||
}
|
||||
|
||||
/// Sends a message based on a template.
|
||||
pub fn send<
|
||||
'a,
|
||||
Printer: PrinterService,
|
||||
ImapService: ImapServiceInterface<'a>,
|
||||
SmtpService: SmtpServiceInterface,
|
||||
>(
|
||||
mbox: &Mbox,
|
||||
attachments_paths: Vec<&str>,
|
||||
tpl: &str,
|
||||
printer: &mut Printer,
|
||||
imap: &mut ImapService,
|
||||
smtp: &mut SmtpService,
|
||||
) -> Result<()> {
|
||||
let tpl = if atty::is(Stream::Stdin) || printer.is_json() {
|
||||
tpl.replace("\r", "")
|
||||
} else {
|
||||
io::stdin()
|
||||
.lock()
|
||||
.lines()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
};
|
||||
let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?;
|
||||
let sent_msg = smtp.send_msg(&msg)?;
|
||||
let flags = Flags::try_from(vec![Flag::Seen])?;
|
||||
imap.append_raw_msg_with_flags(mbox, &sent_msg.formatted(), flags)?;
|
||||
printer.print("Template successfully sent")
|
||||
}
|
||||
|
|
|
@ -176,6 +176,12 @@ fn main() -> Result<()> {
|
|||
Some(tpl_arg::Command::Forward(seq, tpl)) => {
|
||||
return tpl_handler::forward(seq, tpl, &account, &mut printer, &mut imap);
|
||||
}
|
||||
Some(tpl_arg::Command::Save(atts, tpl)) => {
|
||||
return tpl_handler::save(&mbox, atts, tpl, &mut printer, &mut imap);
|
||||
}
|
||||
Some(tpl_arg::Command::Send(atts, tpl)) => {
|
||||
return tpl_handler::send(&mbox, atts, tpl, &mut printer, &mut imap, &mut smtp);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
@ -155,6 +155,16 @@ nmap gD <plug>(himalaya-msg-delete)
|
|||
|
||||
![gif](https://user-images.githubusercontent.com/10437171/110708795-84387900-81fb-11eb-8f8a-f7e7862e816d.gif)
|
||||
|
||||
| Function | Default binding |
|
||||
| --- | --- |
|
||||
| Add attachment | `ga` |
|
||||
|
||||
They can be customized:
|
||||
|
||||
```vim
|
||||
nmap ga <plug>(himalaya-msg-add-attachment)
|
||||
```
|
||||
|
||||
When you exit this special buffer, you will be prompted 4 choices:
|
||||
|
||||
- `Send`: sends the message
|
||||
|
|
|
@ -5,6 +5,7 @@ let s:plain_req = function("himalaya#request#plain")
|
|||
|
||||
let s:msg_id = 0
|
||||
let s:draft = ""
|
||||
let s:attachment_paths = []
|
||||
|
||||
function! himalaya#msg#list_with(account, mbox, page, should_throw)
|
||||
let pos = getpos(".")
|
||||
|
@ -254,6 +255,7 @@ endfunction
|
|||
function! himalaya#msg#draft_handle()
|
||||
try
|
||||
let account = himalaya#account#curr()
|
||||
let attachments = join(map(s:attachment_paths, "'--attachment '.v:val"), " ")
|
||||
while 1
|
||||
let choice = input("(s)end, (d)raft, (q)uit or (c)ancel? ")
|
||||
let choice = tolower(choice)[0]
|
||||
|
@ -261,15 +263,15 @@ function! himalaya#msg#draft_handle()
|
|||
|
||||
if choice == "s"
|
||||
return s:cli(
|
||||
\"--account %s send -- %s",
|
||||
\[shellescape(account), shellescape(s:draft)],
|
||||
\"--account %s template send %s -- %s",
|
||||
\[shellescape(account), attachments, shellescape(s:draft)],
|
||||
\"Sending message",
|
||||
\0,
|
||||
\)
|
||||
elseif choice == "d"
|
||||
return s:cli(
|
||||
\"--account %s --mailbox Drafts save -- %s",
|
||||
\[shellescape(account), shellescape(s:draft)],
|
||||
\"--account %s --mailbox Drafts template save %s -- %s",
|
||||
\[shellescape(account), attachments, shellescape(s:draft)],
|
||||
\"Saving draft",
|
||||
\0,
|
||||
\)
|
||||
|
@ -336,6 +338,21 @@ function! himalaya#msg#complete_contact(findstart, base)
|
|||
endtry
|
||||
endfunction
|
||||
|
||||
function! himalaya#msg#add_attachment()
|
||||
try
|
||||
let attachment_path = input("Attachment path: ", "", "file")
|
||||
if empty(expand(glob(attachment_path)))
|
||||
throw "The file does not exist"
|
||||
endif
|
||||
call add(s:attachment_paths, attachment_path)
|
||||
redraw | call himalaya#shared#log#info("Attachment added!")
|
||||
catch
|
||||
if !empty(v:exception)
|
||||
redraw | call himalaya#shared#log#err(v:exception)
|
||||
endif
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
" Utils
|
||||
|
||||
" https://newbedev.com/get-usable-window-width-in-vim-script
|
||||
|
|
|
@ -7,6 +7,10 @@ if exists("g:himalaya_complete_contact_cmd")
|
|||
setlocal completefunc=himalaya#msg#complete_contact
|
||||
endif
|
||||
|
||||
call himalaya#shared#bindings#define([
|
||||
\["n", "ga", "msg#add_attachment"],
|
||||
\])
|
||||
|
||||
augroup himalaya_write
|
||||
autocmd! * <buffer>
|
||||
autocmd BufWriteCmd <buffer> call himalaya#msg#draft_save()
|
||||
|
|
Loading…
Reference in a new issue