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