Make sections draftable (#1218)

* make sections draftable

* add documentation paragraph about drafting sections
This commit is contained in:
Sam Vente 2020-10-30 16:14:07 +01:00 committed by Vincent Prouillet
parent da37db1258
commit c40fb91ba8
11 changed files with 141 additions and 63 deletions

2
Cargo.lock generated
View file

@ -2205,10 +2205,12 @@ dependencies = [
"search", "search",
"serde", "serde",
"serde_derive", "serde_derive",
"slotmap",
"tempfile", "tempfile",
"templates", "templates",
"tera", "tera",
"utils", "utils",
"walkdir",
] ]
[[package]] [[package]]

View file

@ -22,6 +22,8 @@ pub struct SectionFrontMatter {
/// Higher values means it will be at the end. Defaults to `0` /// Higher values means it will be at the end. Defaults to `0`
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub weight: usize, pub weight: usize,
/// whether the section is a draft
pub draft: bool,
/// Optional template, if we want to specify which template to render for that section /// Optional template, if we want to specify which template to render for that section
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub template: Option<String>, pub template: Option<String>,
@ -114,6 +116,7 @@ impl Default for SectionFrontMatter {
aliases: Vec::new(), aliases: Vec::new(),
generate_feed: false, generate_feed: false,
extra: Map::new(), extra: Map::new(),
draft: false,
} }
} }
} }

View file

@ -8,6 +8,7 @@ include = ["src/**/*"]
[dependencies] [dependencies]
tera = "1" tera = "1"
glob = "0.3" glob = "0.3"
walkdir = "2"
minify-html = "0.3.8" minify-html = "0.3.8"
rayon = "1" rayon = "1"
serde = "1" serde = "1"
@ -15,6 +16,7 @@ serde_derive = "1"
sass-rs = "0.2" sass-rs = "0.2"
lazy_static = "1.1" lazy_static = "1.1"
relative-path = "1" relative-path = "1"
slotmap = "0.4"
errors = { path = "../errors" } errors = { path = "../errors" }
config = { path = "../config" } config = { path = "../config" }

View file

@ -4,16 +4,17 @@ pub mod sass;
pub mod sitemap; pub mod sitemap;
pub mod tpls; pub mod tpls;
use slotmap::DefaultKey;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::remove_dir_all; use std::fs::remove_dir_all;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use glob::glob;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use minify_html::{with_friendly_error, Cfg}; use minify_html::{with_friendly_error, Cfg};
use rayon::prelude::*; use rayon::prelude::*;
use tera::{Context, Tera}; use tera::{Context, Tera};
use walkdir::{DirEntry, WalkDir};
use config::{get_config, Config}; use config::{get_config, Config};
use errors::{bail, Error, Result}; use errors::{bail, Error, Result};
@ -166,72 +167,107 @@ impl Site {
/// out of them /// out of them
pub fn load(&mut self) -> Result<()> { pub fn load(&mut self) -> Result<()> {
let base_path = self.base_path.to_string_lossy().replace("\\", "/"); let base_path = self.base_path.to_string_lossy().replace("\\", "/");
let content_glob = format!("{}/{}", base_path, "content/**/*.md");
let (section_entries, page_entries): (Vec<_>, Vec<_>) = glob(&content_glob)
.expect("Invalid glob")
.filter_map(|e| e.ok())
.filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.'))
.partition(|entry| {
entry.as_path().file_name().unwrap().to_str().unwrap().starts_with("_index.")
});
self.library = Arc::new(RwLock::new(Library::new(
page_entries.len(),
section_entries.len(),
self.config.is_multilingual(),
)));
let sections = {
let config = &self.config;
section_entries
.into_par_iter()
.map(|entry| {
let path = entry.as_path();
Section::from_file(path, config, &self.base_path)
})
.collect::<Vec<_>>()
};
let pages = {
let config = &self.config;
page_entries
.into_par_iter()
.filter(|entry| match &config.ignored_content_globset {
Some(gs) => !gs.is_match(entry.as_path()),
None => true,
})
.map(|entry| {
let path = entry.as_path();
Page::from_file(path, config, &self.base_path)
})
.collect::<Vec<_>>()
};
// Kinda duplicated code for add_section/add_page but necessary to do it that
// way because of the borrow checker
for section in sections {
let s = section?;
self.add_section(s, false)?;
}
self.create_default_index_sections()?;
self.library = Arc::new(RwLock::new(Library::new(0, 0, self.config.is_multilingual())));
let mut pages_insert_anchors = HashMap::new(); let mut pages_insert_anchors = HashMap::new();
for page in pages {
let p = page?; // not the most elegant loop, but this is necessary to use skip_current_dir
// Should draft pages be ignored? // which we can only decide to use after we've deserialised the section
if p.meta.draft && !self.include_drafts { // so it's kinda necessecary
let mut dir_walker = WalkDir::new(format!("{}/{}", base_path, "content/")).into_iter();
loop {
let entry: DirEntry = match dir_walker.next() {
None => break,
Some(Err(_)) => continue,
Some(Ok(entry)) => entry,
};
let path = entry.path();
let file_name = match path.file_name() {
None => continue,
Some(name) => name.to_str().unwrap(),
};
// ignore excluded content
match &self.config.ignored_content_globset {
Some(gs) => {
if gs.is_match(path) {
continue;
}
}
None => (),
}
// we process a section when we encounter the dir
// so we can process it before any of the pages
// therefore we should skip the actual file to avoid duplication
if file_name.starts_with("_index.") {
continue; continue;
} }
pages_insert_anchors.insert(
p.file.path.clone(), // skip hidden files and non md files
self.find_parent_section_insert_anchor(&p.file.parent.clone(), &p.lang), if !path.is_dir() && (!file_name.ends_with(".md") || file_name.starts_with(".")) {
); continue;
self.add_page(p, false)?; }
// is it a section or not?
if path.is_dir() {
// if we are processing a section we have to collect
// index files for all languages and process them simultaniously
// before any of the pages
let index_files = WalkDir::new(&path)
.max_depth(1)
.into_iter()
.filter_map(|e| match e {
Err(_) => None,
Ok(f) => {
let path_str = f.path().file_name().unwrap().to_str().unwrap();
if f.path().is_file()
&& path_str.starts_with("_index.")
&& path_str.ends_with(".md")
{
Some(f)
} else {
None
}
}
})
.collect::<Vec<DirEntry>>();
for index_file in index_files {
let section = match Section::from_file(
index_file.path(),
&self.config,
&self.base_path,
) {
Err(_) => continue,
Ok(sec) => sec,
};
// if the section is drafted we can skip the enitre dir
if section.meta.draft && !self.include_drafts {
dir_walker.skip_current_dir();
continue;
}
self.add_section(section, false)?;
}
} else {
let page = Page::from_file(path, &self.config, &self.base_path)
.expect("error deserialising page");
// should we skip drafts?
if page.meta.draft && !self.include_drafts {
continue;
}
pages_insert_anchors.insert(
page.file.path.clone(),
self.find_parent_section_insert_anchor(&page.file.parent.clone(), &page.lang),
);
self.add_page(page, false)?;
}
} }
self.create_default_index_sections()?;
{ {
let library = self.library.read().unwrap(); let library = self.library.read().unwrap();

View file

@ -177,6 +177,9 @@ fn can_build_site_without_live_reload() {
assert!(file_exists!(public, "nested_sass/sass.css")); assert!(file_exists!(public, "nested_sass/sass.css"));
assert!(file_exists!(public, "nested_sass/scss.css")); assert!(file_exists!(public, "nested_sass/scss.css"));
assert!(!file_exists!(public, "secret_section/index.html"));
assert!(!file_exists!(public, "secret_section/page.html"));
assert!(!file_exists!(public, "secret_section/secret_sub_section/hello.html"));
// no live reload code // no live reload code
assert_eq!( assert_eq!(
file_contains!(public, "index.html", "/livereload.js?port=1112&amp;mindelay=10"), file_contains!(public, "index.html", "/livereload.js?port=1112&amp;mindelay=10"),
@ -210,7 +213,7 @@ fn can_build_site_without_live_reload() {
#[test] #[test]
fn can_build_site_with_live_reload_and_drafts() { fn can_build_site_with_live_reload_and_drafts() {
let (_, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| { let (site, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| {
site.enable_live_reload(1000); site.enable_live_reload(1000);
site.include_drafts(); site.include_drafts();
(site, true) (site, true)
@ -254,6 +257,15 @@ fn can_build_site_with_live_reload_and_drafts() {
// Drafts are included // Drafts are included
assert!(file_exists!(public, "posts/draft/index.html")); assert!(file_exists!(public, "posts/draft/index.html"));
assert!(file_contains!(public, "sitemap.xml", "draft")); assert!(file_contains!(public, "sitemap.xml", "draft"));
// drafted sections are included
let library = site.library.read().unwrap();
assert_eq!(library.sections().len(), 14);
assert!(file_exists!(public, "secret_section/index.html"));
assert!(file_exists!(public, "secret_section/draft-page/index.html"));
assert!(file_exists!(public, "secret_section/page/index.html"));
assert!(file_exists!(public, "secret_section/secret_sub_section/hello/index.html"));
} }
#[test] #[test]

View file

@ -18,6 +18,9 @@ Any non-Markdown file in a section directory is added to the `assets` collection
[content overview](@/documentation/content/overview.md#asset-colocation). These files are then available in the [content overview](@/documentation/content/overview.md#asset-colocation). These files are then available in the
Markdown file using relative links. Markdown file using relative links.
## Drafting
Just like pages sections can be drafted by setting the `draft` option in the front matter. By default this is not done. When a section is drafted it's descendants like pages, subsections and assets will not be processed unless the `--drafts` flag is passed. Note that even pages that don't have a `draft` status will not be processed if one of their parent sections is drafted.
## Front matter ## Front matter
The `_index.md` file within a directory defines the content and metadata for that section. To set The `_index.md` file within a directory defines the content and metadata for that section. To set
@ -39,6 +42,9 @@ title = ""
description = "" description = ""
# A draft section is only loaded if the `--drafts` flag is passed to `zola build`, `zola serve` or `zola check`.
draft = false
# Used to sort pages by "date", "weight" or "none". See below for more information. # Used to sort pages by "date", "weight" or "none". See below for more information.
sort_by = "none" sort_by = "none"

View file

@ -0,0 +1,4 @@
+++
title="Drafted section"
draft=true
+++

View file

@ -0,0 +1,4 @@
+++
title="drafted page in drafted section"
draft=true
+++

View file

@ -0,0 +1,3 @@
+++
title="non draft page"
+++

View file

@ -0,0 +1,3 @@
+++
title="subsection of a secret section"
+++

View file

@ -0,0 +1,3 @@
+++
title="Is anyone ever going to read this?"
+++