forc_doc/render/item/
components.rs

1//! Handles creation of the head and body of an HTML doc.
2use crate::{
3    doc::module::ModuleInfo,
4    render::{
5        item::context::ItemContext,
6        search::generate_searchbar,
7        sidebar::{Sidebar, SidebarNav},
8        DocStyle, Renderable, IDENTITY,
9    },
10    RenderPlan, ASSETS_DIR_NAME,
11};
12use anyhow::Result;
13use horrorshow::{box_html, Raw, RenderBox};
14
15use sway_types::BaseIdent;
16
17use super::documentable_type::DocumentableType;
18
19// Asset file names to avoid repeated string formatting
20const SWAY_LOGO_FILE: &str = "sway-logo.svg";
21const NORMALIZE_CSS_FILE: &str = "normalize.css";
22const SWAYDOC_CSS_FILE: &str = "swaydoc.css";
23const AYU_CSS_FILE: &str = "ayu.css";
24const AYU_MIN_CSS_FILE: &str = "ayu.min.css";
25
26/// All necessary components to render the header portion of
27/// the item html doc.
28#[derive(Clone, Debug)]
29pub struct ItemHeader {
30    pub module_info: ModuleInfo,
31    pub friendly_name: &'static str,
32    pub item_name: BaseIdent,
33}
34impl Renderable for ItemHeader {
35    /// Basic HTML header component
36    fn render(self, _render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
37        let ItemHeader {
38            module_info,
39            friendly_name,
40            item_name,
41        } = self;
42
43        let favicon = module_info
44            .to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/{SWAY_LOGO_FILE}"));
45        let normalize = module_info
46            .to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/{NORMALIZE_CSS_FILE}"));
47        let swaydoc = module_info
48            .to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/{SWAYDOC_CSS_FILE}"));
49        let ayu =
50            module_info.to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/{AYU_CSS_FILE}"));
51        let ayu_hjs = module_info
52            .to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/{AYU_MIN_CSS_FILE}"));
53
54        Ok(box_html! {
55            head {
56                meta(charset="utf-8");
57                meta(name="viewport", content="width=device-width, initial-scale=1.0");
58                meta(name="generator", content="swaydoc");
59                meta(
60                    name="description",
61                    content=format!(
62                        "API documentation for the Sway `{}` {} in `{}`.",
63                        item_name.as_str(), friendly_name, module_info.location(),
64                    )
65                );
66                meta(name="keywords", content=format!("sway, swaylang, sway-lang, {}", item_name.as_str()));
67                link(rel="icon", href=favicon);
68                title: format!("{} in {} - Sway", item_name.as_str(), module_info.location());
69                link(rel="stylesheet", type="text/css", href=normalize);
70                link(rel="stylesheet", type="text/css", href=swaydoc, id="mainThemeStyle");
71                link(rel="stylesheet", type="text/css", href=ayu);
72                link(rel="stylesheet", href=ayu_hjs);
73                // TODO: Add links for fonts
74            }
75        })
76    }
77}
78
79/// All necessary components to render the body portion of
80/// the item html doc. Many parts of the HTML body structure will be the same
81/// for each item, but things like struct fields vs trait methods will be different.
82#[derive(Clone, Debug)]
83pub struct ItemBody {
84    pub module_info: ModuleInfo,
85    pub ty: DocumentableType,
86    /// The item name varies depending on type.
87    /// We store it during info gathering to avoid
88    /// multiple match statements.
89    pub item_name: BaseIdent,
90    pub code_str: String,
91    pub attrs_opt: Option<String>,
92    pub item_context: ItemContext,
93}
94impl SidebarNav for ItemBody {
95    fn sidebar(&self) -> Sidebar {
96        let style = DocStyle::Item {
97            title: Some(self.ty.as_block_title()),
98            name: Some(self.item_name.clone()),
99        };
100        Sidebar::new(
101            None,
102            style,
103            self.module_info.clone(),
104            self.item_context.to_doclinks(),
105        )
106    }
107}
108impl Renderable for ItemBody {
109    /// HTML body component
110    fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
111        let sidebar = self.sidebar();
112        let ItemBody {
113            module_info,
114            ty,
115            item_name,
116            code_str,
117            attrs_opt,
118            item_context,
119        } = self;
120
121        let doc_name = ty.doc_name().to_string();
122        let block_title = ty.as_block_title();
123        let sidebar = sidebar.render(render_plan.clone())?;
124        let item_context = (item_context.context_opt.is_some()
125            || item_context.impl_traits.is_some())
126        .then(|| -> Result<Box<dyn RenderBox>> { item_context.render(render_plan.clone()) });
127        let sway_hjs =
128            module_info.to_html_shorthand_path_string(&format!("{ASSETS_DIR_NAME}/highlight.js"));
129        let rendered_module_anchors = module_info.get_anchors()?;
130
131        Ok(box_html! {
132            body(class=format!("swaydoc {doc_name}")) {
133                : sidebar;
134                // this is the main code block
135                main {
136                    div(class="width-limiter") {
137                        : generate_searchbar(&module_info);
138                        section(id="main-content", class="content") {
139                            div(class="main-heading") {
140                                h1(class="fqn") {
141                                    span(class="in-band") {
142                                        : format!("{} ", block_title.item_title_str());
143                                        @ for anchor in rendered_module_anchors {
144                                            : Raw(anchor);
145                                        }
146                                        a(class=&doc_name, href=IDENTITY) {
147                                            : item_name.as_str();
148                                        }
149                                    }
150                                }
151                            }
152                            div(class="docblock item-decl") {
153                                pre(class=format!("sway {}", &doc_name)) {
154                                    code { : code_str; }
155                                }
156                            }
157                            @ if attrs_opt.is_some() {
158                                // expand or hide description of main code block
159                                details(class="swaydoc-toggle top-doc", open) {
160                                    summary(class="hideme") {
161                                        span { : "Expand description" }
162                                    }
163                                    // this is the description
164                                    div(class="docblock") {
165                                        : Raw(attrs_opt.unwrap())
166                                    }
167                                }
168                            }
169                            @ if item_context.is_some() {
170                                : item_context.unwrap();
171                            }
172                        }
173                        section(id="search", class="search-results");
174                    }
175                }
176                script(src=sway_hjs);
177                script {
178                    : "hljs.highlightAll();";
179                }
180            }
181        })
182    }
183}