diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 30ec1f180..c9f032d74 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 000000000..72cd42276 --- /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 . + */ +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>>, +} + +pub fn detect<'a>(envelope: &'a Envelope) -> Option> { + 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::>>() + }) + .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 aed01e3ab..818226370 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, + 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, - 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 451ac3d3f..7745576c4 100644 --- a/ui/src/execute/actions.rs +++ b/ui/src/execute/actions.rs @@ -48,6 +48,13 @@ pub enum TabAction { Kill(Uuid), } +#[derive(Debug)] +pub enum MailingListAction { + ListPost, + ListArchive, + ListUnsubscribe, +} + #[derive(Debug)] pub enum Action { Listing(ListingAction), @@ -56,4 +63,5 @@ pub enum Action { SubSort(SortField, SortOrder), Tab(TabAction), ToggleThreadSnooze, + MailingListAction(MailingListAction), }