Merge pull request #45 from Keats/index

Index page
This commit is contained in:
Vincent Prouillet 2017-04-23 13:28:27 +09:00 committed by GitHub
commit d6437856a4
7 changed files with 53 additions and 38 deletions

View file

@ -123,14 +123,18 @@ which template will be used to render that section.
Sections will also automatically pick up their subsections, allowing you to make some complex pages layout and Sections will also automatically pick up their subsections, allowing you to make some complex pages layout and
table of contents. table of contents.
A special case is the `_index.md` at the root of the `content` directory which represents the homepage. It is only there
to control pagination and sorting of the homepage.
### Code highlighting themes ### Code highlighting themes
Code highlighting can be turned on by setting `highlight_code = true` in `config.toml`. Code highlighting can be turned on by setting `highlight_code = true` in `config.toml`.
When turned on, all text between backticks will be highlighted, like the example below. When turned on, all text between backticks will be highlighted, like the example below.
```rust ```rust
let site = Site::new(); let site = Site::new();
``` ```
If the name of the language is not given, it will default to plain-text highlighting. If the name of the language is not given, it will default to plain-text highlighting.
Gutenberg uses Sublime Text themes for syntax highlighting. It comes with the following theme Gutenberg uses Sublime Text themes for syntax highlighting. It comes with the following theme

View file

@ -37,7 +37,7 @@ lazy_static!{
}; };
} }
/// A ShortCode that has a body /// A shortcode that has a body
/// Called by having some content like {% ... %} body {% end %} /// Called by having some content like {% ... %} body {% end %}
/// We need the struct to hold the data while we're processing the markdown /// We need the struct to hold the data while we're processing the markdown
#[derive(Debug)] #[derive(Debug)]
@ -62,7 +62,7 @@ impl ShortCode {
pub fn render(&self, tera: &Tera) -> Result<String> { pub fn render(&self, tera: &Tera) -> Result<String> {
let mut context = Context::new(); let mut context = Context::new();
for (key, value) in self.args.iter() { for (key, value) in &self.args {
context.add(key, value); context.add(key, value);
} }
context.add("body", &self.body); context.add("body", &self.body);
@ -132,7 +132,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
// for example an article could have several titles named Example // for example an article could have several titles named Example
// We add a counter after the slug if the slug is already present, which // We add a counter after the slug if the slug is already present, which
// means we will have example, example-1, example-2 etc // means we will have example, example-1, example-2 etc
fn find_anchor(anchors: &Vec<String>, name: String, level: u8) -> String { fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
if level == 0 && !anchors.contains(&name) { if level == 0 && !anchors.contains(&name) {
return name.to_string(); return name.to_string();
} }
@ -164,16 +164,14 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
} }
// Shortcode without body // Shortcode without body
if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") { if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") && SHORTCODE_RE.is_match(&text) {
if SHORTCODE_RE.is_match(&text) { let (name, args) = parse_shortcode(&text);
let (name, args) = parse_shortcode(&text); added_shortcode = true;
added_shortcode = true; match render_simple_shortcode(tera, &name, &args) {
match render_simple_shortcode(tera, &name, &args) { Ok(s) => return Event::Html(Owned(format!("</p>{}", s))),
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), Err(e) => {
Err(e) => { error = Some(e);
error = Some(e); return Event::Html(Owned("".to_string()));
return Event::Html(Owned("".to_string()));
}
} }
} }
// non-matching will be returned normally below // non-matching will be returned normally below
@ -277,7 +275,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
}; };
} }
return Event::Start(Tag::Link(link.clone(), title.clone())); Event::Start(Tag::Link(link.clone(), title.clone()))
}, },
// need to know when we are in a code block to disable shortcodes in them // need to know when we are in a code block to disable shortcodes in them
Event::Start(Tag::Code) => { Event::Start(Tag::Code) => {
@ -291,7 +289,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
Event::Start(Tag::Header(num)) => { Event::Start(Tag::Header(num)) => {
in_header = true; in_header = true;
// ugly eh // ugly eh
return Event::Html(Owned(format!("<h{} ", num))); Event::Html(Owned(format!("<h{} ", num)))
}, },
Event::End(Tag::Header(_)) => { Event::End(Tag::Header(_)) => {
in_header = false; in_header = false;

View file

@ -152,22 +152,21 @@ impl Page {
// Pages with custom urls exists outside of sections // Pages with custom urls exists outside of sections
if let Some(ref u) = page.meta.url { if let Some(ref u) = page.meta.url {
page.path = u.trim().to_string(); page.path = u.trim().to_string();
} else { } else if !page.components.is_empty() {
if !page.components.is_empty() { // If we have a folder with an asset, don't consider it as a component
// If we have a folder with an asset, don't consider it as a component if page.file_name == "index" {
if page.file_name == "index" { page.components.pop();
page.components.pop(); // also set parent_path to grandparent instead
// also set parent_path to grandparent instead page.parent_path = page.parent_path.parent().unwrap().to_path_buf();
page.parent_path = page.parent_path.parent().unwrap().to_path_buf();
}
// Don't add a trailing slash to sections
page.path = format!("{}/{}", page.components.join("/"), page.slug);
} else {
page.path = page.slug.clone();
} }
// Don't add a trailing slash to sections
page.path = format!("{}/{}", page.components.join("/"), page.slug);
} else {
page.path = page.slug.clone();
} }
page.permalink = config.make_permalink(&page.path); page.permalink = config.make_permalink(&page.path);
Ok(page) Ok(page)

View file

@ -64,6 +64,7 @@ pub struct Site {
pub config: Config, pub config: Config,
pub pages: HashMap<PathBuf, Page>, pub pages: HashMap<PathBuf, Page>,
pub sections: BTreeMap<PathBuf, Section>, pub sections: BTreeMap<PathBuf, Section>,
pub index: Option<Section>,
pub tera: Tera, pub tera: Tera,
live_reload: bool, live_reload: bool,
output_path: PathBuf, output_path: PathBuf,
@ -91,6 +92,7 @@ impl Site {
config: get_config(path, config_file), config: get_config(path, config_file),
pages: HashMap::new(), pages: HashMap::new(),
sections: BTreeMap::new(), sections: BTreeMap::new(),
index: None,
tera: tera, tera: tera,
live_reload: false, live_reload: false,
output_path: path.join("public"), output_path: path.join("public"),
@ -117,16 +119,21 @@ impl Site {
/// Reads all .md files in the `content` directory and create pages/sections /// Reads all .md files in the `content` directory and create pages/sections
/// out of them /// out of them
pub fn load(&mut self) -> Result<()> { pub fn load(&mut self) -> Result<()> {
let path = self.base_path.to_string_lossy().replace("\\", "/"); let base_path = self.base_path.to_string_lossy().replace("\\", "/");
let content_glob = format!("{}/{}", path, "content/**/*.md"); let content_glob = format!("{}/{}", base_path, "content/**/*.md");
// TODO: make that parallel, that's the main bottleneck // TODO: make that parallel, that's the main bottleneck
// `add_section` and `add_page` can't be used in the parallel version afaik // `add_section` and `add_page` can't be used in the parallel version afaik
for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) {
let path = entry.as_path(); let path = entry.as_path();
if path.file_name().unwrap() == "_index.md" { if path.file_name().unwrap() == "_index.md" {
self.add_section(path)?; // Index section
if path.parent().unwrap() == self.base_path.join("content") {
self.index = Some(Section::from_file(path, &self.config)?);
} else {
// all the other sections
self.add_section(path)?;
}
} else { } else {
self.add_page(path)?; self.add_page(path)?;
} }

View file

@ -0,0 +1,4 @@
+++
title = "Home"
description = ""
+++

View file

@ -53,10 +53,10 @@ authors = ["Bob", "Alice"]"#;
assert_eq!(res.title, "Hello".to_string()); assert_eq!(res.title, "Hello".to_string());
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); assert_eq!(res.slug.unwrap(), "hello-world".to_string());
let extra = res.extra.unwrap(); let extra = res.extra.unwrap();
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); assert_eq!(extra["language"], to_value("en").unwrap());
assert_eq!( assert_eq!(
extra.get("authors").unwrap(), extra["authors"],
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() to_value(["Bob".to_string(), "Alice".to_string()]).unwrap()
); );
} }

View file

@ -22,6 +22,9 @@ fn test_can_parse_site() {
assert_eq!(site.pages.len(), 10); assert_eq!(site.pages.len(), 10);
let posts_path = path.join("content").join("posts"); let posts_path = path.join("content").join("posts");
// We have an index page
assert!(site.index.is_some());
// Make sure we remove all the pwd + content from the sections // Make sure we remove all the pwd + content from the sections
let basic = &site.pages[&posts_path.join("simple.md")]; let basic = &site.pages[&posts_path.join("simple.md")];
assert_eq!(basic.components, vec!["posts".to_string()]); assert_eq!(basic.components, vec!["posts".to_string()]);