use crate::utils::ToUrlPath; use handlebars::{ Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason, }; use mdbook_core::utils::escape_html_attribute; use std::path::Path; use std::{cmp::Ordering, collections::BTreeMap}; // Handlebars helper to construct TOC #[derive(Clone, Copy)] pub(crate) struct RenderToc { pub no_section_label: bool, } impl HelperDef for RenderToc { fn call<'reg: 'rc, 'rc>( &self, _h: &Helper<'rc>, _r: &'reg Handlebars<'_>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> Result<(), RenderError> { // get value from context data // rc.get_path() is current json parent path, you should always use it like this // param is the key of value you want to display let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| { serde_json::value::from_value::>>(c.as_json().clone()) .map_err(|_| { RenderErrorReason::Other("Could not decode the JSON data".to_owned()).into() }) })?; let fold_enable = rc .evaluate(ctx, "@root/fold_enable")? .as_json() .as_bool() .ok_or_else(|| { RenderErrorReason::Other("Type error for `fold_enable`, bool expected".to_owned()) })?; let fold_level = rc .evaluate(ctx, "@root/fold_level")? .as_json() .as_u64() .ok_or_else(|| { RenderErrorReason::Other("Type error for `fold_level`, u64 expected".to_owned()) })?; // If true, then this is the iframe and we need target="_parent" let is_toc_html = rc .evaluate(ctx, "@root/is_toc_html")? .as_json() .as_bool() .unwrap_or(false); out.write("
    ")?; let mut current_level = 1; let mut first = true; for item in chapters { let level = item .get("section") .map(|s| s.matches('.').count()) .unwrap_or(1); // Expand if folding is disabled, or if levels that are larger than this would not // be folded. let is_expanded = !fold_enable || level - 1 < (fold_level as usize); match level.cmp(¤t_level) { Ordering::Greater => { // There is an assumption that when descending, it can // only go one level down at a time. This should be // enforced by the nature of markdown lists and the // summary parser. assert_eq!(level, current_level + 1); current_level += 1; out.write("
      ")?; write_li_open_tag(out, is_expanded)?; } Ordering::Less => { while level < current_level { out.write("")?; out.write("
    ")?; current_level -= 1; } write_li_open_tag(out, is_expanded)?; } Ordering::Equal => { if !first { out.write("")?; } write_li_open_tag(out, is_expanded)?; } } first = false; // Spacer if item.contains_key("spacer") { out.write("
  1. ")?; continue; } // Part title if let Some(title) = item.get("part") { out.write("
  2. ")?; out.write(&escape_html_attribute(title))?; out.write("
  3. ")?; continue; } out.write("")?; // Link let path_exists = match item.get("path") { Some(path) if !path.is_empty() => { out.write("" } else { "\">" })?; true } _ => { out.write("")?; false } }; if !self.no_section_label { // Section does not necessarily exist if let Some(section) = item.get("section") { out.write("")?; out.write(section)?; out.write(" ")?; } } if let Some(name) = item.get("name") { out.write(&escape_html_attribute(name))?; } if path_exists { out.write("")?; } else { out.write("")?; } // Render expand/collapse toggle if let Some(flag) = item.get("has_sub_items") { let has_sub_items = flag.parse::().unwrap_or_default(); if fold_enable && has_sub_items { // The
    here is to manage rotating the element when // the chapter title is long and word-wraps. out.write("
    ")?; } } out.write("")?; } while current_level > 0 { out.write("")?; out.write("
")?; current_level -= 1; } Ok(()) } } fn write_li_open_tag(out: &mut dyn Output, is_expanded: bool) -> Result<(), std::io::Error> { let mut li = String::from("
  • "); out.write(&li) }