summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-18 22:01:02 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-18 22:01:02 +0300
commit07700ca00f1e5e78ea37173c40122761cdaf6d53 (patch)
treed1a0e925848ae2a987bbf1748f36ff36e50bb772
parentba1d0c42e0af56c5a74c3b506f0a952afa974872 (diff)
ui: Add possible mailing list actions from List-* headers
-rw-r--r--ui/src/components/mail/view.rs2
-rw-r--r--ui/src/components/mail/view/list_management.rs116
-rw-r--r--ui/src/execute.rs16
-rw-r--r--ui/src/execute/actions.rs8
4 files changed, 141 insertions, 1 deletions
diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs
index 30ec1f18..c9f032d7 100644
--- a/ui/src/components/mail/view.rs
+++ b/ui/src/components/mail/view.rs
@@ -24,6 +24,8 @@ use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio};
+mod list_management;
+
mod html;
pub use self::html::*;
mod thread;
diff --git a/ui/src/components/mail/view/list_management.rs b/ui/src/components/mail/view/list_management.rs
new file mode 100644
index 00000000..72cd4227
--- /dev/null
+++ b/ui/src/components/mail/view/list_management.rs
@@ -0,0 +1,116 @@
+/*
+ * meli - ui crate.
+ *
+ * Copyright 2017-2019 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+use melib::parser;
+use melib::Envelope;
+use melib::StackVec;
+use std::convert::From;
+
+#[derive(Debug, Copy)]
+pub enum UnsubscribeOption<'a> {
+ Url(&'a [u8]),
+ Email(&'a [u8]),
+}
+
+impl<'a> From<&'a [u8]> for UnsubscribeOption<'a> {
+ fn from(value: &'a [u8]) -> Self {
+ if value.starts_with(b"mailto:") {
+ /* if branch looks if value looks like a mailto url but doesn't validate it.
+ * parser::mailto() will handle this if user tries to unsubscribe.
+ */
+ UnsubscribeOption::Email(value)
+ } else {
+ /* Otherwise treat it as url. There's no foolproof way to check if this is valid, so
+ * postpone it until we try an HTTP request.
+ */
+ UnsubscribeOption::Url(value)
+ }
+ }
+}
+
+/* Required for StackVec's place holder elements, never actually used */
+impl<'a> Default for UnsubscribeOption<'a> {
+ fn default() -> Self {
+ UnsubscribeOption::Email(b"")
+ }
+}
+
+impl<'a> Clone for UnsubscribeOption<'a> {
+ fn clone(&self) -> Self {
+ match self {
+ UnsubscribeOption::Url(a) => UnsubscribeOption::Url(a.clone()),
+ UnsubscribeOption::Email(a) => UnsubscribeOption::Email(a.clone()),
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct ListActions<'a> {
+ pub id: Option<&'a str>,
+ pub archive: Option<&'a str>,
+ pub post: Option<&'a str>,
+ pub unsubscribe: Option<StackVec<UnsubscribeOption<'a>>>,
+}
+
+pub fn detect<'a>(envelope: &'a Envelope) -> Option<ListActions<'a>> {
+ let mut ret = ListActions::default();
+
+ if let Some(id) = envelope.other_headers().get("List-ID") {
+ ret.id = Some(id);
+ } else if let Some(id) = envelope.other_headers().get("List-Id") {
+ ret.id = Some(id);
+ }
+
+ if let Some(archive) = envelope.other_headers().get("List-Archive") {
+ ret.archive = Some(archive);
+ }
+
+ if let Some(post) = envelope.other_headers().get("List-Post") {
+ ret.post = Some(post);
+ }
+
+ if let Some(unsubscribe) = envelope.other_headers().get("List-Unsubscribe") {
+ ret.unsubscribe = parser::angle_bracket_delimeted_list(unsubscribe.as_bytes())
+ .map(|mut vec| {
+ /* Prefer email options first, since this _is_ a mail client after all and it's
+ * more automated */
+ vec.sort_unstable_by(|a, b| {
+ match (a.starts_with(b"mailto:"), b.starts_with(b"mailto:")) {
+ (true, false) => std::cmp::Ordering::Less,
+ (false, true) => std::cmp::Ordering::Greater,
+ _ => std::cmp::Ordering::Equal,
+ }
+ });
+
+ vec.into_iter()
+ .map(|elem| UnsubscribeOption::from(elem))
+ .collect::<StackVec<UnsubscribeOption<'a>>>()
+ })
+ .to_full_result()
+ .ok();
+ }
+
+ if ret.id.is_none() && ret.archive.is_none() && ret.post.is_none() && ret.unsubscribe.is_none()
+ {
+ None
+ } else {
+ Some(ret)
+ }
+}
diff --git a/ui/src/execute.rs b/ui/src/execute.rs
index aed01e3a..81822637 100644
--- a/ui/src/execute.rs
+++ b/ui/src/execute.rs
@@ -27,6 +27,7 @@ use std;
pub mod actions;
pub use crate::actions::Action::{self, *};
pub use crate::actions::ListingAction::{self, *};
+pub use crate::actions::MailingListAction::{self, *};
pub use crate::actions::TabAction::{self, *};
named!(
@@ -95,6 +96,19 @@ named!(
map!(ws!(tag!("toggle_thread_snooze")), |_| ToggleThreadSnooze)
);
+named!(
+ mailinglist<Action>,
+ alt_complete!(
+ map!(ws!(tag!("list-post")), |_| MailingListAction(ListPost))
+ | map!(ws!(tag!("list-unsubscribe")), |_| MailingListAction(
+ ListUnsubscribe
+ ))
+ | map!(ws!(tag!("list-archive")), |_| MailingListAction(
+ ListArchive
+ ))
+ )
+);
+
named!(pub parse_command<Action>,
- alt_complete!( goto | toggle | sort | subsort | close | toggle_thread_snooze)
+ alt_complete!( goto | toggle | sort | subsort | close | toggle_thread_snooze | mailinglist)
);
diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs
index 451ac3d3..7745576c 100644
--- a/ui/src/execute/actions.rs
+++ b/ui/src/execute/actions.rs
@@ -49,6 +49,13 @@ pub enum TabAction {
}
#[derive(Debug)]
+pub enum MailingListAction {
+ ListPost,
+ ListArchive,
+ ListUnsubscribe,
+}
+
+#[derive(Debug)]
pub enum Action {
Listing(ListingAction),
ViewMailbox(usize),
@@ -56,4 +63,5 @@ pub enum Action {
SubSort(SortField, SortOrder),
Tab(TabAction),
ToggleThreadSnooze,
+ MailingListAction(MailingListAction),
}