yggdrasil/components/
nav.rs1use crate::components::header::NavItemConfig;
6use crate::routes::{is_showcase_path, Route};
7
8pub fn build_nav_items(path: &str) -> Vec<NavItemConfig> {
18 let path = match path.trim_end_matches('/') {
20 "" => "/",
21 trimmed => trimmed,
22 };
23 vec![
24 NavItemConfig {
25 route: Route::Home,
26 label: "首页",
27 is_active: path == "/" || path.starts_with("/page/"),
28 },
29 NavItemConfig {
30 route: Route::Notes,
31 label: "笔记",
32 is_active: path == "/notes" || path.starts_with("/notes/"),
33 },
34 NavItemConfig {
35 route: Route::Archives,
36 label: "归档",
37 is_active: path == "/archives" || path.starts_with("/tags/"),
38 },
39 NavItemConfig {
40 route: Route::Friends,
41 label: "友链",
42 is_active: path == "/friends",
43 },
44 NavItemConfig {
45 route: Route::About,
46 label: "关于",
47 is_active: is_about_family(path),
48 },
49 ]
50}
51
52fn is_about_family(path: &str) -> bool {
56 matches!(path, "/about" | "/about/writing") || is_showcase_path(path)
57}
58
59#[cfg(test)]
60mod tests {
61 use super::build_nav_items;
62 use crate::routes::Route;
63
64 fn active(path: &str) -> Vec<&'static str> {
65 build_nav_items(path)
66 .into_iter()
67 .filter(|item| item.is_active)
68 .map(|item| item.label)
69 .collect()
70 }
71
72 #[test]
73 fn highlights_by_path() {
74 assert_eq!(active("/"), ["首页"]);
75 assert_eq!(active("/page/2"), ["首页"]);
76 assert_eq!(active("/notes"), ["笔记"]);
77 assert_eq!(active("/notes/book/3"), ["笔记"]);
78 assert_eq!(active("/notes/some-note"), ["笔记"]);
79 assert_eq!(active("/archives"), ["归档"]);
80 assert_eq!(active("/tags/rust"), ["归档"]);
81 assert_eq!(active("/friends"), ["友链"]);
82 assert_eq!(active("/about"), ["关于"]);
83 assert_eq!(active("/about/writing/"), ["关于"]);
84 assert!(active("/post/hello").is_empty());
85 assert!(active("/search").is_empty());
86 assert!(active("/changelog").is_empty());
87 }
88
89 #[test]
90 fn showcase_belongs_to_about_family() {
91 assert_eq!(active("/about/components"), ["关于"]);
92 assert_eq!(active("/about/components/"), ["关于"]);
93 assert_eq!(active("/about/components/button"), ["关于"]);
94 assert!(active("/about/componentsx").is_empty());
96 }
97
98 #[test]
99 fn tags_index_without_slug_is_not_highlighted() {
100 assert!(active("/tags").is_empty());
102 }
103
104 #[test]
105 fn items_keep_fixed_order_and_targets() {
106 let items = build_nav_items("/");
107 let seen: Vec<_> = items
108 .iter()
109 .map(|item| (item.label, item.route.to_string()))
110 .collect();
111 assert_eq!(
112 seen,
113 [
114 ("首页", "/".to_string()),
115 ("笔记", "/notes".to_string()),
116 ("归档", "/archives".to_string()),
117 ("友链", "/friends".to_string()),
118 ("关于", "/about".to_string()),
119 ]
120 );
121 assert_eq!(items[0].route, Route::Home);
122 }
123}