section.subsections is now an array of paths
Close #446 Close #260 Close #478 Close #284 Close #480
This commit is contained in:
parent
7ef4acbfbc
commit
f14dbcbdf4
|
@ -6,6 +6,8 @@
|
|||
|
||||
- Gutenberg has changed name to REPLACE_ME!
|
||||
- The `pagers` variable of Paginator objects has been removed
|
||||
- `section.subsections` is now an array of paths to be used with the `get_section`
|
||||
Tera function
|
||||
|
||||
### Others
|
||||
- Update dependencies, fixing a few bugs with templates
|
||||
|
@ -25,6 +27,8 @@
|
|||
- RSS feed now takes all available articles by default instead of limiting to 10000
|
||||
- `templates` directory is now optional
|
||||
- Add Reason and F# syntax highlighting
|
||||
- Add `parent_section` to pages and section pointing to the relative path of the parent
|
||||
section if there is one to be used with the `get_section` Tera function
|
||||
|
||||
## 0.4.2 (2018-09-03)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use tera::{Tera, Context as TeraContext, Value, Map};
|
||||
use slug::slugify;
|
||||
use slotmap::{Key, DenseSlotMap};
|
||||
use slotmap::{Key};
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
use config::Config;
|
||||
|
@ -23,6 +23,7 @@ pub struct SerializingPage<'a> {
|
|||
content: &'a str,
|
||||
permalink: &'a str,
|
||||
slug: &'a str,
|
||||
parent_section: Option<String>,
|
||||
title: &'a Option<String>,
|
||||
description: &'a Option<String>,
|
||||
date: &'a Option<String>,
|
||||
|
@ -47,7 +48,7 @@ pub struct SerializingPage<'a> {
|
|||
|
||||
impl<'a> SerializingPage<'a> {
|
||||
/// Grabs all the data from a page, including sibling pages
|
||||
pub fn from_page(page: &'a Page, pages: &'a DenseSlotMap<Page>) -> Self {
|
||||
pub fn from_page(page: &'a Page, library: &'a Library) -> Self {
|
||||
let mut year = None;
|
||||
let mut month = None;
|
||||
let mut day = None;
|
||||
|
@ -56,12 +57,15 @@ impl<'a> SerializingPage<'a> {
|
|||
month = Some(d.1);
|
||||
day = Some(d.2);
|
||||
}
|
||||
let pages = library.pages();
|
||||
let lighter = page.lighter.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap())));
|
||||
let heavier = page.heavier.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap())));
|
||||
let earlier = page.earlier.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap())));
|
||||
let later = page.later.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap())));
|
||||
let parent_section = page.parent_section.map(|k| library.get_section_by_key(k).file.relative.clone());
|
||||
|
||||
SerializingPage {
|
||||
parent_section,
|
||||
content: &page.content,
|
||||
permalink: &page.permalink,
|
||||
slug: &page.slug,
|
||||
|
@ -100,6 +104,7 @@ impl<'a> SerializingPage<'a> {
|
|||
}
|
||||
|
||||
SerializingPage {
|
||||
parent_section: None,
|
||||
content: &page.content,
|
||||
permalink: &page.permalink,
|
||||
slug: &page.slug,
|
||||
|
@ -133,6 +138,8 @@ pub struct Page {
|
|||
pub file: FileInfo,
|
||||
/// The front matter meta-data
|
||||
pub meta: PageFrontMatter,
|
||||
/// The parent section if there is one
|
||||
pub parent_section: Option<Key>,
|
||||
/// The actual content of the page, in markdown
|
||||
pub raw_content: String,
|
||||
/// All the non-md files we found next to the .md file
|
||||
|
@ -177,6 +184,7 @@ impl Page {
|
|||
Page {
|
||||
file: FileInfo::new_page(file_path),
|
||||
meta,
|
||||
parent_section: None,
|
||||
raw_content: "".to_string(),
|
||||
assets: vec![],
|
||||
content: "".to_string(),
|
||||
|
@ -320,7 +328,7 @@ impl Page {
|
|||
context.insert("config", config);
|
||||
context.insert("current_url", &self.permalink);
|
||||
context.insert("current_path", &self.path);
|
||||
context.insert("page", &self.to_serialized(library.pages()));
|
||||
context.insert("page", &self.to_serialized(library));
|
||||
|
||||
render_template(&tpl_name, tera, &context, &config.theme)
|
||||
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display()))
|
||||
|
@ -335,8 +343,8 @@ impl Page {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn to_serialized<'a>(&'a self, pages: &'a DenseSlotMap<Page>) -> SerializingPage<'a> {
|
||||
SerializingPage::from_page(self, pages)
|
||||
pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializingPage<'a> {
|
||||
SerializingPage::from_page(self, library)
|
||||
}
|
||||
|
||||
pub fn to_serialized_basic(&self) -> SerializingPage {
|
||||
|
@ -349,6 +357,7 @@ impl Default for Page {
|
|||
Page {
|
||||
file: FileInfo::default(),
|
||||
meta: PageFrontMatter::default(),
|
||||
parent_section: None,
|
||||
raw_content: "".to_string(),
|
||||
assets: vec![],
|
||||
content: "".to_string(),
|
||||
|
|
|
@ -21,6 +21,7 @@ use library::Library;
|
|||
pub struct SerializingSection<'a> {
|
||||
content: &'a str,
|
||||
permalink: &'a str,
|
||||
parent_section: Option<String>,
|
||||
title: &'a Option<String>,
|
||||
description: &'a Option<String>,
|
||||
extra: &'a HashMap<String, Value>,
|
||||
|
@ -31,7 +32,7 @@ pub struct SerializingSection<'a> {
|
|||
toc: &'a [Header],
|
||||
assets: Vec<String>,
|
||||
pages: Vec<SerializingPage<'a>>,
|
||||
subsections: Vec<SerializingSection<'a>>,
|
||||
subsections: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> SerializingSection<'a> {
|
||||
|
@ -40,14 +41,17 @@ impl<'a> SerializingSection<'a> {
|
|||
let mut subsections = Vec::with_capacity(section.subsections.len());
|
||||
|
||||
for k in §ion.pages {
|
||||
pages.push(library.get_page_by_key(*k).to_serialized(library.pages()));
|
||||
pages.push(library.get_page_by_key(*k).to_serialized(library));
|
||||
}
|
||||
|
||||
for k in §ion.subsections {
|
||||
subsections.push(library.get_section_by_key(*k).to_serialized(library));
|
||||
subsections.push(library.get_section_path_by_key(*k));
|
||||
}
|
||||
|
||||
let parent_section = section.parent_section.map(|k| library.get_section_by_key(k).file.relative.clone());
|
||||
|
||||
SerializingSection {
|
||||
parent_section,
|
||||
content: §ion.content,
|
||||
permalink: §ion.permalink,
|
||||
title: §ion.meta.title,
|
||||
|
@ -67,6 +71,7 @@ impl<'a> SerializingSection<'a> {
|
|||
/// Same as from_section but doesn't fetch pages and sections
|
||||
pub fn from_section_basic(section: &'a Section) -> Self {
|
||||
SerializingSection {
|
||||
parent_section: None,
|
||||
content: §ion.content,
|
||||
permalink: §ion.permalink,
|
||||
title: §ion.meta.title,
|
||||
|
@ -106,6 +111,8 @@ pub struct Section {
|
|||
pub pages: Vec<Key>,
|
||||
/// All pages that cannot be sorted in this section
|
||||
pub ignored_pages: Vec<Key>,
|
||||
/// The relative path of the parent section if there is one
|
||||
pub parent_section: Option<Key>,
|
||||
/// All direct subsections
|
||||
pub subsections: Vec<Key>,
|
||||
/// Toc made from the headers of the markdown file
|
||||
|
@ -124,6 +131,7 @@ impl Section {
|
|||
Section {
|
||||
file: FileInfo::new_section(file_path),
|
||||
meta,
|
||||
parent_section: None,
|
||||
path: "".to_string(),
|
||||
components: vec![],
|
||||
permalink: "".to_string(),
|
||||
|
@ -262,6 +270,7 @@ impl Default for Section {
|
|||
Section {
|
||||
file: FileInfo::default(),
|
||||
meta: SectionFrontMatter::default(),
|
||||
parent_section: None,
|
||||
path: "".to_string(),
|
||||
components: vec![],
|
||||
permalink: "".to_string(),
|
||||
|
|
|
@ -81,7 +81,7 @@ impl Library {
|
|||
/// Find out the direct subsections of each subsection if there are some
|
||||
/// as well as the pages for each section
|
||||
pub fn populate_sections(&mut self) {
|
||||
let mut grandparent_paths: HashMap<PathBuf, Vec<PathBuf>> = HashMap::new();
|
||||
let mut grandparent_paths: HashMap<PathBuf, Vec<_>> = HashMap::new();
|
||||
|
||||
for section in self.sections.values_mut() {
|
||||
if let Some(ref grand_parent) = section.file.grand_parent {
|
||||
|
@ -99,6 +99,7 @@ impl Library {
|
|||
let parent_section_path = page.file.parent.join("_index.md");
|
||||
if let Some(section_key) = self.paths_to_sections.get(&parent_section_path) {
|
||||
self.sections.get_mut(*section_key).unwrap().pages.push(key);
|
||||
page.parent_section = Some(*section_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,14 +110,22 @@ impl Library {
|
|||
for (key, section) in &self.sections {
|
||||
sections_weight.insert(key, section.meta.weight);
|
||||
}
|
||||
for section in self.sections.values_mut() {
|
||||
if let Some(paths) = grandparent_paths.get(§ion.file.parent) {
|
||||
section.subsections = paths
|
||||
.iter()
|
||||
.map(|p| sections[p])
|
||||
.collect::<Vec<_>>();
|
||||
section.subsections
|
||||
.sort_by(|a, b| sections_weight[a].cmp(§ions_weight[b]));
|
||||
|
||||
for (grandparent, children) in &grandparent_paths {
|
||||
let mut subsections = vec![];
|
||||
let grandparent_path = grandparent.join("_index.md");
|
||||
|
||||
if let Some(ref mut section) = self.get_section_mut(&grandparent_path) {
|
||||
subsections = children.iter().map(|p| sections[p]).collect();
|
||||
subsections.sort_by(|a, b| sections_weight[a].cmp(§ions_weight[b]));
|
||||
section.subsections = subsections.clone();
|
||||
}
|
||||
|
||||
// Only there for subsections so we must have a parent section
|
||||
for key in &subsections {
|
||||
if let Some(ref mut subsection) = self.sections.get_mut(*key) {
|
||||
subsection.parent_section = Some(sections[&grandparent_path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -219,6 +228,11 @@ impl Library {
|
|||
None
|
||||
}
|
||||
|
||||
/// Only used in tests
|
||||
pub fn get_section_key(&self, path: &PathBuf) -> Option<&Key> {
|
||||
self.paths_to_sections.get(path)
|
||||
}
|
||||
|
||||
pub fn get_section(&self, path: &PathBuf) -> Option<&Section> {
|
||||
self.sections.get(self.paths_to_sections.get(path).cloned().unwrap_or_default())
|
||||
}
|
||||
|
@ -231,6 +245,14 @@ impl Library {
|
|||
self.sections.get(key).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_section_mut_by_key(&mut self, key: Key) -> &mut Section {
|
||||
self.sections.get_mut(key).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_section_path_by_key(&self, key: Key) -> &str {
|
||||
&self.get_section_by_key(key).file.relative
|
||||
}
|
||||
|
||||
pub fn get_page(&self, path: &PathBuf) -> Option<&Page> {
|
||||
self.pages.get(self.paths_to_pages.get(path).cloned().unwrap_or_default())
|
||||
}
|
||||
|
@ -241,7 +263,6 @@ impl Library {
|
|||
|
||||
pub fn remove_section(&mut self, path: &PathBuf) -> Option<Section> {
|
||||
if let Some(k) = self.paths_to_sections.remove(path) {
|
||||
// TODO: delete section from parent subsection if there is one
|
||||
self.sections.remove(k)
|
||||
} else {
|
||||
None
|
||||
|
@ -250,7 +271,6 @@ impl Library {
|
|||
|
||||
pub fn remove_page(&mut self, path: &PathBuf) -> Option<Page> {
|
||||
if let Some(k) = self.paths_to_pages.remove(path) {
|
||||
// TODO: delete page from all parent sections
|
||||
self.pages.remove(k)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -240,10 +240,8 @@ impl Site {
|
|||
}
|
||||
|
||||
self.register_early_global_fns();
|
||||
self.render_markdown()?;
|
||||
self.populate_sections();
|
||||
// self.library.cache_all_pages();
|
||||
// self.library.cache_all_sections();
|
||||
self.render_markdown()?;
|
||||
self.populate_taxonomies()?;
|
||||
self.register_tera_global_fns();
|
||||
|
||||
|
|
|
@ -22,10 +22,6 @@ fn can_parse_site() {
|
|||
assert_eq!(site.library.pages().len(), 15);
|
||||
let posts_path = path.join("content").join("posts");
|
||||
|
||||
// Make sure we remove all the pwd + content from the sections
|
||||
let basic = site.library.get_page(&posts_path.join("simple.md")).unwrap();
|
||||
assert_eq!(basic.file.components, vec!["posts".to_string()]);
|
||||
|
||||
// Make sure the page with a url doesn't have any sections
|
||||
let url_post = site.library.get_page(&posts_path.join("fixed-url.md")).unwrap();
|
||||
assert_eq!(url_post.path, "a-fixed-url/");
|
||||
|
@ -41,10 +37,17 @@ fn can_parse_site() {
|
|||
let index_section = site.library.get_section(&path.join("content").join("_index.md")).unwrap();
|
||||
assert_eq!(index_section.subsections.len(), 3);
|
||||
assert_eq!(index_section.pages.len(), 1);
|
||||
assert!(index_section.parent_section.is_none());
|
||||
|
||||
let posts_section = site.library.get_section(&posts_path.join("_index.md")).unwrap();
|
||||
assert_eq!(posts_section.subsections.len(), 1);
|
||||
assert_eq!(posts_section.pages.len(), 7);
|
||||
assert_eq!(posts_section.parent_section, Some(*site.library.get_section_key(&index_section.file.path).unwrap()));
|
||||
|
||||
// Make sure we remove all the pwd + content from the sections
|
||||
let basic = site.library.get_page(&posts_path.join("simple.md")).unwrap();
|
||||
assert_eq!(basic.file.components, vec!["posts".to_string()]);
|
||||
assert_eq!(basic.parent_section, Some(*site.library.get_section_key(&posts_section.file.path).unwrap()));
|
||||
|
||||
let tutorials_section = site.library.get_section(&posts_path.join("tutorials").join("_index.md")).unwrap();
|
||||
assert_eq!(tutorials_section.subsections.len(), 2);
|
||||
|
@ -57,6 +60,7 @@ fn can_parse_site() {
|
|||
let devops_section = site.library.get_section(&posts_path.join("tutorials").join("devops").join("_index.md")).unwrap();
|
||||
assert_eq!(devops_section.subsections.len(), 0);
|
||||
assert_eq!(devops_section.pages.len(), 2);
|
||||
assert_eq!(devops_section.parent_section, Some(*site.library.get_section_key(&tutorials_section.file.path).unwrap()));
|
||||
|
||||
let prog_section = site.library.get_section(&posts_path.join("tutorials").join("programming").join("_index.md")).unwrap();
|
||||
assert_eq!(prog_section.subsections.len(), 0);
|
||||
|
|
|
@ -56,7 +56,7 @@ pub fn make_get_page(library: &Library) -> GlobalFn {
|
|||
for page in library.pages_values() {
|
||||
pages.insert(
|
||||
page.file.relative.clone(),
|
||||
to_value(library.get_page(&page.file.path).unwrap().to_serialized(library.pages())).unwrap(),
|
||||
to_value(library.get_page(&page.file.path).unwrap().to_serialized(library)).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ month: Number?;
|
|||
day: Number?;
|
||||
// Paths of colocated assets, relative to the content directory
|
||||
assets: Array<String>;
|
||||
// The relative path of the parent section if existing, for use with the `get_section` Tera function
|
||||
parent_section: String?;
|
||||
```
|
||||
|
||||
## Section variables
|
||||
|
@ -70,7 +72,9 @@ extra: HashMap<String, Any>;
|
|||
// Pages directly in this section, sorted if asked
|
||||
pages: Array<Pages>;
|
||||
// Direct subsections to this section, sorted by subsections weight
|
||||
subsections: Array<Section>;
|
||||
// This only contains the path to use in the `get_section` Tera function to get
|
||||
// the actual section object if you need it
|
||||
subsections: Array<String>;
|
||||
// Unicode word count
|
||||
word_count: Number;
|
||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
||||
|
@ -79,6 +83,8 @@ reading_time: Number;
|
|||
toc: Array<Header>;
|
||||
// Paths of colocated assets, relative to the content directory
|
||||
assets: Array<String>;
|
||||
// The relative path of the parent section if existing, for use with the `get_section` Tera function
|
||||
parent_section: String?;
|
||||
```
|
||||
|
||||
## Table of contents
|
||||
|
|
3
docs/templates/documentation.html
vendored
3
docs/templates/documentation.html
vendored
|
@ -8,7 +8,8 @@
|
|||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for subsection in section.subsections %}
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
{% for page in section.pages %}
|
||||
{{page.title}}
|
||||
{% endfor %}
|
||||
{% for subsection in section.subsections %}
|
||||
{% for sub in section.subsections %}
|
||||
{% set subsection = get_section(path=sub) %}
|
||||
{{subsection.title}}
|
||||
Sub-pages: {{subsection.pages | length}}
|
||||
{% endfor %}
|
||||
|
|
Loading…
Reference in a new issue