Compare commits
No commits in common. "56311bbe05a8559939db585ccd0c0d69e67a736c" and "fc87fd702c164169b62b9cf45a3ba6fd30804bb3" have entirely different histories.
56311bbe05
...
fc87fd702c
@ -23,12 +23,6 @@ pub type UnixTime = isize;
|
||||
/// # Thread ID, sans "thread:"
|
||||
pub type ThreadId = String;
|
||||
|
||||
const TEXT_PLAIN: &'static str = "text/plain";
|
||||
const TEXT_HTML: &'static str = "text/html";
|
||||
const MULTIPART_ALTERNATIVE: &'static str = "multipart/alternative";
|
||||
const MULTIPART_MIXED: &'static str = "multipart/mixed";
|
||||
const MULTIPART_RELATED: &'static str = "multipart/related";
|
||||
|
||||
#[derive(Debug, SimpleObject)]
|
||||
pub struct ThreadSummary {
|
||||
pub thread: ThreadId,
|
||||
@ -322,6 +316,7 @@ impl QueryRoot {
|
||||
let mut messages = Vec::new();
|
||||
for (path, id) in std::iter::zip(nm.files(&thread_id)?, nm.message_ids(&thread_id)?) {
|
||||
let tags = nm.tags_for_query(&format!("id:{id}"))?;
|
||||
info!("{id}: {tags:?}\nfile: {path}");
|
||||
let file = File::open(&path)?;
|
||||
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||
let m = parse_mail(&mmap)?;
|
||||
@ -431,10 +426,10 @@ impl Mutation {
|
||||
fn extract_body(m: &ParsedMail) -> Result<Body, Error> {
|
||||
let body = m.get_body()?;
|
||||
let ret = match m.ctype.mimetype.as_str() {
|
||||
TEXT_PLAIN => return Ok(Body::text(body)),
|
||||
TEXT_HTML => return Ok(Body::html(body)),
|
||||
MULTIPART_MIXED => extract_mixed(m),
|
||||
MULTIPART_ALTERNATIVE => extract_alternative(m),
|
||||
"text/plain" => return Ok(Body::text(body)),
|
||||
"text/html" => return Ok(Body::html(body)),
|
||||
"multipart/mixed" => extract_mixed(m),
|
||||
"multipart/alternative" => extract_alternative(m),
|
||||
_ => extract_unhandled(m),
|
||||
};
|
||||
if let Err(err) = ret {
|
||||
@ -449,6 +444,7 @@ fn extract_unhandled(m: &ParsedMail) -> Result<Body, Error> {
|
||||
"Unhandled body content type:\n{}",
|
||||
render_content_type_tree(m)
|
||||
);
|
||||
warn!("{}", msg);
|
||||
Ok(Body::UnhandledContentType(UnhandledContentType {
|
||||
text: msg,
|
||||
}))
|
||||
@ -459,100 +455,59 @@ fn extract_unhandled(m: &ParsedMail) -> Result<Body, Error> {
|
||||
// then give up.
|
||||
fn extract_alternative(m: &ParsedMail) -> Result<Body, Error> {
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype.as_str() == TEXT_HTML {
|
||||
if sp.ctype.mimetype == "text/html" {
|
||||
let body = sp.get_body()?;
|
||||
return Ok(Body::html(body));
|
||||
}
|
||||
}
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype.as_str() == TEXT_PLAIN {
|
||||
if sp.ctype.mimetype == "text/plain" {
|
||||
let body = sp.get_body()?;
|
||||
return Ok(Body::text(body));
|
||||
}
|
||||
}
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype.as_str() == MULTIPART_RELATED {
|
||||
return extract_related(sp);
|
||||
}
|
||||
}
|
||||
Err(format!(
|
||||
"extract_alternative failed to find suitable subpart, searched: {:?}",
|
||||
vec![TEXT_HTML, TEXT_PLAIN]
|
||||
)
|
||||
.into())
|
||||
Err("extract_alternative".into())
|
||||
}
|
||||
|
||||
// multipart/mixed defines multiple types of context all of which should be presented to the user
|
||||
// 'serially'.
|
||||
fn extract_mixed(m: &ParsedMail) -> Result<Body, Error> {
|
||||
let handled_types = vec![
|
||||
MULTIPART_ALTERNATIVE,
|
||||
MULTIPART_RELATED,
|
||||
TEXT_HTML,
|
||||
TEXT_PLAIN,
|
||||
];
|
||||
let mut unhandled_types: Vec<_> = m
|
||||
.subparts
|
||||
.iter()
|
||||
.map(|sp| sp.ctype.mimetype.as_str())
|
||||
.filter(|mt| !handled_types.contains(&mt))
|
||||
.collect();
|
||||
unhandled_types.sort();
|
||||
warn!("{MULTIPART_MIXED} contains the following unhandled mimetypes {unhandled_types:?}");
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype.as_str() == MULTIPART_ALTERNATIVE {
|
||||
if sp.ctype.mimetype == "multipart/alternative" {
|
||||
return extract_alternative(sp);
|
||||
}
|
||||
}
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype == MULTIPART_RELATED {
|
||||
if sp.ctype.mimetype == "multipart/related" {
|
||||
return extract_related(sp);
|
||||
}
|
||||
}
|
||||
for sp in &m.subparts {
|
||||
let body = sp.get_body()?;
|
||||
match sp.ctype.mimetype.as_str() {
|
||||
TEXT_PLAIN => return Ok(Body::text(body)),
|
||||
TEXT_HTML => return Ok(Body::html(body)),
|
||||
"text/plain" => return Ok(Body::text(body)),
|
||||
"text/html" => return Ok(Body::html(body)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Err(format!(
|
||||
"extract_mixed failed to find suitable subpart, searched: {:?}",
|
||||
handled_types
|
||||
)
|
||||
.into())
|
||||
Err("extract_mixed".into())
|
||||
}
|
||||
|
||||
fn extract_related(m: &ParsedMail) -> Result<Body, Error> {
|
||||
// TODO(wathiede): collect related things and change return type to new Body arm.
|
||||
let handled_types = vec![TEXT_HTML, TEXT_PLAIN];
|
||||
let mut unhandled_types: Vec<_> = m
|
||||
.subparts
|
||||
.iter()
|
||||
.map(|sp| sp.ctype.mimetype.as_str())
|
||||
.filter(|mt| !handled_types.contains(&mt))
|
||||
.collect();
|
||||
unhandled_types.sort();
|
||||
warn!("{MULTIPART_RELATED} contains the following unhandled mimetypes {unhandled_types:?}");
|
||||
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype == TEXT_HTML {
|
||||
if sp.ctype.mimetype == "text/html" {
|
||||
let body = sp.get_body()?;
|
||||
return Ok(Body::html(body));
|
||||
}
|
||||
}
|
||||
for sp in &m.subparts {
|
||||
if sp.ctype.mimetype == TEXT_PLAIN {
|
||||
if sp.ctype.mimetype == "text/plain" {
|
||||
let body = sp.get_body()?;
|
||||
return Ok(Body::text(body));
|
||||
}
|
||||
}
|
||||
Err(format!(
|
||||
"extract_related failed to find suitable subpart, searched: {:?}",
|
||||
handled_types
|
||||
)
|
||||
.into())
|
||||
Err("extract_related".into())
|
||||
}
|
||||
|
||||
// TODO(wathiede): make this walk_attachments that takes a closure.
|
||||
|
||||
@ -27,10 +27,6 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.message .header .media-right {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.message .headers {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@ -58,13 +54,6 @@
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.message .body .attachments hr {
|
||||
border: none;
|
||||
border-top: 1px dashed #888;
|
||||
background-color: #f000;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: red;
|
||||
}
|
||||
@ -72,9 +61,7 @@
|
||||
.view-part-text-plain {
|
||||
padding: 0.5em;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
iframe {
|
||||
@ -109,7 +96,7 @@
|
||||
}
|
||||
|
||||
.index .date {
|
||||
width: 7em;
|
||||
width: 10em;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
@ -196,7 +183,6 @@
|
||||
|
||||
.search-results .row .summary {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-results .row .subject {
|
||||
@ -205,20 +191,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-results td.subject {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-results .subject .tag {}
|
||||
|
||||
.search-results .subject .text {
|
||||
padding-left: 0.5rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-results .row .from {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -278,9 +250,7 @@
|
||||
}
|
||||
|
||||
.content-tree {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -21,9 +21,6 @@ mod desktop;
|
||||
mod mobile;
|
||||
mod tablet;
|
||||
|
||||
// TODO(wathiede): create a QueryString enum that wraps single and multiple message ids and thread
|
||||
// ids, and has a to_query_string() that knows notmuch's syntax. Then remove the smattering of
|
||||
// format!() calls all over with magic strings representing notmuch specific syntax.
|
||||
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
|
||||
fn set_title(title: &str) {
|
||||
seed::document().set_title(&format!("lb: {}", title));
|
||||
@ -171,7 +168,7 @@ fn view_search_results(
|
||||
tags_chiclet(&tags, false),
|
||||
" ",
|
||||
a![
|
||||
C!["has-text-light", "text"],
|
||||
C!["has-text-light"],
|
||||
attrs! {
|
||||
At::Href => urls::thread(&tid)
|
||||
},
|
||||
@ -183,7 +180,6 @@ fn view_search_results(
|
||||
});
|
||||
|
||||
div![
|
||||
C!["search-results"],
|
||||
search_toolbar(count, pager, show_bulk_edit),
|
||||
table![
|
||||
C![
|
||||
@ -240,7 +236,6 @@ fn search_toolbar(
|
||||
C!["level-left"],
|
||||
IF!(show_bulk_edit =>
|
||||
span![
|
||||
// TODO(wathiede): add "Mark as spam"
|
||||
C!["level-item", "buttons", "has-addons"],
|
||||
button![
|
||||
C!["button"],
|
||||
@ -425,13 +420,11 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
small![from_detail],
|
||||
table![
|
||||
IF!(!msg.to.is_empty() =>
|
||||
tr![
|
||||
td![ "To" ],
|
||||
//td![ if i==0 { "To" }else { "" } ],
|
||||
td![
|
||||
msg.to.iter().enumerate().map(|(i, to)|
|
||||
tr![
|
||||
td![ if i==0 { "To" }else { "" } ],
|
||||
td![
|
||||
small![
|
||||
if i>0 { ", " }else { "" },
|
||||
match to {
|
||||
ShowThreadQueryThreadMessagesTo {
|
||||
name: Some(name),
|
||||
@ -443,20 +436,18 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
} => format!("{name}"),
|
||||
ShowThreadQueryThreadMessagesTo {
|
||||
addr: Some(addr), ..
|
||||
} => format!("{addr}"),
|
||||
} => format!("<{addr}>"),
|
||||
_ => String::from("UNKNOWN"),
|
||||
}
|
||||
|
||||
])
|
||||
]
|
||||
]),
|
||||
]])),
|
||||
IF!(!msg.cc.is_empty() =>
|
||||
tr![
|
||||
td![ "CC" ],
|
||||
td![
|
||||
msg.cc.iter().enumerate().map(|(i, cc)|
|
||||
tr![
|
||||
td![ if i==0 { "CC" }else { "" } ],
|
||||
td![
|
||||
small![
|
||||
if i>0 { ", " }else { "" },
|
||||
match cc {
|
||||
ShowThreadQueryThreadMessagesCc {
|
||||
name: Some(name),
|
||||
@ -472,9 +463,8 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
_ => String::from("UNKNOWN"),
|
||||
}
|
||||
|
||||
])
|
||||
]
|
||||
]),
|
||||
]])),
|
||||
tr![
|
||||
td!["Date"],
|
||||
td![msg.timestamp.map(|ts| span![C!["header"], human_age(ts)])]
|
||||
@ -487,14 +477,14 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
C!["media-right"],
|
||||
span![
|
||||
C!["read-status"],
|
||||
i![C![
|
||||
i![
|
||||
C![
|
||||
"far",
|
||||
if is_unread {
|
||||
"fa-envelope"
|
||||
} else {
|
||||
"fa-envelope-open"
|
||||
},
|
||||
]]
|
||||
],
|
||||
ev(Ev::Click, move |e| {
|
||||
e.stop_propagation();
|
||||
@ -502,6 +492,8 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
})
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
fn render_closed_header(msg: &ShowThreadQueryThreadMessages) -> Node<Msg> {
|
||||
@ -625,7 +617,7 @@ fn message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node<Msg>
|
||||
IF!(!msg.attachments.is_empty() =>
|
||||
div![
|
||||
C!["attachments"],
|
||||
hr![],
|
||||
br![],
|
||||
h2!["Attachments"],
|
||||
msg.attachments
|
||||
.iter()
|
||||
@ -663,7 +655,6 @@ fn thread(thread: &ShowThreadQueryThread, open_messages: &HashSet<String>) -> No
|
||||
h3![C!["is-size-5"], &thread.subject],
|
||||
span![C!["tags"], tags_chiclet(&tags, false)],
|
||||
span![
|
||||
// TODO(wathiede): add "Mark as spam"
|
||||
C!["level-item", "buttons", "has-addons"],
|
||||
button![
|
||||
C!["button"],
|
||||
@ -700,6 +691,7 @@ fn thread(thread: &ShowThreadQueryThread, open_messages: &HashSet<String>) -> No
|
||||
fn view_content_tree(content_tree: &str) -> Node<Msg> {
|
||||
let debug_open = use_state(|| false);
|
||||
div![
|
||||
hr![],
|
||||
small![
|
||||
i![C![
|
||||
"fa-solid",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user