diff --git a/web/src/view/desktop.rs b/web/src/view/desktop.rs index 05d90ee..66dfc2f 100644 --- a/web/src/view/desktop.rs +++ b/web/src/view/desktop.rs @@ -3,8 +3,8 @@ use seed_hooks::{state_access::CloneState, topo, use_state}; use crate::{ api::urls, - state::{Context, Model, Msg, Tag}, - view::{self, view_header, view_search_results}, + state::{Context, Model, Msg}, + view::{self, view_header, view_search_results, view_tags}, }; #[topo::nested] @@ -33,100 +33,9 @@ pub(super) fn view(model: &Model) -> Node { show_icon_text, ), }; - fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node { - let href = if search_unread { - urls::search(&format!("is:unread tag:{}", t.name), 0) - } else { - urls::search(&format!("tag:{}", t.name), 0) - }; - li![a![ - attrs! { - At::Href => href - }, - (0..indent).map(|_| span![C!["tag-indent"], ""]), - i![ - C!["tag-tag", "fa-solid", "fa-tag"], - style! { - //"--fa-primary-color" => t.fg_color, - St::Color => t.bg_color, - }, - ], - display_name, - IF!(t.unread>0 => format!(" ({})", t.unread)) - ]] - } - fn matches(a: &[&str], b: &[&str]) -> usize { - std::iter::zip(a.iter(), b.iter()) - .take_while(|(a, b)| a == b) - .count() - } - fn view_tag_list<'a>( - tags: impl Iterator, - search_unread: bool, - ) -> Vec> { - let mut lis = Vec::new(); - let mut last = Vec::new(); - for t in tags { - let parts: Vec<_> = t.name.split('/').collect(); - let mut n = matches(&last, &parts); - if n <= parts.len() - 2 && parts.len() > 1 { - // Synthesize fake tags for proper indenting. - for i in n..parts.len() - 1 { - let display_name = parts[n]; - lis.push(view_tag_li( - &display_name, - n, - &Tag { - name: parts[..i + 1].join("/"), - bg_color: "#fff".to_string(), - fg_color: "#000".to_string(), - unread: 0, - }, - search_unread, - )); - } - n = parts.len() - 1; - } - let display_name = parts[n]; - lis.push(view_tag_li(&display_name, n, t, search_unread)); - last = parts; - } - lis - } - let unread = model - .tags - .as_ref() - .map(|tags| tags.iter().filter(|t| t.unread > 0).collect()) - .unwrap_or(Vec::new()); - let tags_open = use_state(|| false); - let force_tags_open = unread.is_empty(); div![ C!["main-content"], - aside![ - C!["tags-menu", "menu"], - IF!(!unread.is_empty() => p![C!["menu-label"], "Unread"]), - IF!(!unread.is_empty() => ul![C!["menu-list"], view_tag_list(unread.into_iter(),true)]), - p![ - C!["menu-label"], - IF!(!force_tags_open => - i![C![ - "fa-solid", - if tags_open.get() { - "fa-angle-up" - } else { - "fa-angle-down" - } - ]]), - " Tags", - ev(Ev::Click, move |_| { - tags_open.set(!tags_open.get()); - }) - ], - ul![ - C!["menu-list"], - IF!(force_tags_open||tags_open.get() => model.tags.as_ref().map(|tags| view_tag_list(tags.iter(),false))), - ] - ], + view_tags(model), div![ view_header(&model.query, &model.refreshing_state), content, diff --git a/web/src/view/mobile.rs b/web/src/view/mobile.rs index 3980df8..b9b7c48 100644 --- a/web/src/view/mobile.rs +++ b/web/src/view/mobile.rs @@ -6,7 +6,10 @@ use crate::{ api::urls, graphql::front_page_query::*, state::{Context, Model, Msg}, - view::{self, human_age, pretty_authors, search_toolbar, set_title, tags_chiclet, view_header}, + view::{ + self, human_age, pretty_authors, search_toolbar, set_title, tags_chiclet, view_header, + view_tags, + }, }; pub(super) fn view(model: &Model) -> Node { @@ -37,6 +40,7 @@ pub(super) fn view(model: &Model) -> Node { view_header(&model.query, &model.refreshing_state), content, view_header(&model.query, &model.refreshing_state), + view_tags(model), ] } diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index 14ade30..692ad04 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -13,7 +13,7 @@ use seed_hooks::{state_access::CloneState, topo, use_state}; use crate::{ api::urls, graphql::{front_page_query::*, show_thread_query::*}, - state::{unread_query, Model, Msg, RefreshingState}, + state::{unread_query, Model, Msg, RefreshingState, Tag}, }; mod desktop; @@ -1000,3 +1000,97 @@ pub fn view(model: &Model) -> Node { _ => div![C!["desktop"], desktop::view(model)], },] } +pub fn view_tags(model: &Model) -> Node { + fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node { + let href = if search_unread { + urls::search(&format!("is:unread tag:{}", t.name), 0) + } else { + urls::search(&format!("tag:{}", t.name), 0) + }; + li![a![ + attrs! { + At::Href => href + }, + (0..indent).map(|_| span![C!["tag-indent"], ""]), + i![ + C!["tag-tag", "fa-solid", "fa-tag"], + style! { + //"--fa-primary-color" => t.fg_color, + St::Color => t.bg_color, + }, + ], + display_name, + IF!(t.unread>0 => format!(" ({})", t.unread)) + ]] + } + fn matches(a: &[&str], b: &[&str]) -> usize { + std::iter::zip(a.iter(), b.iter()) + .take_while(|(a, b)| a == b) + .count() + } + fn view_tag_list<'a>( + tags: impl Iterator, + search_unread: bool, + ) -> Vec> { + let mut lis = Vec::new(); + let mut last = Vec::new(); + for t in tags { + let parts: Vec<_> = t.name.split('/').collect(); + let mut n = matches(&last, &parts); + if n <= parts.len() - 2 && parts.len() > 1 { + // Synthesize fake tags for proper indenting. + for i in n..parts.len() - 1 { + let display_name = parts[n]; + lis.push(view_tag_li( + &display_name, + n, + &Tag { + name: parts[..i + 1].join("/"), + bg_color: "#fff".to_string(), + fg_color: "#000".to_string(), + unread: 0, + }, + search_unread, + )); + } + n = parts.len() - 1; + } + let display_name = parts[n]; + lis.push(view_tag_li(&display_name, n, t, search_unread)); + last = parts; + } + lis + } + let unread = model + .tags + .as_ref() + .map(|tags| tags.iter().filter(|t| t.unread > 0).collect()) + .unwrap_or(Vec::new()); + let tags_open = use_state(|| false); + let force_tags_open = unread.is_empty(); + aside![ + C!["tags-menu", "menu"], + IF!(!unread.is_empty() => p![C!["menu-label"], "Unread"]), + IF!(!unread.is_empty() => ul![C!["menu-list"], view_tag_list(unread.into_iter(),true)]), + p![ + C!["menu-label"], + IF!(!force_tags_open => + i![C![ + "fa-solid", + if tags_open.get() { + "fa-angle-up" + } else { + "fa-angle-down" + } + ]]), + " Tags", + ev(Ev::Click, move |_| { + tags_open.set(!tags_open.get()); + }) + ], + ul![ + C!["menu-list"], + IF!(force_tags_open||tags_open.get() => model.tags.as_ref().map(|tags| view_tag_list(tags.iter(),false))), + ] + ] +} diff --git a/web/src/view/tablet.rs b/web/src/view/tablet.rs index f35f008..b9a6354 100644 --- a/web/src/view/tablet.rs +++ b/web/src/view/tablet.rs @@ -2,7 +2,7 @@ use seed::{prelude::*, *}; use crate::{ state::{Context, Model, Msg}, - view::{self, view_header, view_search_results}, + view::{self, view_header, view_search_results, view_tags}, }; pub(super) fn view(model: &Model) -> Node { @@ -36,6 +36,7 @@ pub(super) fn view(model: &Model) -> Node { view_header(&model.query, &model.refreshing_state), content, view_header(&model.query, &model.refreshing_state), + view_tags(model), ] ] }