Add table of contents support

This commit is contained in:
Vincent Prouillet 2017-06-16 13:00:48 +09:00
parent 4fa88ebc21
commit c3986b701a
6 changed files with 292 additions and 56 deletions

View file

@ -5,10 +5,12 @@ mod sorting;
mod utils;
mod file_info;
mod taxonomies;
mod table_of_contents;
pub use self::page::{Page};
pub use self::section::{Section};
pub use self::pagination::{Paginator, Pager};
pub use self::sorting::{SortBy, sort_pages, populate_previous_and_next_pages};
pub use self::taxonomies::{Taxonomy, TaxonomyItem};
pub use self::table_of_contents::{TempHeader, Header, make_table_of_contents};

View file

@ -16,6 +16,7 @@ use rendering::context::Context;
use fs::{read_file};
use content::utils::{find_related_assets, get_reading_analytics};
use content::file_info::FileInfo;
use content::Header;
#[derive(Clone, Debug, PartialEq)]
@ -45,6 +46,8 @@ pub struct Page {
pub previous: Option<Box<Page>>,
/// The next page, by whatever sorting is used for the index/section
pub next: Option<Box<Page>>,
/// Toc made from the headers of the markdown file
pub toc: Vec<Header>,
}
@ -64,6 +67,7 @@ impl Page {
summary: None,
previous: None,
next: None,
toc: vec![],
}
}
@ -117,12 +121,14 @@ impl Page {
/// We need access to all pages url to render links relative to content
/// so that can't happen at the same time as parsing
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, anchor_insert: InsertAnchor) -> Result<()> {
let context = Context::new(tera, config, permalinks, anchor_insert);
self.content = markdown_to_html(&self.raw_content, &context)?;
let context = Context::new(tera, config, &self.permalink, permalinks, anchor_insert);
let res = markdown_to_html(&self.raw_content, &context)?;
self.content = res.0;
self.toc = res.1;
if self.raw_content.contains("<!-- more -->") {
self.summary = Some({
let summary = self.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0];
markdown_to_html(summary, &context)?
markdown_to_html(summary, &context)?.0
})
}
@ -161,13 +167,14 @@ impl Default for Page {
summary: None,
previous: None,
next: None,
toc: vec![],
}
}
}
impl ser::Serialize for Page {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
let mut state = serializer.serialize_struct("page", 15)?;
let mut state = serializer.serialize_struct("page", 16)?;
state.serialize_field("content", &self.content)?;
state.serialize_field("title", &self.meta.title)?;
state.serialize_field("description", &self.meta.description)?;
@ -184,6 +191,7 @@ impl ser::Serialize for Page {
state.serialize_field("reading_time", &reading_time)?;
state.serialize_field("previous", &self.previous)?;
state.serialize_field("next", &self.next)?;
state.serialize_field("toc", &self.toc)?;
state.end()
}
}

View file

@ -13,6 +13,7 @@ use rendering::markdown::markdown_to_html;
use rendering::context::Context;
use content::Page;
use content::file_info::FileInfo;
use content::Header;
#[derive(Clone, Debug, PartialEq)]
@ -35,6 +36,8 @@ pub struct Section {
pub ignored_pages: Vec<Page>,
/// All direct subsections
pub subsections: Vec<Section>,
/// Toc made from the headers of the markdown file
pub toc: Vec<Header>,
}
impl Section {
@ -51,6 +54,7 @@ impl Section {
pages: vec![],
ignored_pages: vec![],
subsections: vec![],
toc: vec![],
}
}
@ -86,8 +90,10 @@ impl Section {
/// We need access to all pages url to render links relative to content
/// so that can't happen at the same time as parsing
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> {
let context = Context::new(tera, config, permalinks, self.meta.insert_anchor.unwrap());
self.content = markdown_to_html(&self.raw_content, &context)?;
let context = Context::new(tera, config, &self.permalink, permalinks, self.meta.insert_anchor.unwrap());
let res = markdown_to_html(&self.raw_content, &context)?;
self.content = res.0;
self.toc = res.1;
Ok(())
}
@ -129,7 +135,7 @@ impl Section {
impl ser::Serialize for Section {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
let mut state = serializer.serialize_struct("section", 9)?;
let mut state = serializer.serialize_struct("section", 10)?;
state.serialize_field("content", &self.content)?;
state.serialize_field("permalink", &self.permalink)?;
state.serialize_field("title", &self.meta.title)?;
@ -139,6 +145,7 @@ impl ser::Serialize for Section {
state.serialize_field("permalink", &self.permalink)?;
state.serialize_field("pages", &self.pages)?;
state.serialize_field("subsections", &self.subsections)?;
state.serialize_field("toc", &self.toc)?;
state.end()
}
}
@ -156,6 +163,7 @@ impl Default for Section {
pages: vec![],
ignored_pages: vec![],
subsections: vec![],
toc: vec![],
}
}
}

View file

@ -0,0 +1,166 @@
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Header {
#[serde(skip_serializing)]
pub level: i32,
pub id: String,
pub title: String,
pub permalink: String,
pub children: Vec<Header>,
}
impl Header {
pub fn from_temp_header(tmp: &TempHeader, children: Vec<Header>) -> Header {
Header {
level: tmp.level,
id: tmp.id.clone(),
title: tmp.title.clone(),
permalink: tmp.permalink.clone(),
children,
}
}
}
/// Used in
#[derive(Debug, PartialEq, Clone)]
pub struct TempHeader {
pub level: i32,
pub id: String,
pub permalink: String,
pub title: String,
}
impl TempHeader {
pub fn new(level: i32) -> TempHeader {
TempHeader {
level,
id: String::new(),
permalink: String::new(),
title: String::new(),
}
}
}
impl Default for TempHeader {
fn default() -> Self {
TempHeader::new(0)
}
}
/// Recursively finds children of a header
fn find_children(parent_level: i32, start_at: usize, temp_headers: &[TempHeader]) -> (usize, Vec<Header>) {
let mut headers = vec![];
let mut start_at = start_at;
// If we have children, we will need to skip some headers since they are already inserted
let mut to_skip = 0;
for h in &temp_headers[start_at..] {
// stop when we encounter a title at the same level or higher
// than the parent one. Here a lower integer is considered higher as we are talking about
// HTML headers: h1, h2, h3, h4, h5 and h6
if h.level <= parent_level {
return (start_at, headers);
}
// Do we need to skip some headers?
if to_skip > 0 {
to_skip -= 1;
continue;
}
let (end, children) = find_children(h.level, start_at + 1, &temp_headers);
headers.push(Header::from_temp_header(h, children));
// we didn't find any children
if end == start_at {
start_at += 1;
to_skip = 0;
} else {
// calculates how many we need to skip. Since the find_children start_at starts at 1,
// we need to remove 1 to ensure correctness
to_skip = end - start_at - 1;
start_at = end;
}
// we don't want to index out of bounds
if start_at + 1 > temp_headers.len() {
return (start_at, headers);
}
}
(start_at, headers)
}
/// Converts the flat temp headers into a nested set of headers
/// representing the hierarchy
pub fn make_table_of_contents(temp_headers: Vec<TempHeader>) -> Vec<Header> {
let mut toc = vec![];
let mut start_idx = 0;
for (i, h) in temp_headers.iter().enumerate() {
if i < start_idx {
continue;
}
let (end_idx, children) = find_children(h.level, start_idx + 1, &temp_headers);
start_idx = end_idx;
toc.push(Header::from_temp_header(h, children));
}
toc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_make_basic_toc() {
let input = vec![
TempHeader::new(1),
TempHeader::new(1),
TempHeader::new(1),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 3);
}
#[test]
fn can_make_more_complex_toc() {
let input = vec![
TempHeader::new(1),
TempHeader::new(2),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(1),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(3),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 2);
assert_eq!(toc[0].children.len(), 3);
assert_eq!(toc[1].children.len(), 1);
assert_eq!(toc[0].children[1].children.len(), 1);
assert_eq!(toc[1].children[0].children.len(), 2);
}
#[test]
fn can_make_messy_toc() {
let input = vec![
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(1),
TempHeader::new(4),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 5);
assert_eq!(toc[2].children.len(), 1);
assert_eq!(toc[4].children.len(), 1);
}
}

View file

@ -12,14 +12,22 @@ pub struct Context<'a> {
pub tera: &'a Tera,
pub highlight_code: bool,
pub highlight_theme: String,
pub current_page_permalink: String,
pub permalinks: &'a HashMap<String, String>,
pub insert_anchor: InsertAnchor,
}
impl<'a> Context<'a> {
pub fn new(tera: &'a Tera, config: &'a Config, permalinks: &'a HashMap<String, String>, insert_anchor: InsertAnchor) -> Context<'a> {
pub fn new(
tera: &'a Tera,
config: &'a Config,
current_page_permalink: &str,
permalinks: &'a HashMap<String, String>,
insert_anchor: InsertAnchor,
) -> Context<'a> {
Context {
tera,
current_page_permalink: current_page_permalink.to_string(),
permalinks,
insert_anchor,
highlight_code: config.highlight_code.unwrap(),

View file

@ -16,6 +16,7 @@ use front_matter::InsertAnchor;
use rendering::context::Context;
use rendering::highlighting::THEME_SET;
use rendering::short_code::{ShortCode, parse_shortcode, render_simple_shortcode};
use content::{TempHeader, Header, make_table_of_contents};
// We need to put those in a struct to impl Send and sync
pub struct Setup {
@ -37,7 +38,7 @@ lazy_static!{
}
pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec<Header>)> {
// We try to be smart about highlighting code as it can be time-consuming
// If the global config disables it, then we do nothing. However,
// if we see a code block in the content, we assume that this page needs
@ -62,9 +63,10 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
// pulldown_cmark can send several text events for a title if there are markdown
// specific characters like `!` in them. We only want to insert the anchor the first time
let mut header_already_inserted = false;
let mut anchors: Vec<String> = vec![];
// the rendered html
let mut html = String::new();
let mut anchors: Vec<String> = vec![];
// We might have cases where the slug is already present in our list of anchor
// for example an article could have several titles named Example
@ -83,6 +85,11 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
find_anchor(anchors, name, level + 1)
}
let mut headers = vec![];
// Defaults to a 0 level so not a real header
// It should be an Option ideally but not worth the hassle to update
let mut temp_header = TempHeader::default();
let mut opts = Options::empty();
opts.insert(OPTION_ENABLE_TABLES);
opts.insert(OPTION_ENABLE_FOOTNOTES);
@ -158,6 +165,13 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
} else {
String::new()
};
// update the header and add it to the list
temp_header.id = id.clone();
temp_header.title = text.clone().into_owned();
temp_header.permalink = format!("{}#{}", context.current_page_permalink, id);
headers.push(temp_header.clone());
temp_header = TempHeader::default();
header_already_inserted = true;
let event = match context.insert_anchor {
InsertAnchor::Left => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, anchor_link, text))),
@ -230,6 +244,7 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
},
Event::Start(Tag::Header(num)) => {
in_header = true;
temp_header = TempHeader::new(num);
// ugly eh
Event::Html(Owned(format!("<h{} ", num)))
},
@ -264,7 +279,7 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
match error {
Some(e) => Err(e),
None => Ok(html.replace("<p></p>", "")),
None => Ok((html.replace("<p></p>", ""), make_table_of_contents(headers))),
}
}
@ -287,9 +302,9 @@ mod tests {
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("hello", &context).unwrap();
assert_eq!(res, "<p>hello</p>\n");
assert_eq!(res.0, "<p>hello</p>\n");
}
#[test]
@ -297,11 +312,11 @@ mod tests {
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let mut context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let mut context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
context.highlight_code = false;
let res = markdown_to_html("```\n$ gutenberg server\n```", &context).unwrap();
assert_eq!(
res,
res.0,
"<pre><code>$ gutenberg server\n</code></pre>\n"
);
}
@ -311,10 +326,10 @@ mod tests {
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &context).unwrap();
assert_eq!(
res,
res.0,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
);
}
@ -324,10 +339,10 @@ mod tests {
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap();
assert_eq!(
res,
res.0,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
);
}
@ -337,11 +352,11 @@ mod tests {
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("```yolo\nlist.append(1)\n```", &context).unwrap();
// defaults to plain text
assert_eq!(
res,
res.0,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>"
);
}
@ -350,21 +365,21 @@ mod tests {
fn can_render_shortcode() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html(r#"
Hello
{{ youtube(id="ub36ffWAqgQ") }}
"#, &context).unwrap();
assert!(res.contains("<p>Hello</p>\n<div >"));
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
assert!(res.0.contains("<p>Hello</p>\n<div >"));
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
}
#[test]
fn can_render_several_shortcode_in_row() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html(r#"
Hello
@ -373,26 +388,26 @@ Hello
{{ youtube(id="ub36ffWAqgQ", autoplay=true) }}
{{ vimeo(id="210073083") }}
{{ streamable(id="c0ic") }}
{{ streamable(id="c0ic") }}
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
"#, &context).unwrap();
assert!(res.contains("<p>Hello</p>\n<div >"));
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
assert!(res.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#));
assert!(res.contains(r#"//player.vimeo.com/video/210073083""#));
assert!(res.0.contains("<p>Hello</p>\n<div >"));
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
assert!(res.0.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#));
assert!(res.0.contains(r#"//player.vimeo.com/video/210073083""#));
}
#[test]
fn doesnt_render_shortcode_in_code_block() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &context).unwrap();
assert_eq!(res, "<p><code>{{ youtube(id=&quot;w7Ft2ymGmfc&quot;) }}</code></p>\n");
assert_eq!(res.0, "<p><code>{{ youtube(id=&quot;w7Ft2ymGmfc&quot;) }}</code></p>\n");
}
#[test]
@ -402,7 +417,7 @@ Hello
tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html(r#"
Hello
@ -410,7 +425,7 @@ Hello
A quote
{% end %}
"#, &context).unwrap();
assert_eq!(res, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
assert_eq!(res.0, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
}
#[test]
@ -418,7 +433,7 @@ A quote
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("{{ hello(flash=true) }}", &context);
assert!(res.is_err());
}
@ -429,14 +444,14 @@ A quote
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
let tera_ctx = Tera::default();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks, InsertAnchor::None);
let res = markdown_to_html(
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
&context
).unwrap();
assert!(
res.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#)
res.0.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#)
);
}
@ -446,11 +461,11 @@ A quote
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
let tera_ctx = Tera::default();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks, InsertAnchor::None);
let res = markdown_to_html(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap();
assert!(
res.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
res.0.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
);
}
@ -459,7 +474,7 @@ A quote
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("[rel link](./pages/about.md)", &context);
assert!(res.is_err());
}
@ -469,9 +484,9 @@ A quote
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html(r#"# Hello"#, &context).unwrap();
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n");
assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n");
}
#[test]
@ -479,19 +494,19 @@ A quote
let tera_ctx = Tera::default();
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
let context = Context::new(&tera_ctx, &config_ctx, "", &permalinks_ctx, InsertAnchor::None);
let res = markdown_to_html("# Hello\n# Hello", &context).unwrap();
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
}
#[test]
fn can_insert_anchor_left() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::Left);
let res = markdown_to_html("# Hello", &context).unwrap();
assert_eq!(
res,
res.0,
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello</h1>\n"
);
}
@ -500,10 +515,10 @@ A quote
fn can_insert_anchor_right() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Right);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::Right);
let res = markdown_to_html("# Hello", &context).unwrap();
assert_eq!(
res,
res.0,
"<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\n</h1>\n"
);
}
@ -513,10 +528,10 @@ A quote
fn can_insert_anchor_with_exclamation_mark() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::Left);
let res = markdown_to_html("# Hello!", &context).unwrap();
assert_eq!(
res,
res.0,
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello!</h1>\n"
);
}
@ -526,10 +541,10 @@ A quote
fn can_insert_anchor_with_link() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::Left);
let res = markdown_to_html("## [](#xresources)Xresources", &context).unwrap();
assert_eq!(
res,
res.0,
"<h2 id=\"xresources\"><a class=\"gutenberg-anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">🔗</a>\nXresources</h2>\n"
);
}
@ -538,11 +553,40 @@ A quote
fn can_insert_anchor_with_other_special_chars() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
let context = Context::new(&GUTENBERG_TERA, &config_ctx, "", &permalinks_ctx, InsertAnchor::Left);
let res = markdown_to_html("# Hello*_()", &context).unwrap();
assert_eq!(
res,
res.0,
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello*_()</h1>\n"
);
}
#[test]
fn can_make_toc() {
let permalinks_ctx = HashMap::new();
let config_ctx = Config::default();
let context = Context::new(
&GUTENBERG_TERA,
&config_ctx,
"https://mysite.com/something",
&permalinks_ctx,
InsertAnchor::Left
);
let res = markdown_to_html(r#"
# Header 1
## Header 2
## Another Header 2
### Last one
"#, &context).unwrap();
let toc = res.1;
assert_eq!(toc.len(), 1);
assert_eq!(toc[0].children.len(), 2);
assert_eq!(toc[0].children[1].children.len(), 1);
}
}