Compare commits

..

No commits in common. "e0fbb0253e196d765b847375ce1049607e0c04ec" and "d45f223d520cd3a62fa953dff858bb57e0c88744" have entirely different histories.

5 changed files with 57 additions and 132 deletions

View File

@ -18,6 +18,7 @@ use server::{
error::ServerError, error::ServerError,
graphql::{GraphqlSchema, QueryRoot}, graphql::{GraphqlSchema, QueryRoot},
}; };
use shared::Message;
#[get("/refresh")] #[get("/refresh")]
async fn refresh(nm: &State<Notmuch>) -> Result<Json<String>, Debug<NotmuchError>> { async fn refresh(nm: &State<Notmuch>) -> Result<Json<String>, Debug<NotmuchError>> {

View File

@ -63,7 +63,7 @@ pub struct Message {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UnhandledContentType { struct UnhandledContentType {
text: String, text: String,
} }
@ -75,9 +75,8 @@ impl UnhandledContentType {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct PlainText { struct PlainText {
text: String, text: String,
content_tree: String,
} }
#[Object] #[Object]
@ -85,15 +84,11 @@ impl PlainText {
async fn contents(&self) -> &str { async fn contents(&self) -> &str {
&self.text &self.text
} }
async fn content_tree(&self) -> &str {
&self.content_tree
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Html { struct Html {
html: String, html: String,
content_tree: String,
} }
#[Object] #[Object]
@ -101,9 +96,6 @@ impl Html {
async fn contents(&self) -> &str { async fn contents(&self) -> &str {
&self.html &self.html
} }
async fn content_tree(&self) -> &str {
&self.content_tree
}
} }
#[derive(Debug, Union)] #[derive(Debug, Union)]
@ -113,21 +105,6 @@ pub enum Body {
Html(Html), Html(Html),
} }
impl Body {
fn html(html: String) -> Body {
Body::Html(Html {
html,
content_tree: "".to_string(),
})
}
fn text(text: String) -> Body {
Body::PlainText(PlainText {
text,
content_tree: "".to_string(),
})
}
}
#[derive(Debug, SimpleObject)] #[derive(Debug, SimpleObject)]
pub struct Email { pub struct Email {
pub name: Option<String>, pub name: Option<String>,
@ -239,12 +216,6 @@ impl QueryRoot {
// TODO(wathiede): normalize all email addresses through an address book with preferred // TODO(wathiede): normalize all email addresses through an address book with preferred
// display names (that default to the most commonly seen name). // display names (that default to the most commonly seen name).
let nm = ctx.data_unchecked::<Notmuch>(); let nm = ctx.data_unchecked::<Notmuch>();
let debug_content_tree = ctx
.look_ahead()
.field("messages")
.field("body")
.field("contentTree")
.exists();
let mut messages = Vec::new(); let mut messages = Vec::new();
for path in nm.files(&thread_id)? { for path in nm.files(&thread_id)? {
let path = path?; let path = path?;
@ -272,21 +243,8 @@ impl QueryRoot {
.get_first_value("date") .get_first_value("date")
.and_then(|d| mailparse::dateparse(&d).ok()); .and_then(|d| mailparse::dateparse(&d).ok());
let body = match extract_body(&m)? { let body = match extract_body(&m)? {
Body::PlainText(PlainText { text, content_tree }) => Body::PlainText(PlainText { Body::Html(Html { html }) => Body::Html(Html {
text,
content_tree: if debug_content_tree {
render_content_type_tree(&m)
} else {
content_tree
},
}),
Body::Html(Html { html, content_tree }) => Body::Html(Html {
html: ammonia::clean(&html), html: ammonia::clean(&html),
content_tree: if debug_content_tree {
render_content_type_tree(&m)
} else {
content_tree
},
}), }),
b => b, b => b,
}; };
@ -316,8 +274,8 @@ impl QueryRoot {
fn extract_body(m: &ParsedMail) -> Result<Body, Error> { fn extract_body(m: &ParsedMail) -> Result<Body, Error> {
let body = m.get_body()?; let body = m.get_body()?;
let ret = match m.ctype.mimetype.as_str() { let ret = match m.ctype.mimetype.as_str() {
"text/plain" => return Ok(Body::text(body)), "text/plain" => return Ok(Body::PlainText(PlainText { text: body })),
"text/html" => return Ok(Body::html(body)), "text/html" => return Ok(Body::Html(Html { html: body })),
"multipart/mixed" => extract_mixed(m), "multipart/mixed" => extract_mixed(m),
"multipart/alternative" => extract_alternative(m), "multipart/alternative" => extract_alternative(m),
_ => extract_unhandled(m), _ => extract_unhandled(m),
@ -343,13 +301,13 @@ fn extract_alternative(m: &ParsedMail) -> Result<Body, Error> {
for sp in &m.subparts { for sp in &m.subparts {
if sp.ctype.mimetype == "text/html" { if sp.ctype.mimetype == "text/html" {
let body = sp.get_body()?; let body = sp.get_body()?;
return Ok(Body::html(body)); return Ok(Body::Html(Html { html: body }));
} }
} }
for sp in &m.subparts { for sp in &m.subparts {
if sp.ctype.mimetype == "text/plain" { if sp.ctype.mimetype == "text/plain" {
let body = sp.get_body()?; let body = sp.get_body()?;
return Ok(Body::text(body)); return Ok(Body::PlainText(PlainText { text: body }));
} }
} }
Err("extract_alternative".into()) Err("extract_alternative".into())
@ -369,8 +327,8 @@ fn extract_mixed(m: &ParsedMail) -> Result<Body, Error> {
for sp in &m.subparts { for sp in &m.subparts {
let body = sp.get_body()?; let body = sp.get_body()?;
match sp.ctype.mimetype.as_str() { match sp.ctype.mimetype.as_str() {
"text/plain" => return Ok(Body::text(body)), "text/plain" => return Ok(Body::PlainText(PlainText { text: body })),
"text/html" => return Ok(Body::html(body)), "text/html" => return Ok(Body::Html(Html { html: body })),
_ => (), _ => (),
} }
} }
@ -382,13 +340,13 @@ fn extract_related(m: &ParsedMail) -> Result<Body, Error> {
for sp in &m.subparts { for sp in &m.subparts {
if sp.ctype.mimetype == "text/html" { if sp.ctype.mimetype == "text/html" {
let body = sp.get_body()?; let body = sp.get_body()?;
return Ok(Body::html(body)); return Ok(Body::Html(Html { html: body }));
} }
} }
for sp in &m.subparts { for sp in &m.subparts {
if sp.ctype.mimetype == "text/plain" { if sp.ctype.mimetype == "text/plain" {
let body = sp.get_body()?; let body = sp.get_body()?;
return Ok(Body::text(body)); return Ok(Body::PlainText(PlainText { text: body }));
} }
} }
Err("extract_related".into()) Err("extract_related".into())

View File

@ -159,22 +159,6 @@
"ofType": null "ofType": null
} }
} }
},
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "contentTree",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
} }
], ],
"inputFields": null, "inputFields": null,
@ -416,22 +400,6 @@
"ofType": null "ofType": null
} }
} }
},
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "contentTree",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
} }
], ],
"inputFields": null, "inputFields": null,

View File

@ -23,11 +23,9 @@ query ShowThreadQuery($threadId: String!) {
} }
... on PlainText { ... on PlainText {
contents contents
contentTree
} }
... on Html { ... on Html {
contents contents
contentTree
} }
} }
path path

View File

@ -985,48 +985,58 @@ fn view_search_pager_legacy(start: usize, count: usize, total: usize) -> Node<Ms
} }
trait Email { trait Email {
fn name(&self) -> Option<&str>; fn name(&self) -> &Option<String>;
fn addr(&self) -> Option<&str>; fn addr(&self) -> &Option<String>;
} }
impl<T: Email> Email for &'_ T { impl<T: Email> Email for &'_ T {
fn name(&self) -> Option<&str> { fn name(&self) -> &Option<String> {
return (*self).name(); return (*self).name();
} }
fn addr(&self) -> Option<&str> { fn addr(&self) -> &Option<String> {
return (*self).addr(); return (*self).addr();
} }
} }
macro_rules! implement_email { impl Email for ShowThreadQueryThreadMessagesCc {
( $t:ty ) => { fn name(&self) -> &Option<String> {
impl Email for $t { return &self.name;
fn name(&self) -> Option<&str> {
self.name.as_deref()
} }
fn addr(&self) -> Option<&str> { fn addr(&self) -> &Option<String> {
self.addr.as_deref() return &self.addr;
}
}
impl Email for ShowThreadQueryThreadMessagesFrom {
fn name(&self) -> &Option<String> {
return &self.name;
}
fn addr(&self) -> &Option<String> {
return &self.addr;
}
}
impl Email for ShowThreadQueryThreadMessagesTo {
fn name(&self) -> &Option<String> {
return &self.name;
}
fn addr(&self) -> &Option<String> {
return &self.addr;
} }
}
};
} }
implement_email!(ShowThreadQueryThreadMessagesTo);
implement_email!(ShowThreadQueryThreadMessagesCc);
implement_email!(ShowThreadQueryThreadMessagesFrom);
fn view_address(email: impl Email) -> Node<Msg> { fn view_addresses<E: Email>(addrs: &[E]) -> Vec<Node<Msg>> {
addrs
.into_iter()
.map(|address| {
span![ span![
C!["tag", "is-black"], C!["tag", "is-black"],
email.addr().as_ref().map(|a| attrs! {At::Title=>a}), address.addr().as_ref().map(|a| attrs! {At::Title=>a}),
email address
.name() .name()
.as_ref() .as_ref()
.unwrap_or(&email.addr().unwrap_or("(UNKNOWN)")) .unwrap_or(address.addr().as_ref().unwrap_or(&"(UNKNOWN)".to_string()))
] ]
} })
.collect::<Vec<_>>()
fn view_addresses(addrs: &[impl Email]) -> Vec<Node<Msg>> {
addrs.into_iter().map(view_address).collect::<Vec<_>>()
} }
fn view_thread(thread: &ShowThreadQueryThread) -> Node<Msg> { fn view_thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
@ -1051,21 +1061,11 @@ fn view_thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
ShowThreadQueryThreadMessagesBodyOnUnhandledContentType { contents }, ShowThreadQueryThreadMessagesBodyOnUnhandledContentType { contents },
) => pre![C!["error"], contents], ) => pre![C!["error"], contents],
ShowThreadQueryThreadMessagesBody::PlainText( ShowThreadQueryThreadMessagesBody::PlainText(
ShowThreadQueryThreadMessagesBodyOnPlainText { ShowThreadQueryThreadMessagesBodyOnPlainText { contents },
contents, ) => div![C!["view-part-text-plain"], contents],
content_tree,
},
) => div![C!["view-part-text-plain"], contents, pre![content_tree]],
ShowThreadQueryThreadMessagesBody::Html( ShowThreadQueryThreadMessagesBody::Html(
ShowThreadQueryThreadMessagesBodyOnHtml { ShowThreadQueryThreadMessagesBodyOnHtml { contents },
contents, ) => div![C!["view-part-text-html"], raw![contents]],
content_tree,
},
) => div![
C!["view-part-text-html"],
raw![contents],
pre![content_tree]
],
} }
], ],
] ]