diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef6edfc3..db28eabf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@
- Page and Section `path` field is not starting with a `/` anymore
- All Tera global fns are now rebuilt on changes
- Use flags for port/interface in `gutenberg serve`
+- Fix various issues with headers markdown rendering
+
## 0.1.3 (2017-08-31)
diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs
index ccc5d205..4209cd31 100644
--- a/components/rendering/src/markdown.rs
+++ b/components/rendering/src/markdown.rs
@@ -5,11 +5,9 @@ use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENAB
use slug::slugify;
use syntect::easy::HighlightLines;
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};
-use tera::{Context as TeraContext};
use errors::Result;
use utils::site::resolve_internal_link;
-use front_matter::InsertAnchor;
use context::Context;
use highlighting::{SYNTAX_SET, THEME_SET};
use short_code::{SHORTCODE_RE, ShortCode, parse_shortcode, render_simple_shortcode};
@@ -40,7 +38,7 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
let mut in_header = false;
// 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 header_created = false;
let mut anchors: Vec = vec![];
// the rendered html
@@ -75,6 +73,23 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
{
let parser = Parser::new_ext(content, opts).map(|event| match event {
Event::Text(text) => {
+ // Header first
+ if in_header {
+ if header_created {
+ temp_header.push(&text);
+ return Event::Html(Owned(String::new()));
+ }
+ let id = find_anchor(&anchors, slugify(&text), 0);
+ anchors.push(id.clone());
+ // update the header and add it to the list
+ temp_header.id = id.clone();
+ // += as we might have some or other things already there
+ temp_header.title += &text;
+ temp_header.permalink = format!("{}#{}", context.current_page_permalink, id);
+ header_created = true;
+ return Event::Html(Owned(String::new()));
+ }
+
// if we are in the middle of a code block
if let Some(ref mut highlighter) = highlighter {
let highlighted = &highlighter.highlight(&text);
@@ -94,10 +109,9 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
Ok(s) => return Event::Html(Owned(format!("
{}", s))),
Err(e) => {
error = Some(e);
- return Event::Html(Owned("".to_string()));
+ return Event::Html(Owned(String::new()));
}
}
- // non-matching will be returned normally below
}
// Shortcode with a body
@@ -107,7 +121,7 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
shortcode_block = Some(ShortCode::new(&name, args));
}
// Don't return anything
- return Event::Text(Owned("".to_string()));
+ return Event::Text(Owned(String::new()));
}
// If we have some text while in a shortcode, it's either the body
@@ -120,45 +134,16 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
Ok(s) => return Event::Html(Owned(format!("{}", s))),
Err(e) => {
error = Some(e);
- return Event::Html(Owned("".to_string()));
+ return Event::Html(Owned(String::new()));
}
}
} else {
shortcode.append(&text);
- return Event::Html(Owned("".to_string()));
+ return Event::Html(Owned(String::new()));
}
}
}
- if in_header {
- if header_already_inserted {
- return Event::Text(text);
- }
- let id = find_anchor(&anchors, slugify(&text), 0);
- anchors.push(id.clone());
- let anchor_link = if context.should_insert_anchor() {
- let mut c = TeraContext::new();
- c.add("id", &id);
- context.tera.render("anchor-link.html", &c).unwrap()
- } 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))),
- InsertAnchor::Right => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, text, anchor_link))),
- InsertAnchor::None => Event::Html(Owned(format!(r#"id="{}">{}"#, id, text)))
- };
- return event;
- }
-
// Business as usual
Event::Text(text)
},
@@ -216,22 +201,33 @@ pub fn markdown_to_html(content: &str, context: &Context) -> Result<(String, Vec
// need to know when we are in a code block to disable shortcodes in them
Event::Start(Tag::Code) => {
in_code_block = true;
+ if in_header {
+ temp_header.push("");
+ return Event::Html(Owned(String::new()));
+ }
event
},
Event::End(Tag::Code) => {
in_code_block = false;
+ if in_header {
+ temp_header.push("
");
+ return Event::Html(Owned(String::new()));
+ }
event
},
Event::Start(Tag::Header(num)) => {
in_header = true;
temp_header = TempHeader::new(num);
- // ugly eh
- Event::Html(Owned(format!(" {
+ // End of a header, reset all the things and return the stringified version of the header
in_header = false;
- header_already_inserted = false;
- event
+ header_created = false;
+ let val = temp_header.to_string(context);
+ headers.push(temp_header.clone());
+ temp_header = TempHeader::default();
+ Event::Html(Owned(val))
},
// If we added shortcodes, don't close a paragraph since there's none
Event::End(Tag::Paragraph) => {
diff --git a/components/rendering/src/table_of_contents.rs b/components/rendering/src/table_of_contents.rs
index ba25bd7a..70f75954 100644
--- a/components/rendering/src/table_of_contents.rs
+++ b/components/rendering/src/table_of_contents.rs
@@ -1,3 +1,8 @@
+use tera::{Context as TeraContext};
+use front_matter::InsertAnchor;
+
+use context::Context;
+
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Header {
@@ -21,6 +26,7 @@ impl Header {
}
}
+/// Populated while receiving events from the markdown parser
#[derive(Debug, PartialEq, Clone)]
pub struct TempHeader {
pub level: i32,
@@ -38,6 +44,27 @@ impl TempHeader {
title: String::new(),
}
}
+
+ pub fn push(&mut self, val: &str) {
+ self.title += val;
+ }
+
+ /// Transform all the information we have about this header into the HTML string for it
+ pub fn to_string(&self, context: &Context) -> String {
+ let anchor_link = if context.should_insert_anchor() {
+ let mut c = TeraContext::new();
+ c.add("id", &self.id);
+ context.tera.render("anchor-link.html", &c).unwrap()
+ } else {
+ String::new()
+ };
+
+ match context.insert_anchor {
+ InsertAnchor::None => format!("{t}\n", lvl=self.level, t=self.title, id=self.id),
+ InsertAnchor::Left => format!("{a}{t}\n", lvl=self.level, a=anchor_link, t=self.title, id=self.id),
+ InsertAnchor::Right => format!("{t}{a}\n", lvl=self.level, a=anchor_link, t=self.title, id=self.id),
+ }
+ }
}
impl Default for TempHeader {
@@ -102,7 +129,7 @@ pub fn make_table_of_contents(temp_headers: &[TempHeader]) -> Vec {
if i < start_idx {
continue;
}
- let (end_idx, children) = find_children(h.level, start_idx + 1, &temp_headers);
+ 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));
}
diff --git a/components/rendering/tests/markdown.rs b/components/rendering/tests/markdown.rs
index eac918ce..3e5a6822 100644
--- a/components/rendering/tests/markdown.rs
+++ b/components/rendering/tests/markdown.rs
@@ -284,3 +284,25 @@ fn can_make_toc() {
assert_eq!(toc[0].children[1].children.len(), 1);
}
+
+#[test]
+fn can_understand_backtick_in_titles() {
+ let permalinks_ctx = HashMap::new();
+ let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
+ let res = markdown_to_html("# `Hello`", &context).unwrap();
+ assert_eq!(
+ res.0,
+ "Hello
\n"
+ );
+}
+
+#[test]
+fn can_understand_backtick_in_paragraphs() {
+ let permalinks_ctx = HashMap::new();
+ let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
+ let res = markdown_to_html("Hello `world`", &context).unwrap();
+ assert_eq!(
+ res.0,
+ "Hello world
\n"
+ );
+}