Add table of contents support
This commit is contained in:
parent
4fa88ebc21
commit
c3986b701a
|
@ -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};
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
166
src/content/table_of_contents.rs
Normal file
166
src/content/table_of_contents.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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="w7Ft2ymGmfc") }}</code></p>\n");
|
||||
assert_eq!(res.0, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue