commit 1c983c06061f13c5b22357e49157d29a593bd9a0
parent 181636084d041db7bbbfef12465b6fdc70f40a3e
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Tue, 14 Dec 2021 21:32:37 -0800
Add parsing to email body
Diffstat:
5 files changed, 84 insertions(+), 8 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -153,6 +153,7 @@ dependencies = [
"anyhow",
"askama",
"hex",
+ "linkify",
"mailparse",
"mbox-reader",
"once_cell",
@@ -307,6 +308,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
+name = "linkify"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccbcd666d915aa3ae3c3774999a9e20b2776a018309b8159d07df062b91f45e8"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -11,14 +11,15 @@ default = ["html"]
html = ["ammonia"]
[dependencies]
-mailparse = "0.13"
-url = "2"
-once_cell = "1.9"
+ammonia = {version = "3", optional = true}
+anyhow = "1.0"
+askama = "0.10"
hex = "0.4"
-sha3 = "0.10"
+linkify = "0.8.0"
+mailparse = "0.13"
mbox-reader = "0.2.0" #unamaintained, should remove dep
+once_cell = "1.9"
pico-args = "0.4.1"
-askama = "0.10"
-anyhow = "1.0"
quick-xml = "0.22"
-ammonia = {version = "3", optional = true}
+sha3 = "0.10"
+url = "2"
diff --git a/src/filters.rs b/src/filters.rs
@@ -1,3 +1,4 @@
+use linkify::{LinkFinder, LinkKind};
use std::time::{SystemTime, UNIX_EPOCH};
pub fn time_ago(amount: &u64) -> askama::Result<String> {
@@ -9,6 +10,67 @@ const SOLAR_YEAR_SECS: u64 = 31556926;
// add <span> for lines starting with > to make them grey
// parse hyperlinks for you
+// stolen from
+// https://github.com/robinst/linkify/blob/demo/src/lib.rs#L5
+
+pub fn email_body(body: &str) -> askama::Result<String> {
+ let mut bytes = Vec::new();
+ let mut in_reply: bool = false;
+ for line in body.lines() {
+ if line.starts_with(">") || (line.starts_with("On ") && line.ends_with("wrote:")) {
+ if !in_reply {
+ in_reply = true;
+ bytes.extend_from_slice(b"<span class='reply-text'>");
+ }
+ } else if in_reply {
+ bytes.extend_from_slice(b"</span>");
+ in_reply = false
+ }
+
+ let mut finder = LinkFinder::new();
+ for span in finder.spans(line) {
+ match span.kind() {
+ Some(LinkKind::Url) => {
+ bytes.extend_from_slice(b"<a href=\"");
+ escape(span.as_str(), &mut bytes);
+ bytes.extend_from_slice(b"\">");
+ escape(span.as_str(), &mut bytes);
+ bytes.extend_from_slice(b"</a>");
+ }
+ Some(LinkKind::Email) => {
+ bytes.extend_from_slice(b"<a href=\"mailto:");
+ escape(span.as_str(), &mut bytes);
+ bytes.extend_from_slice(b"\">");
+ escape(span.as_str(), &mut bytes);
+ bytes.extend_from_slice(b"</a>");
+ }
+ _ => {
+ escape(span.as_str(), &mut bytes);
+ }
+ }
+ }
+ bytes.extend(b"\n");
+ }
+ if in_reply {
+ bytes.extend_from_slice(b"</span>");
+ }
+ // TODO err conversion
+ Ok(String::from_utf8(bytes).expect("not utf8"))
+}
+
+fn escape(text: &str, dest: &mut Vec<u8>) {
+ for c in text.bytes() {
+ match c {
+ b'&' => dest.extend_from_slice(b"&"),
+ b'<' => dest.extend_from_slice(b"<"),
+ b'>' => dest.extend_from_slice(b">"),
+ b'"' => dest.extend_from_slice(b"""),
+ b'\'' => dest.extend_from_slice(b"'"),
+ _ => dest.push(c),
+ }
+ }
+}
+
fn timeago(unixtime: u64) -> String {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
diff --git a/templates/static/style.css b/templates/static/style.css
@@ -40,6 +40,9 @@ table { border-spacing: 0.5em 0.1em; }
color: grey;
}
+.reply-text {
+ color: grey;
+}
.email-body {
white-space:pre-line;
font-family:monospace;
diff --git a/templates/thread.html b/templates/thread.html
@@ -14,7 +14,7 @@
{% match message.in_reply_to %}{% when Some with (replies_to) %}
<a title="replies-to" href="#{{replies_to}}">Re:</a>{%when none %}{% endmatch %}
<div class="email-body">
- {{message.body}}
+ {{message.body | email_body | safe }}
</div>
<div class="right">
<a href="{{message.mailto()}}">✉️ reply</a>