2017-06-13 20:53:25 +08:00
|
|
|
use std::collections::BTreeMap;
|
2018-07-23 12:45:01 -05:00
|
|
|
use std::path::Path;
|
2015-07-30 15:38:21 +02:00
|
|
|
|
2019-05-26 01:50:41 +07:00
|
|
|
use crate::utils;
|
2018-07-11 23:33:44 +10:00
|
|
|
|
2018-08-05 15:08:47 +08:00
|
|
|
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
|
2019-06-11 09:26:24 -07:00
|
|
|
use pulldown_cmark::{html, Event, Parser};
|
2015-07-30 15:38:21 +02:00
|
|
|
|
|
|
|
|
// Handlebars helper to construct TOC
|
|
|
|
|
#[derive(Clone, Copy)]
|
2018-01-08 00:31:46 +08:00
|
|
|
pub struct RenderToc {
|
2018-03-14 23:28:32 +08:00
|
|
|
pub no_section_label: bool,
|
2018-01-08 00:31:46 +08:00
|
|
|
}
|
2015-07-30 15:38:21 +02:00
|
|
|
|
|
|
|
|
impl HelperDef for RenderToc {
|
2018-08-21 10:58:44 -05:00
|
|
|
fn call<'reg: 'rc, 'rc>(
|
|
|
|
|
&self,
|
2019-05-07 01:20:58 +07:00
|
|
|
_h: &Helper<'reg, 'rc>,
|
|
|
|
|
_r: &'reg Handlebars,
|
|
|
|
|
ctx: &'rc Context,
|
|
|
|
|
rc: &mut RenderContext<'reg>,
|
|
|
|
|
out: &mut dyn Output,
|
2018-08-21 10:58:44 -05:00
|
|
|
) -> Result<(), RenderError> {
|
2016-03-17 22:31:28 +01:00
|
|
|
// 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
|
2019-07-13 00:11:05 +08:00
|
|
|
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
|
|
|
|
|
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
|
2017-10-03 17:25:23 +05:45
|
|
|
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
|
|
|
|
})?;
|
2018-08-02 20:22:49 -05:00
|
|
|
let current = rc
|
2019-07-13 00:11:05 +08:00
|
|
|
.evaluate(ctx, "@root/path")?
|
|
|
|
|
.as_json()
|
2018-03-14 23:28:32 +08:00
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
|
|
|
|
.replace("\"", "");
|
2015-07-30 15:38:21 +02:00
|
|
|
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<ol class=\"chapter\">")?;
|
2015-07-30 15:38:21 +02:00
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
let mut current_level = 1;
|
2015-07-30 15:38:21 +02:00
|
|
|
|
2017-06-13 20:53:25 +08:00
|
|
|
for item in chapters {
|
2016-03-17 22:31:28 +01:00
|
|
|
// Spacer
|
2017-02-15 22:01:26 -05:00
|
|
|
if item.get("spacer").is_some() {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<li class=\"spacer\"></li>")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
continue;
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
2015-07-30 15:38:21 +02:00
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
let level = if let Some(s) = item.get("section") {
|
2017-06-27 09:08:58 +02:00
|
|
|
s.matches('.').count()
|
2016-03-17 22:31:28 +01:00
|
|
|
} else {
|
|
|
|
|
1
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if level > current_level {
|
2016-07-16 10:23:22 -04:00
|
|
|
while level > current_level {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<li>")?;
|
|
|
|
|
out.write("<ol class=\"section\">")?;
|
2016-07-16 10:23:22 -04:00
|
|
|
current_level += 1;
|
|
|
|
|
}
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<li>")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
} else if level < current_level {
|
|
|
|
|
while level < current_level {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("</ol>")?;
|
|
|
|
|
out.write("</li>")?;
|
2016-07-16 10:23:22 -04:00
|
|
|
current_level -= 1;
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<li>")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
} else {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<li")?;
|
2017-02-15 22:01:26 -05:00
|
|
|
if item.get("section").is_none() {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(" class=\"affix\"")?;
|
2015-07-31 15:06:08 +02:00
|
|
|
}
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(">")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Link
|
|
|
|
|
let path_exists = if let Some(path) = item.get("path") {
|
|
|
|
|
if !path.is_empty() {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<a href=\"")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
|
2017-05-19 13:04:37 +02:00
|
|
|
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
|
|
|
|
|
.with_extension("html")
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
// Hack for windows who tends to use `\` as separator instead of `/`
|
|
|
|
|
.replace("\\", "/");
|
2016-03-17 22:31:28 +01:00
|
|
|
|
|
|
|
|
// Add link
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(&utils::fs::path_to_root(¤t))?;
|
|
|
|
|
out.write(&tmp)?;
|
|
|
|
|
out.write("\"")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
|
|
|
|
|
if path == ¤t {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(" class=\"active\"")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
|
|
|
|
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(">")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
2015-07-30 15:38:21 +02:00
|
|
|
} else {
|
|
|
|
|
false
|
2016-03-17 22:31:28 +01:00
|
|
|
};
|
2015-09-11 20:52:55 +02:00
|
|
|
|
2018-01-08 00:31:46 +08:00
|
|
|
if !self.no_section_label {
|
|
|
|
|
// Section does not necessarily exist
|
|
|
|
|
if let Some(section) = item.get("section") {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("<strong aria-hidden=\"true\">")?;
|
|
|
|
|
out.write(§ion)?;
|
|
|
|
|
out.write("</strong> ")?;
|
2018-01-08 00:31:46 +08:00
|
|
|
}
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
2015-12-30 00:46:55 +01:00
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
if let Some(name) = item.get("name") {
|
|
|
|
|
// Render only inline code blocks
|
|
|
|
|
|
|
|
|
|
// filter all events that are not inline code blocks
|
2017-05-05 08:41:50 +08:00
|
|
|
let parser = Parser::new(name).filter(|event| match *event {
|
2019-06-11 09:26:24 -07:00
|
|
|
Event::Code(_) | Event::InlineHtml(_) | Event::Text(_) => true,
|
2018-03-14 23:28:32 +08:00
|
|
|
_ => false,
|
|
|
|
|
});
|
2016-03-17 22:31:28 +01:00
|
|
|
|
|
|
|
|
// render markdown to html
|
|
|
|
|
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
|
|
|
|
|
html::push_html(&mut markdown_parsed_name, parser);
|
|
|
|
|
|
|
|
|
|
// write to the handlebars template
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write(&markdown_parsed_name)?;
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
2015-12-30 00:46:55 +01:00
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
if path_exists {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("</a>")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
}
|
2015-12-30 00:46:55 +01:00
|
|
|
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("</li>")?;
|
2016-07-16 10:23:22 -04:00
|
|
|
}
|
|
|
|
|
while current_level > 1 {
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("</ol>")?;
|
|
|
|
|
out.write("</li>")?;
|
2016-07-16 10:23:22 -04:00
|
|
|
current_level -= 1;
|
2015-07-30 15:38:21 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-05 15:08:47 +08:00
|
|
|
out.write("</ol>")?;
|
2016-03-17 22:31:28 +01:00
|
|
|
Ok(())
|
2015-07-30 15:38:21 +02:00
|
|
|
}
|
|
|
|
|
}
|