commit d857ddf4dadb2fd313d0da2bbadb5863e17cc58c
parent 4e54e7945985f51f236ddc5e474e4410ee71b23f
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Sat, 19 Mar 2022 22:03:16 -0700
Fix gemini output
Diffstat:
5 files changed, 110 insertions(+), 62 deletions(-)
diff --git a/TODO b/TODO
@@ -1,18 +1,15 @@
TODO
====
-understand content-transfer-encoding for eml body
-use get_thread_name?
-get export working -> use mail-builder (fork it and/or cut deps)
-get reply link working
allow mbox/single folder input
atom feeds working
gemini pages finish up
+-> gemini body parser
paginate list home
create list home page
-
dkim?
+
Duplicate ID verification: warn on duplicate ID, use first received-date. This
is to prevent someone overwriting old emails secretly
diff --git a/src/models.rs b/src/models.rs
@@ -120,6 +120,20 @@ impl MailAddress {
address: address.unwrap().to_string(),
}
}
+
+ pub fn to_string(&self) -> String {
+ let mut out = String::new();
+ if let Some(n) = &self.name {
+ out.push('"');
+ out.push_str(&n);
+ out.push('"');
+ out.push(' ');
+ }
+ out.push('<');
+ out.push_str(&self.address);
+ out.push('>');
+ out
+ }
}
// TODO rename
diff --git a/src/templates/gmi.rs b/src/templates/gmi.rs
@@ -1,6 +1,8 @@
// WIP
//
use crate::models::*;
+use crate::time::Date;
+use crate::util::*;
use nanotemplate::template;
impl Lists {
@@ -17,7 +19,41 @@ impl Lists {
impl List {
pub fn to_gmi(&self) -> Vec<String> {
- vec![]
+ // TODO paginate
+ let mut threads = "# list name".to_string();
+ for thread in &self.thread_topics {
+ threads.push_str(
+ // TODO reuse with html templates?
+ &template(
+ r#"
+=> threads/{path_id}.gmi {subject}
+{preview}
+{from} | {replies} replies | {date}
+"#,
+ &[
+ (
+ "path_id",
+ &h(thread.message.pathescape_msg_id().to_str().unwrap()),
+ ),
+ ("subject", &h(&thread.message.subject)),
+ ("replies", &thread.reply_count.to_string()),
+ ("preview", &h(&thread.message.preview)),
+ ("date", &h(&Date::from(thread.last_reply).ymd())),
+ (
+ "from",
+ &h(&thread. // awkawrd
+ message.from
+ .name
+ .clone()
+ .unwrap_or(thread.message.from.address.clone())
+ .clone()),
+ ),
+ ],
+ )
+ .unwrap(),
+ );
+ }
+ vec![threads]
}
}
@@ -29,24 +65,47 @@ impl Thread {
self.messages[0].subject.replace("\n", " ")
);
for msg in &self.messages {
+ let mut optional_headers = String::new();
+ if let Some(irt) = &msg.in_reply_to {
+ optional_headers.push_str(&format!("\nIn-Reply-To: {}", &h(&irt)));
+ }
+ // TODO no copy pasta
+ let cc_string = &h(&msg
+ .cc
+ .iter()
+ .map(|t| t.to_string())
+ .collect::<Vec<String>>()
+ .join(", "));
+ if msg.cc.len() > 0 {
+ optional_headers.push_str(&format!("\nCc: {}", cc_string));
+ }
let msg = template(
r#"
## {subject}
From: {from}
Date: {date}
-In-Reply-To: adsf
Message-Id: {msg_id}
-To: ...
-Cc: ...
----------------------
+To: {to}{optional_headers}
+--------------------------------------
{body}
"#,
&[
("subject", &h(&msg.subject)),
("date", &h(&msg.date)),
("msg_id", &h(&msg.id)),
+ (
+ "to",
+ &h(&msg
+ .to
+ .iter()
+ .map(|t| t.to_string())
+ .collect::<Vec<String>>()
+ .join(", ")),
+ ),
+ ("optional_headers", &optional_headers),
("from", &h(&msg.from.address)),
- ("body", &escape_body(&msg.body)),
+ // TODO escape # in body?
+ ("body", &unformat_flowed(&msg.body)),
],
)
.unwrap();
diff --git a/src/templates/html.rs b/src/templates/html.rs
@@ -228,28 +228,6 @@ impl Thread {
}
}
-impl StrMessage {
- pub fn to_html(&self) -> String {
- // TODO test thoroughly
- template(
- r#"<div id="{id}", class="message">
- <div class="message-meta">
- <span class="bold">
- {subject}
- </span>
- <a href="mailto:{from}" class="bold">{from}</a>
- <span>{date}</span>
- <a class="permalink" href=#{id}>🔗</a>
- </div>
- </div>
- etc
- "#,
- &[("id", "asdf")],
- )
- .unwrap()
- }
-}
-
// gpl licensed from wikipedia https://commons.wikimedia.org/wiki/File:Generic_Feed-icon.svg
pub const RSS_SVG: &'static str = r#"
data:image/svg+xml,<?xml version="1.0" encoding="UTF-8"?>
@@ -320,32 +298,3 @@ pub fn email_body(body: &str, flowed: bool) -> String {
// TODO err conversion
String::from_utf8(bytes).unwrap()
}
-
-// TODO MOVE!
-// stolen from https://github.com/deltachat/deltachat-core-rust/blob/master/src/format_flowed.rs
-// undoes format=flowed
-pub fn unformat_flowed(text: &str) -> String {
- let mut result = String::new();
- let mut skip_newline = true;
-
- for line in text.split('\n') {
- // Revert space-stuffing
- let line = line.strip_prefix(' ').unwrap_or(line);
-
- if !skip_newline {
- result.push('\n');
- }
-
- if let Some(line) = line.strip_suffix(' ') {
- // Flowed line
- result += line;
- result.push(' ');
- skip_newline = true;
- } else {
- // Fixed line
- result += line;
- skip_newline = false;
- }
- }
- result
-}
diff --git a/src/util.rs b/src/util.rs
@@ -12,3 +12,32 @@ pub fn truncate_ellipsis(s: &str, n: usize) -> String {
out.push_str("...");
out.to_string()
}
+
+// TODO quoted lines?
+// stolen from https://github.com/deltachat/deltachat-core-rust/blob/master/src/format_flowed.rs
+// undoes format=flowed
+pub fn unformat_flowed(text: &str) -> String {
+ let mut result = String::new();
+ let mut skip_newline = true;
+
+ for line in text.split('\n') {
+ // Revert space-stuffing
+ let line = line.strip_prefix(' ').unwrap_or(line);
+
+ if !skip_newline {
+ result.push('\n');
+ }
+
+ if let Some(line) = line.strip_suffix(' ') {
+ // Flowed line
+ result += line;
+ result.push(' ');
+ skip_newline = true;
+ } else {
+ // Fixed line
+ result += line;
+ skip_newline = false;
+ }
+ }
+ result
+}