Compare commits
No commits in common. "e0fbb0253e196d765b847375ce1049607e0c04ec" and "d45f223d520cd3a62fa953dff858bb57e0c88744" have entirely different histories.
e0fbb0253e
...
d45f223d52
@ -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>> {
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -23,11 +23,9 @@ query ShowThreadQuery($threadId: String!) {
|
|||||||
}
|
}
|
||||||
... on PlainText {
|
... on PlainText {
|
||||||
contents
|
contents
|
||||||
contentTree
|
|
||||||
}
|
}
|
||||||
... on Html {
|
... on Html {
|
||||||
contents
|
contents
|
||||||
contentTree
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path
|
path
|
||||||
|
|||||||
@ -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]
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user