diff --git a/web/src/lib.rs b/web/src/lib.rs index c024bc1..32f7f8d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -5,9 +5,8 @@ use log::{error, info, Level}; use seed::{prelude::*, *}; -use serde::Deserialize; -use notmuch::{Message, SearchSummary, ThreadNode, ThreadSet}; +use notmuch::{Content, Part, SearchSummary, ThreadNode, ThreadSet}; // ------ ------ // Init @@ -17,7 +16,7 @@ use notmuch::{Message, SearchSummary, ThreadNode, ThreadSet}; fn init(_: Url, orders: &mut impl Orders) -> Model { orders .skip() - .perform_cmd(async { Msg::SearchResult(search_request("is:unread").await) }); + .perform_cmd(async { Msg::SearchResult(search_request("*").await) }); Model { search_results: None, show_results: None, @@ -120,61 +119,139 @@ async fn show_request(query: &str) -> fetch::Result { // View // ------ ------ +// +// +// +// +// +// +// +// +// +// +// +// +// fn view_message(thread: &ThreadNode) -> Node { - let msg = thread.0.as_ref(); - if let Some(msg) = msg { + let message = thread.0.as_ref().expect("ThreadNode missing Message"); + let children = &thread.1; + div![ + C!["message"], + /* TODO(wathiede): collect all the tags and show them here. */ + div![C!["header"], "From: ", &message.headers.from], + div![C!["header"], "Date: ", &message.headers.date], + div![C!["header"], "To: ", &message.headers.to], 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],], + C!["body"], + match &message.body { + Some(body) => view_body(body.as_slice()), + None => div![""], + }, + ], + children.iter().map(view_message) + ] +} + +fn view_body(body: &[Part]) -> Node { + div![body.iter().map(view_part)] +} + +fn view_part(part: &Part) -> Node { + match part.content_type.as_str() { + "text/plain" => match &part.content { + Some(Content::String(content)) => pre![content], + _ => div![ + C!["error"], + format!("Unhandled content enum for text/plain"), ], - 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)],], - ], - // msg.body.iter().map(|part| pre![part]) - pre![format!("{:#?}", thread)] - ] - } else { - div![h2!["No message"]] + }, + _ => div![ + C!["error"], + format!("Unhandled content type: {}", part.content_type) + ], } } +fn first_subject(thread: &ThreadNode) -> Option { + if let Some(msg) = &thread.0 { + return Some(msg.headers.subject.clone()); + } else { + for tn in &thread.1 { + if let Some(s) = first_subject(&tn) { + return Some(s); + } + } + } + None +} +/* +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)],], + ], + ] +} else { + div![h2!["No message"]] +} +*/ + // `view` describes what to display. fn view(model: &Model) -> Node { let content = if let Some(show_results) = &model.show_results { - div![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) - ]) - ])] + assert_eq!(show_results.0.len(), 1); + let thread = &show_results.0[0]; + assert_eq!(thread.0.len(), 1); + let thread_node = &thread.0[0]; + div![ + h1![first_subject(&thread_node)], + a![ + attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, + "Original" + ], + 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) + ]) + ]), + */ + pre![format!("Thread: {:#?}", show_results).replace(" ", " ")] + ] } else if let Some(search_results) = &model.search_results { let rows = search_results.0.iter().map(|r| { let tid = r.thread.clone(); @@ -198,6 +275,10 @@ fn view(model: &Model) -> Node { "Unread", ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())), ], + button![ + "All", + ev(Ev::Click, |_| Msg::SearchRequest("*".to_string())), + ], input![ attrs! { At::Placeholder => "Search";