1 use std
::collections
::BTreeMap
;
6 use handlebars
::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError}
;
7 use pulldown_cmark
::{html, Event, Parser}
;
9 // Handlebars helper to construct TOC
10 #[derive(Clone, Copy)]
11 pub struct RenderToc
{
12 pub no_section_label
: bool
,
15 impl HelperDef
for RenderToc
{
16 fn call
<'reg
: 'rc
, 'rc
>(
18 _h
: &Helper
<'reg
, 'rc
>,
19 _r
: &'reg Handlebars
<'_
>,
21 rc
: &mut RenderContext
<'reg
, 'rc
>,
23 ) -> Result
<(), RenderError
> {
24 // get value from context data
25 // rc.get_path() is current json parent path, you should always use it like this
26 // param is the key of value you want to display
27 let chapters
= rc
.evaluate(ctx
, "@root/chapters").and_then(|c
| {
28 serde_json
::value
::from_value
::<Vec
<BTreeMap
<String
, String
>>>(c
.as_json().clone())
29 .map_err(|_
| RenderError
::new("Could not decode the JSON data"))
32 .evaluate(ctx
, "@root/path")?
35 .ok_or_else(|| RenderError
::new("Type error for `path`, string expected"))?
38 let current_section
= rc
39 .evaluate(ctx
, "@root/section")?
46 .evaluate(ctx
, "@root/fold_enable")?
49 .ok_or_else(|| RenderError
::new("Type error for `fold_enable`, bool expected"))?
;
52 .evaluate(ctx
, "@root/fold_level")?
55 .ok_or_else(|| RenderError
::new("Type error for `fold_level`, u64 expected"))?
;
57 out
.write("<ol class=\"chapter\">")?
;
59 let mut current_level
= 1;
61 for item
in chapters
{
63 if item
.get("spacer").is_some() {
64 out
.write("<li class=\"spacer\"></li>")?
;
68 let (section
, level
) = if let Some(s
) = item
.get("section") {
69 (s
.as_str(), s
.matches('
.'
).count())
75 if !fold_enable
|| (!section
.is_empty() && current_section
.starts_with(section
)) {
76 // Expand if folding is disabled, or if the section is an
77 // ancestor or the current section itself.
80 // Levels that are larger than this would be folded.
81 level
- 1 < fold_level
as usize
84 if level
> current_level
{
85 while level
> current_level
{
87 out
.write("<ol class=\"section\">")?
;
90 write_li_open_tag(out
, is_expanded
, false)?
;
91 } else if level
< current_level
{
92 while level
< current_level
{
97 write_li_open_tag(out
, is_expanded
, false)?
;
99 write_li_open_tag(out
, is_expanded
, item
.get("section").is_none())?
;
103 if let Some(title
) = item
.get("part") {
104 out
.write("<li class=\"part-title\">")?
;
111 let path_exists
= if let Some(path
) =
113 .and_then(|p
| if p
.is_empty() { None }
else { Some(p) }
)
115 out
.write("<a href=\"")?
;
117 let tmp
= Path
::new(item
.get("path").expect("Error: path should be Some(_)"))
118 .with_extension("html")
121 // Hack for windows who tends to use `\` as separator instead of `/`
125 out
.write(&utils
::fs
::path_to_root(¤t_path
))?
;
129 if path
== ¤t_path
{
130 out
.write(" class=\"active\"")?
;
140 if !self.no_section_label
{
141 // Section does not necessarily exist
142 if let Some(section
) = item
.get("section") {
143 out
.write("<strong aria-hidden=\"true\">")?
;
144 out
.write(§ion
)?
;
145 out
.write("</strong> ")?
;
149 if let Some(name
) = item
.get("name") {
150 // Render only inline code blocks
152 // filter all events that are not inline code blocks
153 let parser
= Parser
::new(name
).filter(|event
| match *event
{
154 Event
::Code(_
) | Event
::Html(_
) | Event
::Text(_
) => true,
158 // render markdown to html
159 let mut markdown_parsed_name
= String
::with_capacity(name
.len() * 3 / 2);
160 html
::push_html(&mut markdown_parsed_name
, parser
);
162 // write to the handlebars template
163 out
.write(&markdown_parsed_name
)?
;
169 out
.write("</div>")?
;
172 // Render expand/collapse toggle
173 if let Some(flag
) = item
.get("has_sub_items") {
174 let has_sub_items
= flag
.parse
::<bool
>().unwrap_or_default();
175 if fold_enable
&& has_sub_items
{
176 out
.write("<a class=\"toggle\"><div>❱</div></a>")?
;
181 while current_level
> 1 {
192 fn write_li_open_tag(
193 out
: &mut dyn Output
,
196 ) -> Result
<(), std
::io
::Error
> {
197 let mut li
= String
::from("<li class=\"chapter-item ");
199 li
.push_str("expanded ");
202 li
.push_str("affix ");