ddoc/config/
page_list.rs

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 == &current_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 == &current_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    /// Generate the HTML for a menu or submenu hosted on a page.
134    #[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}