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"] crate-type = ["cdylib"]
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = "0.3.18" wasm-bindgen-test = "0.3.33"
[dependencies] [dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.7"
log = "0.4.14" log = "0.4.17"
seed = "0.8.0" seed = "0.9.2"
console_log = {git = "http://git.z.xinu.tv/wathiede/console_log"} 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"} notmuch = {path = "../notmuch"}
[package.metadata.wasm-pack.profile.release] [package.metadata.wasm-pack.profile.release]

View File

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

View File

@ -4,9 +4,8 @@
#![allow(clippy::wildcard_imports)] #![allow(clippy::wildcard_imports)]
use log::{error, info, Level}; use log::{error, info, Level};
use seed::{prelude::*, *};
use notmuch::{Content, Part, SearchSummary, ThreadNode, ThreadSet}; use notmuch::{Content, Part, SearchSummary, ThreadNode, ThreadSet};
use seed::{prelude::*, *};
// ------ ------ // ------ ------
// Init // Init
@ -138,9 +137,11 @@ fn view_message(thread: &ThreadNode) -> Node<Msg> {
div![ div![
C!["message"], C!["message"],
/* TODO(wathiede): collect all the tags and show them here. */ /* 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"], "From: ", &message.headers.from],
div![C!["header"], "Date: ", &message.headers.date], div![C!["header"], "Date: ", &message.headers.date],
div![C!["header"], "To: ", &message.headers.to], div![C!["header"], "To: ", &message.headers.to],
hr![],
div![ div![
C!["body"], C!["body"],
match &message.body { match &message.body {
@ -156,14 +157,60 @@ fn view_body(body: &[Part]) -> Node<Msg> {
div![body.iter().map(view_part)] div![body.iter().map(view_part)]
} }
fn view_part(part: &Part) -> Node<Msg> { fn view_text_plain(content: &Option<Content>) -> Node<Msg> {
match part.content_type.as_str() { match &content {
"text/plain" => match &part.content { Some(Content::String(content)) => p![C!["text_plain"], content],
Some(Content::String(content)) => pre![content],
_ => div![ _ => div![
C!["error"], C!["error"],
format!("Unhandled content enum for text/plain"), format!("Unhandled content enum for text/plain"),
], ],
}
}
fn view_part(part: &Part) -> Node<Msg> {
match part.content_type.as_str() {
"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 multipart/mixed"),
],
}, },
_ => div![ _ => div![
C!["error"], C!["error"],
@ -187,35 +234,35 @@ fn first_subject(thread: &ThreadNode) -> Option<String> {
/* /*
let msg = thread.0.as_ref(); let msg = thread.0.as_ref();
if let Some(msg) = msg { if let Some(msg) = msg {
div![ div![
a![attrs! {At::Href=>api::original(&msg.id)}, "Original"], a![attrs! {At::Href=>api::original(&msg.id)}, "Original"],
table![ table![
tr![th!["Subject"], td![&msg.headers.subject],], tr![th!["Subject"], td![&msg.headers.subject],],
tr![th!["From"], td![&msg.headers.from],], tr![th!["From"], td![&msg.headers.from],],
tr![th!["To"], td![&msg.headers.to],], tr![th!["To"], td![&msg.headers.to],],
tr![th!["CC"], td![&msg.headers.cc],], tr![th!["CC"], td![&msg.headers.cc],],
tr![th!["BCC"], td![&msg.headers.bcc],], tr![th!["BCC"], td![&msg.headers.bcc],],
tr![th!["Reply-To"], td![&msg.headers.reply_to],], tr![th!["Reply-To"], td![&msg.headers.reply_to],],
tr![th!["Date"], td![&msg.headers.date],], tr![th!["Date"], td![&msg.headers.date],],
], ],
table![ table![
tr![th!["MessageId"], td![&msg.id],], tr![th!["MessageId"], td![&msg.id],],
tr![ tr![
th!["Match"], th!["Match"],
td![if msg.r#match { "true" } else { "false" }], td![if msg.r#match { "true" } else { "false" }],
], ],
tr![ tr![
th!["Excluded"], th!["Excluded"],
td![if msg.excluded { "true" } else { "false" }], td![if msg.excluded { "true" } else { "false" }],
], ],
tr![th!["Filename"], td![&msg.filename],], tr![th!["Filename"], td![&msg.filename],],
tr![th!["Timestamp"], td![msg.timestamp.to_string()],], tr![th!["Timestamp"], td![msg.timestamp.to_string()],],
tr![th!["Date"], td![&msg.date_relative],], tr![th!["Date"], td![&msg.date_relative],],
tr![th!["Tags"], td![format!("{:?}", msg.tags)],], tr![th!["Tags"], td![format!("{:?}", msg.tags)],],
], ],
] ]
} else { } else {
div![h2!["No message"]] div![h2!["No message"]]
} }
*/ */
@ -250,7 +297,7 @@ fn view(model: &Model) -> Node<Msg> {
]) ])
]), ]),
*/ */
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 { } else if let Some(search_results) = &model.search_results {
let rows = search_results.0.iter().map(|r| { let rows = search_results.0.iter().map(|r| {