web: version bumps and handle more message body types.

Added handling for text/html, multipart/alternative w/ text/html &
text/plain subparts, and multipart/mixed.
This commit is contained in:
Bill Thiede 2022-11-13 15:43:27 -08:00
parent 84290d1da6
commit 431df7da3b
3 changed files with 103 additions and 53 deletions

View File

@ -13,14 +13,14 @@ edition = "2018"
crate-type = ["cdylib"]
[dev-dependencies]
wasm-bindgen-test = "0.3.18"
wasm-bindgen-test = "0.3.33"
[dependencies]
console_error_panic_hook = "0.1.6"
log = "0.4.14"
seed = "0.8.0"
console_error_panic_hook = "0.1.7"
log = "0.4.17"
seed = "0.9.2"
console_log = {git = "http://git.z.xinu.tv/wathiede/console_log"}
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0.147", features = ["derive"] }
notmuch = {path = "../notmuch"}
[package.metadata.wasm-pack.profile.release]

View File

@ -16,6 +16,9 @@
.error {
background-color: red;
}
.text_plain {
white-space: pre-line;
}
</style>
</head>

View File

@ -4,9 +4,8 @@
#![allow(clippy::wildcard_imports)]
use log::{error, info, Level};
use seed::{prelude::*, *};
use notmuch::{Content, Part, SearchSummary, ThreadNode, ThreadSet};
use seed::{prelude::*, *};
// ------ ------
// Init
@ -138,9 +137,11 @@ fn view_message(thread: &ThreadNode) -> Node<Msg> {
div![
C!["message"],
/* TODO(wathiede): collect all the tags and show them here. */
/* TODO(wathiede): collect all the attachments from all the subparts */
div![C!["header"], "From: ", &message.headers.from],
div![C!["header"], "Date: ", &message.headers.date],
div![C!["header"], "To: ", &message.headers.to],
hr![],
div![
C!["body"],
match &message.body {
@ -156,13 +157,59 @@ fn view_body(body: &[Part]) -> Node<Msg> {
div![body.iter().map(view_part)]
}
fn view_text_plain(content: &Option<Content>) -> Node<Msg> {
match &content {
Some(Content::String(content)) => p![C!["text_plain"], content],
_ => div![
C!["error"],
format!("Unhandled content enum for text/plain"),
],
}
}
fn view_part(part: &Part) -> Node<Msg> {
match part.content_type.as_str() {
"text/plain" => match &part.content {
Some(Content::String(content)) => pre![content],
"text/plain" => view_text_plain(&part.content),
"text/html" => {
if let Some(Content::String(html)) = &part.content {
return div![Node::from_html(None, &html)];
} else {
div![
C!["error"],
format!("Unhandled content enum for multipart/mixed"),
]
}
}
// https://en.wikipedia.org/wiki/MIME#alternative
// RFC1341 states: In general, user agents that compose multipart/alternative entities
// should place the body parts in increasing order of preference, that is, with the
// preferred format last.
"multipart/alternative" => {
if let Some(Content::Multipart(parts)) = &part.content {
for part in parts.iter().rev() {
if part.content_type == "text/html" {
if let Some(Content::String(html)) = &part.content {
return div![Node::from_html(None, &html)];
}
}
if part.content_type == "text/plain" {
return view_text_plain(&part.content);
}
}
div!["No known multipart/alternative parts"]
} else {
div![
C!["error"],
format!("multipart/alternative with non-multipart content"),
]
}
}
"multipart/mixed" => match &part.content {
Some(Content::Multipart(parts)) => div![parts.iter().map(view_part)],
_ => div![
C!["error"],
format!("Unhandled content enum for text/plain"),
format!("Unhandled content enum for multipart/mixed"),
],
},
_ => div![
@ -187,35 +234,35 @@ fn first_subject(thread: &ThreadNode) -> Option<String> {
/*
let msg = thread.0.as_ref();
if let Some(msg) = msg {
div![
a![attrs! {At::Href=>api::original(&msg.id)}, "Original"],
table![
tr![th!["Subject"], td![&msg.headers.subject],],
tr![th!["From"], td![&msg.headers.from],],
tr![th!["To"], td![&msg.headers.to],],
tr![th!["CC"], td![&msg.headers.cc],],
tr![th!["BCC"], td![&msg.headers.bcc],],
tr![th!["Reply-To"], td![&msg.headers.reply_to],],
tr![th!["Date"], td![&msg.headers.date],],
],
table![
tr![th!["MessageId"], td![&msg.id],],
tr![
th!["Match"],
td![if msg.r#match { "true" } else { "false" }],
],
tr![
th!["Excluded"],
td![if msg.excluded { "true" } else { "false" }],
],
tr![th!["Filename"], td![&msg.filename],],
tr![th!["Timestamp"], td![msg.timestamp.to_string()],],
tr![th!["Date"], td![&msg.date_relative],],
tr![th!["Tags"], td![format!("{:?}", msg.tags)],],
],
]
div![
a![attrs! {At::Href=>api::original(&msg.id)}, "Original"],
table![
tr![th!["Subject"], td![&msg.headers.subject],],
tr![th!["From"], td![&msg.headers.from],],
tr![th!["To"], td![&msg.headers.to],],
tr![th!["CC"], td![&msg.headers.cc],],
tr![th!["BCC"], td![&msg.headers.bcc],],
tr![th!["Reply-To"], td![&msg.headers.reply_to],],
tr![th!["Date"], td![&msg.headers.date],],
],
table![
tr![th!["MessageId"], td![&msg.id],],
tr![
th!["Match"],
td![if msg.r#match { "true" } else { "false" }],
],
tr![
th!["Excluded"],
td![if msg.excluded { "true" } else { "false" }],
],
tr![th!["Filename"], td![&msg.filename],],
tr![th!["Timestamp"], td![msg.timestamp.to_string()],],
tr![th!["Date"], td![&msg.date_relative],],
tr![th!["Tags"], td![format!("{:?}", msg.tags)],],
],
]
} else {
div![h2!["No message"]]
div![h2!["No message"]]
}
*/
@ -235,22 +282,22 @@ fn view(model: &Model) -> Node<Msg> {
view_message(&thread_node),
/*
show_results
.0
.iter()
.enumerate()
.map(|(thread_idx, thread)| div![
h2![format!("thread {}", thread_idx)],
thread
.0
.iter()
.enumerate()
.map(|(thread_node_idx, thread_node)| div![
h3![format!("thread node {}", thread_node_idx)],
view_message(thread_node)
])
]),
.0
.iter()
.enumerate()
.map(|(thread_idx, thread)| div![
h2![format!("thread {}", thread_idx)],
thread
.0
.iter()
.enumerate()
.map(|(thread_node_idx, thread_node)| div![
h3![format!("thread node {}", thread_node_idx)],
view_message(thread_node)
])
]),
*/
pre![format!("Thread: {:#?}", show_results).replace(" ", " ")]
pre!["Add zippy for debug dump"] /* pre![format!("Thread: {:#?}", show_results).replace(" ", " ")] */
]
} else if let Some(search_results) = &model.search_results {
let rows = search_results.0.iter().map(|r| {