ddoc/config/
nav_component.rs

1use {
2    crate::*,
3    indexmap::IndexMap,
4    serde::{
5        Deserialize,
6        Serialize,
7    },
8};
9
10/// Deprecated navigation components structure for compatibility with ddoc <= 0.11
11#[derive(Debug, Clone, Deserialize)]
12pub struct NavComponents {
13    #[serde(default)]
14    header: NavDir,
15    #[serde(default)]
16    footer: NavDir,
17    #[serde(default)]
18    ui: UiOptions,
19}
20
21/// Either the header or footer navigation configuration for ddoc <= 0.11
22#[derive(Debug, Clone, Default, Deserialize, Serialize)]
23#[serde(transparent)]
24struct NavDir {
25    components: IndexMap<ClassName, NavComponent>,
26}
27
28#[derive(Debug, Clone, Deserialize, Serialize)]
29#[serde(untagged)]
30enum NavComponent {
31    Menu(Menu),
32    NavLinks(Vec<NavLink>),
33}
34
35impl NavComponents {
36    /// Build a `ElementList` from old-style `NavComponents`, adding the
37    /// parts which were implicit in ddoc <= 0.11
38    pub fn into_body_composite(&self) -> ElementList {
39        let mut children = Vec::new();
40        if !self.header.is_empty() {
41            children.push(Element::new_composite(
42                "header",
43                self.header.to_children(&self.ui),
44            ));
45        }
46        children.push(Element::new_composite(
47            "article",
48            vec![
49                Element::new_composite("aside.page-nav", vec![ElementContent::Toc.into()]),
50                ElementContent::Main.into(),
51            ],
52        ));
53        if !self.footer.is_empty() {
54            children.push(Element::new_composite(
55                "footer",
56                self.footer.to_children(&self.ui),
57            ));
58        }
59        ElementList { children }
60    }
61}
62impl NavDir {
63    pub fn is_empty(&self) -> bool {
64        self.components.is_empty()
65    }
66    fn to_children(
67        &self,
68        ui: &UiOptions,
69    ) -> Vec<Element> {
70        let mut children = Vec::new();
71        for (class, comp) in &self.components {
72            match comp {
73                NavComponent::Menu(_) => {
74                    let menu_insert = Menu {
75                        hamburger_checkbox: ui.hamburger_checkbox,
76                    };
77                    children.push(Element {
78                        classes: vec![class.clone()],
79                        content: ElementContent::Menu(menu_insert),
80                    });
81                }
82                NavComponent::NavLinks(links) => {
83                    let mut nav_children = Vec::new();
84                    for link in links {
85                        nav_children.push(ElementContent::Link(link.clone()).into());
86                    }
87                    let mut nav = Element::new_composite("nav", nav_children);
88                    nav.classes.push(class.clone());
89                    children.push(nav);
90                }
91            }
92        }
93        children
94    }
95}
96
97#[test]
98fn test_nav_component_deserialize() {
99    #[derive(Debug, Clone, Deserialize, Serialize)]
100    struct TestConfig {
101        header: NavDir,
102        footer: NavDir,
103    }
104    let hjson = r#"
105        header: {
106            before-menu: [
107                {
108                    img: img/dystroy-rust-white.svg
109                    href: https://dystroy.org
110                    alt: dystroy.org homepage
111                    class: external-nav-link
112                }
113                {
114                    url: /index.md
115                    alt: ddoc homepage
116                    label: ddoc
117                    class: home-link
118                }
119            ]
120            middle: menu
121            after-menu: [
122                {
123                    img: img/ddoc-left-arrow.svg
124                    href: --previous
125                    class: previous-page-link
126                    alt: Previous Page
127                }
128                {
129                    img: img/ddoc-search.svg
130                    href: --search
131                    class: search-opener
132                    alt: Search
133                }
134                {
135                    img: img/ddoc-right-arrow.svg
136                    url: --next
137                    class: next-page-link
138                    alt: Next Page
139                }
140                {
141                    img: img/github-mark-white.svg
142                    class: external-nav-link
143                    alt: GitHub
144                    href: https://github.com/Canop/ddoc
145                }
146            ]
147        }
148        footer: {
149            right: [
150                {
151                    label: made with **ddoc**
152                    href: https://dystroy.org/ddoc
153                }
154            ]
155        }
156    "#;
157    let test_config: TestConfig = deser_hjson::from_str(hjson).unwrap();
158    assert_eq!(test_config.header.components.len(), 3);
159    assert_eq!(test_config.footer.components.len(), 1);
160    assert!(matches!(
161        test_config.header.components.get("middle").unwrap(),
162        NavComponent::Menu(_)
163    ));
164    let NavComponent::NavLinks(links) = test_config.header.components.get("after-menu").unwrap()
165    else {
166        panic!("Expected NavLinks");
167    };
168    assert_eq!(links[2].href.as_deref(), Some("--next"));
169}