commit 272d09602df051a963f3ff89cfd12b3c9c5d5f6f
parent 78d2c8188b6501f7a8ffde75e23869cef24a9e48
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Sun, 12 Dec 2021 17:33:15 -0800
some frontend improvements
Diffstat:
5 files changed, 80 insertions(+), 35 deletions(-)
diff --git a/crabmail/src/filters.rs b/crabmail/src/filters.rs
@@ -39,30 +39,15 @@ fn timeago(unixtime: u64) -> String {
_ => format!("{} {}s ago", amount, metric),
}
}
-// // NOTE this function is currently unsafe
-// pub fn get_body(email: &&ParsedMail) -> askama::Result<String> {
-// let core_email = email.subparts.get(0).unwrap_or(email);
+pub fn get_body(email: &&ParsedMail) -> askama::Result<String> {
+ let core_email = email.subparts.get(0).unwrap_or(email);
-// #[cfg(feature = "html")]
-// {
-// use ammonia;
-// use std::collections::HashSet;
-// use std::iter::FromIterator;
-// // TODO dont initialize each time
-// // TODO sanitize id, classes, etc.
-// let tags = HashSet::from_iter(vec!["a", "b", "i", "br", "p", "span", "u"]);
-// if core_email.ctype.mimetype == "text/html" {
-// let a = ammonia::Builder::new()
-// .tags(tags)
-// .clean(&core_email.get_body().unwrap_or("".to_string()))
-// .to_string();
-// return Ok(a);
-// }
-// }
+ #[cfg(feature = "html")]
+ {}
-// if core_email.ctype.mimetype == "text/plain" {
-// // TODO html escape this.
-// return Ok(core_email.get_body().unwrap_or("".to_string()));
-// }
-// return Ok(String::from("[No valid body found]"));
-// }
+ if core_email.ctype.mimetype == "text/plain" {
+ // TODO html escape this.
+ return Ok(core_email.get_body().unwrap_or("".to_string()));
+ }
+ return Ok(String::from("[No valid body found]"));
+}
diff --git a/crabmail/src/main.rs b/crabmail/src/main.rs
@@ -30,11 +30,48 @@ struct Email {
in_reply_to: Option<String>,
date: u64, // unix epoch. received date
body: String,
+ mime: String,
// raw_email: String,
}
+#[cfg(feature = "html")]
+fn parse_html_body(email: &ParsedMail) -> String {
+ use ammonia;
+ use std::collections::HashSet;
+ use std::iter::FromIterator;
+ // TODO dont initialize each time
+ // TODO sanitize id, classes, etc.
+ let tags = HashSet::from_iter(vec!["a", "b", "i", "br", "p", "span", "u"]);
+ let a = ammonia::Builder::new()
+ .tags(tags)
+ .clean(&email.get_body().unwrap_or("".to_string()))
+ .to_string();
+ a
+}
+
fn local_parse_email(data: &[u8]) -> Result<Email> {
let parsed_mail = parse_mail(data)?;
+ let mut body: String = "[Message has no body]".to_owned();
+ let mut mime: String = "".to_owned();
+ let nobody = "[No body found]";
+ if parsed_mail.subparts.len() == 0 {
+ body = parsed_mail.get_body().unwrap_or(nobody.to_owned());
+ } else {
+ for sub in &parsed_mail.subparts {
+ if sub.ctype.mimetype == "text/plain" {
+ body = sub.get_body().unwrap_or(nobody.to_owned());
+ mime = sub.ctype.mimetype.clone();
+ break;
+ }
+ }
+ #[cfg(feature = "html")]
+ for sub in &parsed_mail.subparts {
+ if sub.ctype.mimetype == "text/html" {
+ mime = sub.ctype.mimetype.clone();
+ break;
+ }
+ }
+ }
let headers = parsed_mail.headers;
let id = headers
.get_first_value("message-id")
@@ -52,10 +89,10 @@ fn local_parse_email(data: &[u8]) -> Result<Email> {
let date = dateparse(
&headers
.get_first_value("received")
- .context("No date header")?,
+ .unwrap_or(headers.get_first_value("date").context("No date header")?),
)? as u64;
let from = headers.get_first_value("from").context("No from header")?;
- let body = "lorem ipsum".to_owned();
+
return Ok(Email {
id,
in_reply_to,
@@ -63,6 +100,7 @@ fn local_parse_email(data: &[u8]) -> Result<Email> {
subject,
date,
body,
+ mime,
});
}
@@ -87,7 +125,10 @@ fn main() -> Result<()> {
let mut email_index: HashMap<String, Email> = HashMap::new();
for entry in mbox.iter() {
let buffer = entry.message().unwrap();
- let email = local_parse_email(buffer)?;
+ let email = match local_parse_email(buffer) {
+ Ok(e) => e,
+ Err(_) => continue,
+ };
// TODO fix borrow checker
if let Some(reply) = email.in_reply_to.clone() {
match thread_index.get(&reply) {
diff --git a/crabmail/templates/base.html b/crabmail/templates/base.html
@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Permissions-Policy" content="interest-cohort=()"/>
- <link rel="stylesheet" type="text/css" href="style.css" />
+ <link rel="stylesheet" type="text/css" href="../style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0" />
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📧</text></svg>
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📧</text></svg>">
<meta name="description" content="Crabmail mailing list">
<title>Crabmail Mailing List</title>
{% block head %}{% endblock %}
diff --git a/crabmail/templates/static/style.css b/crabmail/templates/static/style.css
@@ -16,8 +16,27 @@ table { border-spacing: 0.5em 0.1em; }
font-family: "Roboto Mono", monospace;
}
+
.email-body {
white-space:pre-line;
+ font-family:monospace;
+ font-size: 1rem;
+}
+
+.button {
+ background-color: blue;
+ border: none;
+ color: white;
+ text-align: center;
+ text-decoration: none;
+ padding: 3px 3px;
+ display: inline-block;
+ font-size: 16px;
+}
+
+.button:hover {
+ background-color: green;
+ color: white;
}
.filesize {
@@ -41,7 +60,6 @@ h1, h2, h3 {
}
h3 {
- display: inline-block;
margin-right: 2em;
}
diff --git a/crabmail/templates/thread.html b/crabmail/templates/thread.html
@@ -3,14 +3,15 @@
{% block content %}
<div class="page-title"><h1>{{root.subject}}</h1></div>
<div>
- <div class="message">
{% for message in messages %}
- <h3>{{message.subject}}</h3>
- <b>From: </b>{{message.from}}<br>
- <b>Date: </b>{{message.date | time_ago}}<br>
+ <div class="message">
+ <h3 id="{{message.id}}"><a href="#{{message.id}}">#</a> {{message.subject}}</h3>
+ <b>From: </b>{{message.from}} <em>{{message.date | time_ago}}</em><br>
<div class="email-body">
{{message.body}}
</div>
+ <a class="button">reply</a>
+ <hr>
{% endfor %}
</div>
</div>