1use {
2 crate::*,
3 indexmap::IndexMap,
4 serde::{
5 Deserialize,
6 Serialize,
7 },
8 std::fmt::Write,
9};
10
11#[derive(Debug, Clone, Deserialize, Serialize)]
12#[serde(untagged)]
13pub enum ListItem {
14 Page(PagePath),
15 List(PageList),
16}
17
18#[derive(Debug, Clone, Deserialize, Serialize)]
19#[serde(transparent)]
20pub struct PageList {
21 pub items: IndexMap<String, ListItem>,
22}
23
24impl PageList {
25 pub fn first_page_path(&self) -> Option<PagePath> {
26 for item in self.items.values() {
27 match item {
28 ListItem::Page(path) => {
29 return Some(path.clone());
30 }
31 ListItem::List(submenu) => {
32 if let Some(path) = submenu.first_page_path() {
33 return Some(path);
34 }
35 }
36 }
37 }
38 None
39 }
40 pub fn add_pages(
41 &self,
42 project: &mut Project,
43 ) {
44 for (title, item) in &self.items {
45 match item {
46 ListItem::Page(path) => {
47 if !project.pages.contains_key(path) {
48 let md_file_path = path.md_path_buf(&project.src_path);
49 let page = Page::new(title.clone(), path.clone(), md_file_path);
50 project.pages.insert(path.clone(), page);
51 }
52 }
53 ListItem::List(submenu) => {
54 submenu.add_pages(project);
55 }
56 }
57 }
58 }
59 pub fn add_page_paths<'m>(
60 &'m self,
61 list: &mut Vec<&'m PagePath>,
62 ) {
63 for item in self.items.values() {
64 match item {
65 ListItem::Page(path) => {
66 if !list.contains(&path) {
67 list.push(path);
68 }
69 }
70 ListItem::List(submenu) => {
71 submenu.add_page_paths(list);
72 }
73 }
74 }
75 }
76 pub fn previous(
77 &self,
78 current_page: &PagePath,
79 ) -> Option<&PagePath> {
80 let mut page_paths = Vec::new();
81 self.add_page_paths(&mut page_paths);
82 for (i, path) in page_paths.iter().enumerate() {
83 if path == ¤t_page {
84 if i > 0 {
85 return Some(page_paths[i - 1]);
86 } else {
87 return None;
88 }
89 }
90 }
91 None
92 }
93 pub fn next(
94 &self,
95 current_page: &PagePath,
96 ) -> Option<&PagePath> {
97 let mut page_paths = Vec::new();
98 self.add_page_paths(&mut page_paths);
99 for (i, path) in page_paths.iter().enumerate() {
100 if path == ¤t_page {
101 if i + 1 < page_paths.len() {
102 return Some(page_paths[i + 1]);
103 } else {
104 return None;
105 }
106 }
107 }
108 None
109 }
110 pub fn push_nav(
111 &self,
112 html: &mut String,
113 classes: &[ClassName],
114 menu_insert: &Menu,
115 hosting_page_path: &PagePath,
116 ) -> DdResult<()> {
117 html.push_str("<nav class=\"site-nav");
118 for class in classes {
119 html.push(' ');
120 html.push_str(class.as_str());
121 }
122 html.push_str("\">\n");
123 if menu_insert.hamburger_checkbox {
124 html.push_str(
125 "<input type=checkbox id=nav-toggle class=nav-toggle>\n\
126 <label for=nav-toggle class=nav-toggle-label>☰</label>\n",
127 );
128 }
129 self.push_nav_item_html(html, hosting_page_path);
130 html.push_str("</nav>\n");
131 Ok(())
132 }
133 #[allow(clippy::only_used_in_recursion)]
135 fn push_nav_item_html(
136 &self,
137 html: &mut String,
138 hosting_page_path: &PagePath,
139 ) {
140 html.push_str("<ul class=\"nav-menu\">\n");
141 for (title, item) in &self.items {
142 let (link, selected) = match item {
143 ListItem::Page(path) => {
144 (hosting_page_path.link_to(path), path == hosting_page_path)
145 }
146 ListItem::List(submenu) => {
147 let first_page_path = submenu.first_page_path();
148 let link = first_page_path
149 .as_ref()
150 .map(|p| hosting_page_path.link_to(p))
151 .unwrap_or_else(|| "#".to_string());
152 (link, false)
153 }
154 };
155 let selected_class = if selected { "selected" } else { "not-selected" };
156 let _ = writeln!(
157 html,
158 "<li class=\"nav-item {}\"><a href=\"{}\">{}</a>",
159 selected_class, link, title,
160 );
161 if let ListItem::List(submenu) = item {
162 submenu.push_nav_item_html(html, hosting_page_path);
163 }
164 html.push_str("</li>\n");
165 }
166 html.push_str("</ul>\n");
167 }
168}
169
170impl ListItem {
171 pub fn first_page_path(&self) -> Option<PagePath> {
172 match self {
173 ListItem::Page(path) => Some(path.clone()),
174 ListItem::List(submenu) => submenu.first_page_path(),
175 }
176 }
177}