From 431df7da3baea376708b3cae6b8a4939709a90ad Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 13 Nov 2022 15:43:27 -0800 Subject: [PATCH] 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. --- web/Cargo.toml | 10 ++-- web/index.html | 3 ++ web/src/lib.rs | 143 ++++++++++++++++++++++++++++++++----------------- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index e681c2e..9764e09 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -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] diff --git a/web/index.html b/web/index.html index b1d979f..edb1ba1 100644 --- a/web/index.html +++ b/web/index.html @@ -16,6 +16,9 @@ .error { background-color: red; } +.text_plain { + white-space: pre-line; +} diff --git a/web/src/lib.rs b/web/src/lib.rs index 32f7f8d..738d4dd 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -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 { 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 { div![body.iter().map(view_part)] } +fn view_text_plain(content: &Option) -> Node { + 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 { 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 { /* 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 { 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| {