diff --git a/server/src/nm.rs b/server/src/nm.rs index ed7ce26..f690fd2 100644 --- a/server/src/nm.rs +++ b/server/src/nm.rs @@ -223,7 +223,7 @@ pub async fn thread( } format!( - r#"

{}

"#, + r#"

{}

"#, // Trim newlines to prevent excessive white space at the beginning/end of // presenation. Leave tabs and spaces incase plain text attempts to center a // header on the first line. @@ -578,7 +578,7 @@ fn flatten_body_parts(parts: &[Body]) -> Body { .map(|p| match p { Body::PlainText(PlainText { text, .. }) => { format!( - r#"

{}

"#, + r#"

{}

"#, // Trim newlines to prevent excessive white space at the beginning/end of // presenation. Leave tabs and spaces incase plain text attempts to center a // header on the first line. diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index 73e0708..d2d9f04 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -30,7 +30,7 @@ mod tw_classes { ]; pub const TAG_X: &[&str] = &[ "rounded-r", - "bg-slate-800", + "bg-neutral-800", "px-2", "py-1", "mr-1", @@ -38,17 +38,17 @@ mod tw_classes { "[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]", ]; pub const BUTTON: &[&str] = &[ - "bg-slate-900", + "bg-neutral-900", "rounded-md", "p-2", "border", - "border-slate-700", + "border-neutral-700", "text-center", "text-sm", "transition-all", "shadow-md", "hover:shadow-lg", - "hover:bg-slate-700", + "hover:bg-neutral-700", "disabled:pointer-events-none", "disabled:opacity-50", "disabled:shadow-none", @@ -62,7 +62,7 @@ mod tw_classes { "checked:appearance-auto", "rounded", "border", - "border-gray-500", + "border-neutral-500", ]; } @@ -133,7 +133,7 @@ fn search_results( "flex-auto", "py-4", "border-b", - "border-gray-800" + "border-neutral-800" ], div![ C!["flex", "items-center", "mr-4"], @@ -432,14 +432,12 @@ fn search_toolbar( pager: &FrontPageQuerySearchPageInfo, show_bulk_edit: bool, ) -> Node { - info!("pager {pager:#?}"); nav![ C!["p-4", "flex", "w-full", "justify-between"], div![ C!["gap-2", "flex"], IF!(show_bulk_edit => div![ - div![ button![ C![&tw_classes::BUTTON, "rounded-r-none"], attrs!{At::Title => "Mark as read"}, @@ -454,11 +452,9 @@ fn search_toolbar( span![C!["pl-2", "hidden", "md:inline"], "Unread"], ev(Ev::Click, |_| Msg::SelectionMarkAsUnread) ] - ] ]), IF!(show_bulk_edit => div![ - div![ button![ C![&tw_classes::BUTTON, "text-red-500"], attrs!{At::Title => "Mark as spam"}, @@ -470,8 +466,7 @@ fn search_toolbar( Msg::SelectionMarkAsRead ]) ) - ], - ], + ] ]), ], div![ @@ -503,7 +498,7 @@ fn raw_text_message(contents: &str) -> Node { (contents, None) }; div![ - C!["NOTPORTED", "view-part-text-plain"], + C!["view-part-text-plain", "font-mono", "whitespace-pre"], contents, truncated_msg, ] @@ -582,7 +577,7 @@ fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node< let avatar = render_avatar(photo_url, &from, true); let unknown = "UNKNOWN".to_string(); div![ - C!["flex", "p-4"], + C!["flex", "p-4", "bg-neutral-800"], div![avatar], div![ C!["px-4", "mr-auto"], @@ -679,7 +674,7 @@ fn render_closed_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Nod let avatar = render_avatar(photo_url, &from, false); let unknown = "UNKNOWN".to_string(); div![ - C!["flex", "p-4"], + C!["flex", "p-4", "bg-neutral-800"], div![avatar], div![ C!["px-4", "mr-auto"], @@ -754,6 +749,7 @@ fn render_closed_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Nod fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) -> Node { let expand_id = msg.id.clone(); div![ + C!["lg:mb-4"], div![ if open { render_open_header(&msg) @@ -771,13 +767,13 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) ], IF!(open => div![ - C![], + C!["bg-white", "text-black", "p-4", "min-w-full", "w-0","overflow-x-auto"], match &msg.body { ShowThreadQueryThreadOnEmailThreadMessagesBody::UnhandledContentType( ShowThreadQueryThreadOnEmailThreadMessagesBodyOnUnhandledContentType { contents ,content_tree}, ) => div![ raw_text_message(&contents), - div![C!["NOTPORTED","error"], + div![C!["bg-red-500","p-4", "min-w-full", "w-0", "overflow-x-auto"], view_content_tree(&content_tree), ] ], @@ -796,59 +792,9 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) content_tree, }, ) => div![ - C!["NOTPORTED","view-part-text-html"], + C!["view-part-text-html"], raw![contents], - IF!(!msg.attachments.is_empty() => - div![ - C!["NOTPORTED","attachments"], - hr![], - h2!["NOTPORTED","Attachments"], - div![C!["NOTPORTED","grid","is-col-min-6"], - msg.attachments - .iter() - .map(|a| { - let default = "UNKNOWN_FILE".to_string(); - let filename = a.filename.as_ref().unwrap_or(&default); - let host = seed::window().location().host().expect("couldn't get host"); - let url = shared::urls::download_attachment(Some(&host), &a.id, &a.idx, filename); - let mut fmtr = Formatter::new(); - fmtr.with_separator(" "); - fmtr.with_scales(Scales::Binary()); - - div![ - C!["NOTPORTED","attachment", "card"], - a.content_type.as_ref().map(|content_type| - IF!(content_type.starts_with("image/") => - div![C!["NOTPORTED","card-image","is-1by1"], - div![ - C!["NOTPORTED","image","is-1by1"], - style!{ - St::BackgroundImage=>format!(r#"url("{url}");"#), - St::BackgroundSize=>"cover", - St::BackgroundPosition=>"center", - } - ] - ] - )), - div![C!["NOTPORTED","card-content"], - div![C!["NOTPORTED","content"], - &a.filename, br![], - small![ fmtr.format(a.size as f64),"B"] - ] - ], - footer![ - C!["NOTPORTED","card-footer"], - a![C!["NOTPORTED","card-footer-item"],span![C!["NOTPORTED","icon"], i![C!["NOTPORTED","fas", "fa-download"]]], - ev(Ev::Click, move |_| { - seed::window().location().set_href(&url - ).expect("failed to set URL"); - }) - ] - ] - ] - }) - ] - ]), + IF!(!msg.attachments.is_empty() => render_attachements(&msg.attachments)), view_content_tree(&content_tree), ], } @@ -856,14 +802,76 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool) ] } +fn render_attachements( + attachments: &[ShowThreadQueryThreadOnEmailThreadMessagesAttachments], +) -> Node { + div![ + C!["border-t", "border-neutral-200", "mt-2", "pt-2"], + h2![C!["text-lg"], "Attachments"], + div![ + C!["flex", "flex-wrap", "gap-2"], + attachments.iter().map(|a| { + let default = "UNKNOWN_FILE".to_string(); + let filename = a.filename.as_ref().unwrap_or(&default); + let host = seed::window().location().host().expect("couldn't get host"); + let url = shared::urls::download_attachment(Some(&host), &a.id, &a.idx, filename); + let mut fmtr = Formatter::new(); + fmtr.with_separator(" "); + fmtr.with_scales(Scales::Binary()); + + div![ + C![ + "flex", + "flex-col", + "flex-none", + "p-2", + "bg-neutral-200", + "border", + "border-neutral-400", + ], + a.content_type.as_ref().map( + |content_type| IF!(content_type.starts_with("image/") => img![ + C!["w-32", "h-32", "md:w-64", "md:h-64", "object-cover"], + attrs!{At::Src=>url}, + ]) + ), + div![ + C!["py-2", "flex", "flex-nowrap", "items-center"], + div![ + C!["flex", "flex-col", "grow", "w-16", "md:w-32"], + span![C!["shrink", "truncate"], &a.filename], + span![C!["text-xs"], fmtr.format(a.size as f64), "B"] + ], + a![ + C![ + "aspect-square", + "px-2", + "pt-1", + "bg-neutral-300", + "border", + "border-neutral-400" + ], + span![i![C!["fas", "fa-download"]]], + ev(Ev::Click, move |_| { + seed::window() + .location() + .set_href(&url) + .expect("failed to set URL"); + }) + ] + ] + ] + }) + ] + ] +} + #[topo::nested] fn thread( thread: &ShowThreadQueryThreadOnEmailThread, open_messages: &HashSet, content_el: &ElRef, ) -> Node { - // TODO remove and replace with CSS styling - let show_icon_text = true; // TODO(wathiede): show per-message subject if it changes significantly from top-level subject let subject = if thread.subject.is_empty() { "(no subject)" @@ -890,68 +898,49 @@ fn thread( let spam_add_thread_id = thread.thread_id.clone(); let spam_unread_thread_id = thread.thread_id.clone(); div![ - C!["p-4"], - h3![C!["text-xl"], subject], - span![ - C!["NOTPORTED", "tags"], - removable_tags_chiclet(&thread.thread_id, &tags, false) - ], + C!["lg:p-4"], div![ - C!["NOTPORTED", "level", "is-mobile"], + C!["p-4", "lg:p-0"], + h3![C!["text-xl"], subject], + span![removable_tags_chiclet(&thread.thread_id, &tags, false)], div![ - C!["NOTPORTED", "level-item"], + C!["pt-4", "gap-2", "flex", "justify-around"], div![ - C!["NOTPORTED", "buttons", "has-addons"], button![ - C!["NOTPORTED", "button", "mark-read"], + C![&tw_classes::BUTTON, "rounded-r-none"], attrs! {At::Title => "Mark as read"}, - span![ - C!["NOTPORTED", "icon", "is-small"], - i![C!["NOTPORTED", "far", "fa-envelope-open"]] - ], - IF!(show_icon_text=>span!["Read"]), + span![i![C!["far", "fa-envelope-open"]]], + span![C!["pl-2", "hidden", "md:inline"], "Read"], ev(Ev::Click, move |_| Msg::MultiMsg(vec![ Msg::SetUnread(read_thread_id, false), Msg::GoToSearchResults ])), ], button![ - C!["NOTPORTED", "button", "mark-unread"], + C![&tw_classes::BUTTON, "rounded-l-none"], attrs! {At::Title => "Mark as unread"}, - span![ - C!["NOTPORTED", "icon", "is-small"], - i![C!["NOTPORTED", "far", "fa-envelope"]] - ], - IF!(show_icon_text=>span!["Unread"]), + span![i![C!["far", "fa-envelope"]]], + span![C!["pl-2", "hidden", "md:inline"], "Unread"], ev(Ev::Click, move |_| Msg::MultiMsg(vec![ Msg::SetUnread(unread_thread_id, true), Msg::GoToSearchResults ])), ], ], - ], - div![ - C!["NOTPORTED", "level-item"], - div![ - C!["NOTPORTED", "buttons", "has-addons"], - button![ - C!["NOTPORTED", "button", "spam"], - attrs! {At::Title => "Spam"}, - span![ - C!["NOTPORTED", "icon", "is-small"], - i![C!["NOTPORTED", "far", "fa-hand"]] - ], - IF!(show_icon_text=>span!["Spam"]), - ev(Ev::Click, move |_| Msg::MultiMsg(vec![ - Msg::AddTag(spam_add_thread_id, "Spam".to_string()), - Msg::SetUnread(spam_unread_thread_id, false), - Msg::GoToSearchResults - ])), - ], - ], + div![button![ + C![&tw_classes::BUTTON, "text-red-500"], + attrs! {At::Title => "Spam"}, + span![i![C!["far", "fa-hand"]]], + span![C!["pl-2", "hidden", "md:inline"], "Spam"], + ev(Ev::Click, move |_| Msg::MultiMsg(vec![ + Msg::AddTag(spam_add_thread_id, "Spam".to_string()), + Msg::SetUnread(spam_unread_thread_id, false), + Msg::GoToSearchResults + ])), + ]] ], ], - div![el_ref(content_el), messages, click_to_top()], + div![C!["lg:mt-4"], el_ref(content_el), messages, click_to_top()], /* TODO(wathiede): plumb in orignal id a![ attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, @@ -998,7 +987,7 @@ fn view_header( }; let query = Url::decode_uri_component(query).unwrap_or("".to_string()); nav![ - C!["flex", "p-4"], + C!["flex", "px-4", "pt-4"], a![ C![IF![is_error => "bg-red-500"], "rounded-r-none"], C![&tw_classes::BUTTON], @@ -1081,7 +1070,7 @@ pub fn tags(model: &Model) -> Node { "self-center", "justify-self-end", "text-sm", - "text-gray-400" + "text-neutral-400" ], IF!(t.unread>0 => format!("{}", t.unread)) ], @@ -1305,7 +1294,7 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node div![ C!["px-4", "mr-auto"], div![ - span![C!["font-semibold", "text-sm"], from,], + span![C!["font-semibold", "text-sm"], from], div![ C!["text-xs"], small![a![ @@ -1341,15 +1330,23 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node } fn reading_progress(ratio: f64) -> Node { let percent = ratio * 100.; - // TODO: percent broken with no styles info!("percent {percent}"); - progress![ - C!["absolute", "w-screen", IF!(percent<1. => "hidden")], - attrs! { - At::Value=>percent, - At::Max=>"100" - }, - format!("{percent}%") + div![ + C![ + "fixed", + "top-0", + "left-0", + "w-screen", + "h-1", + "bg-gray-200", + IF!(percent<1. => "hidden") + ], + div![ + C!["h-1", "bg-green-500"], + style! { + St::Width => format!("{}%", percent) + } + ] ] } pub fn versions(versions: &crate::state::Version) -> Node { @@ -1372,12 +1369,9 @@ pub fn versions(versions: &crate::state::Version) -> Node { fn click_to_top() -> Node { button![ - C!["NOTPORTED", "button", "is-danger", "is-small"], + C![&tw_classes::BUTTON, "bg-red-500", "lg:m-0", "m-4"], span!["Top"], - span![ - C!["NOTPORTED", "icon"], - i![C!["NOTPORTED", "fas", "fa-arrow-turn-up"]] - ], + span![i![C!["fas", "fa-arrow-turn-up"]]], ev(Ev::Click, |_| web_sys::window() .unwrap() .scroll_to_with_x_and_y(0., 0.))