commit
3685ed1672
|
@ -16,7 +16,7 @@ matrix:
|
|||
|
||||
# The earliest stable Rust version that works
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: 1.29.0
|
||||
rust: 1.30.0
|
||||
|
||||
|
||||
before_install: set -e
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.1 (2018-12-14)
|
||||
|
||||
- Fix deleting markdown file in `zola serve`
|
||||
- Fix pagination for taxonomies being broken and add missing documentation for it
|
||||
- Add missing pager pages from the sitemap
|
||||
- Allow and parse full RFC339 datetimes in filenames
|
||||
- Live reload is now enabled for the 404 page on serve
|
||||
|
||||
|
||||
## 0.5.0 (2018-11-17)
|
||||
|
||||
### Breaking
|
||||
|
|
765
Cargo.lock
generated
765
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "zola"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -37,6 +37,14 @@ impl Taxonomy {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paginate_path(&self) -> &str {
|
||||
if let Some(ref path) = self.paginate_path {
|
||||
path
|
||||
} else {
|
||||
"page"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Taxonomy {
|
||||
|
|
|
@ -20,8 +20,11 @@ use content::file_info::FileInfo;
|
|||
use content::ser::SerializingPage;
|
||||
|
||||
lazy_static! {
|
||||
// Check whether a string starts with yyyy-mm-dd{-,_}
|
||||
static ref DATE_IN_FILENAME: Regex = Regex::new(r"^^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(_|-)").unwrap();
|
||||
// Based on https://regex101.com/r/H2n38Z/1/tests
|
||||
// A regex parsing RFC3339 date followed by {_,-}, some characters and ended by .md
|
||||
static ref RFC3339_DATE: Regex = Regex::new(
|
||||
r"^(?P<datetime>(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9])))?)(_|-)(?P<slug>.+$)"
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -113,11 +116,11 @@ impl Page {
|
|||
page.word_count = Some(word_count);
|
||||
page.reading_time = Some(reading_time);
|
||||
|
||||
let mut has_date_in_name = false;
|
||||
if DATE_IN_FILENAME.is_match(&page.file.name) {
|
||||
has_date_in_name = true;
|
||||
let mut slug_from_dated_filename = None;
|
||||
if let Some(ref caps) = RFC3339_DATE.captures(&page.file.name.replace(".md", "")) {
|
||||
slug_from_dated_filename = Some(caps.name("slug").unwrap().as_str().to_string());
|
||||
if page.meta.date.is_none() {
|
||||
page.meta.date = Some(page.file.name[..10].to_string());
|
||||
page.meta.date = Some(caps.name("datetime").unwrap().as_str().to_string());
|
||||
page.meta.date_to_datetime();
|
||||
}
|
||||
}
|
||||
|
@ -132,9 +135,8 @@ impl Page {
|
|||
slugify(&page.file.name)
|
||||
}
|
||||
} else {
|
||||
if has_date_in_name {
|
||||
// skip the date + the {_,-}
|
||||
slugify(&page.file.name[11..])
|
||||
if let Some(slug) = slug_from_dated_filename {
|
||||
slugify(&slug)
|
||||
} else {
|
||||
slugify(&page.file.name)
|
||||
}
|
||||
|
@ -507,7 +509,7 @@ Hello world
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_date_from_filename() {
|
||||
fn can_get_date_from_short_date_in_filename() {
|
||||
let config = Config::default();
|
||||
let content = r#"
|
||||
+++
|
||||
|
@ -523,6 +525,23 @@ Hello world
|
|||
assert_eq!(page.slug, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_date_from_full_rfc3339_date_in_filename() {
|
||||
let config = Config::default();
|
||||
let content = r#"
|
||||
+++
|
||||
+++
|
||||
Hello world
|
||||
<!-- more -->"#
|
||||
.to_string();
|
||||
let res = Page::parse(Path::new("2018-10-02T15:00:00Z-hello.md"), &content, &config);
|
||||
assert!(res.is_ok());
|
||||
let page = res.unwrap();
|
||||
|
||||
assert_eq!(page.meta.date, Some("2018-10-02T15:00:00Z".to_string()));
|
||||
assert_eq!(page.slug, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontmatter_date_override_filename_date() {
|
||||
let config = Config::default();
|
||||
|
|
|
@ -80,7 +80,7 @@ impl Section {
|
|||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> {
|
||||
let (meta, content) = split_section_content(file_path, content)?;
|
||||
let mut section = Section::new(file_path, meta);
|
||||
section.raw_content = content.clone();
|
||||
section.raw_content = content;
|
||||
let (word_count, reading_time) = get_reading_analytics(§ion.raw_content);
|
||||
section.word_count = Some(word_count);
|
||||
section.reading_time = Some(reading_time);
|
||||
|
|
|
@ -14,7 +14,7 @@ use taxonomies::{Taxonomy, TaxonomyItem};
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum PaginationRoot<'a> {
|
||||
Section(&'a Section),
|
||||
Taxonomy(&'a Taxonomy),
|
||||
Taxonomy(&'a Taxonomy, &'a TaxonomyItem),
|
||||
}
|
||||
|
||||
/// A list of all the pages in the paginator with their index and links
|
||||
|
@ -93,14 +93,14 @@ impl<'a> Paginator<'a> {
|
|||
all_pages: &item.pages,
|
||||
pagers: Vec::with_capacity(item.pages.len() / paginate_by),
|
||||
paginate_by,
|
||||
root: PaginationRoot::Taxonomy(taxonomy),
|
||||
root: PaginationRoot::Taxonomy(taxonomy, item),
|
||||
permalink: item.permalink.clone(),
|
||||
path: format!("{}/{}", taxonomy.kind.name, item.slug),
|
||||
paginate_path: taxonomy
|
||||
.kind
|
||||
.paginate_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| "pages".to_string()),
|
||||
.unwrap_or_else(|| "page".to_string()),
|
||||
is_index: false,
|
||||
template: format!("{}/single.html", taxonomy.kind.name),
|
||||
};
|
||||
|
@ -212,8 +212,9 @@ impl<'a> Paginator<'a> {
|
|||
context
|
||||
.insert("section", &SerializingSection::from_section_basic(s, Some(library)));
|
||||
}
|
||||
PaginationRoot::Taxonomy(t) => {
|
||||
PaginationRoot::Taxonomy(t, item) => {
|
||||
context.insert("taxonomy", &t.kind);
|
||||
context.insert("term", &item.serialize(library));
|
||||
}
|
||||
};
|
||||
context.insert("current_url", &pager.permalink);
|
||||
|
@ -349,7 +350,7 @@ mod tests {
|
|||
|
||||
assert_eq!(paginator.pagers[1].index, 2);
|
||||
assert_eq!(paginator.pagers[1].pages.len(), 1);
|
||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/pages/2/");
|
||||
assert_eq!(paginator.pagers[1].path, "tags/something/pages/2/");
|
||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/page/2/");
|
||||
assert_eq!(paginator.pagers[1].path, "tags/something/page/2/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use library::Library;
|
|||
use sorting::sort_pages_by_date;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
struct SerializedTaxonomyItem<'a> {
|
||||
pub struct SerializedTaxonomyItem<'a> {
|
||||
name: &'a str,
|
||||
slug: &'a str,
|
||||
permalink: &'a str,
|
||||
|
@ -71,6 +71,10 @@ impl TaxonomyItem {
|
|||
|
||||
TaxonomyItem { name: name.to_string(), permalink, slug, pages }
|
||||
}
|
||||
|
||||
pub fn serialize<'a>(&'a self, library: &'a Library) -> SerializedTaxonomyItem<'a> {
|
||||
SerializedTaxonomyItem::from_item(self, library)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
|
|
|
@ -311,7 +311,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
if is_md {
|
||||
// only delete if it was able to be added in the first place
|
||||
if !index.exists() && !path.exists() {
|
||||
delete_element(site, path, is_section)?;
|
||||
return delete_element(site, path, is_section);
|
||||
}
|
||||
|
||||
// Added another .md in a assets directory
|
||||
|
@ -352,6 +352,7 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
site.render_orphan_pages()
|
||||
}
|
||||
"section.html" => site.render_sections(),
|
||||
"404.html" => site.render_404(),
|
||||
// Either the index or some unknown template changed
|
||||
// We can't really know what this change affects so rebuild all
|
||||
// the things
|
||||
|
|
|
@ -228,9 +228,22 @@ fn can_rebuild_after_renaming_section_folder() {
|
|||
fn can_rebuild_after_renaming_non_md_asset_in_colocated_folder() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir);
|
||||
let (old_path, new_path) = rename!(site_path, "content/posts/with-assets/zola.png", "gutenberg.png");
|
||||
let (old_path, new_path) =
|
||||
rename!(site_path, "content/posts/with-assets/zola.png", "gutenberg.png");
|
||||
|
||||
// Testing that we don't try to load some images as markdown or something
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_deleting_file() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir);
|
||||
let path = site_path.join("content").join("posts").join("fixed-slug.md");
|
||||
fs::remove_file(&path).unwrap();
|
||||
|
||||
let res = after_content_change(&mut site, &path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ pub struct Rendered {
|
|||
// means we will have example, example-1, example-2 etc
|
||||
fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
|
||||
if level == 0 && !anchors.contains(&name) {
|
||||
return name.to_string();
|
||||
return name;
|
||||
}
|
||||
|
||||
let new_anchor = format!("{}-{}", name, level + 1);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use pest::iterators::Pair;
|
||||
use pest::Parser;
|
||||
use tera::{to_value, Context, Map, Value};
|
||||
use regex::Regex;
|
||||
use tera::{to_value, Context, Map, Value};
|
||||
|
||||
use context::RenderContext;
|
||||
use errors::{Result, ResultExt};
|
||||
|
@ -20,9 +20,9 @@ lazy_static! {
|
|||
|
||||
fn replace_string_markers(input: &str) -> String {
|
||||
match input.chars().next().unwrap() {
|
||||
'"' => input.replace('"', "").to_string(),
|
||||
'\'' => input.replace('\'', "").to_string(),
|
||||
'`' => input.replace('`', "").to_string(),
|
||||
'"' => input.replace('"', ""),
|
||||
'\'' => input.replace('\'', ""),
|
||||
'`' => input.replace('`', ""),
|
||||
_ => unreachable!("How did you even get there"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -627,10 +627,8 @@ impl Site {
|
|||
ensure_directory_exists(&self.output_path)?;
|
||||
let mut context = Context::new();
|
||||
context.insert("config", &self.config);
|
||||
create_file(
|
||||
&self.output_path.join("404.html"),
|
||||
&render_template("404.html", &self.tera, &context, &self.config.theme)?,
|
||||
)
|
||||
let output = render_template("404.html", &self.tera, &context, &self.config.theme)?;
|
||||
create_file(&self.output_path.join("404.html"), &self.inject_livereload(output))
|
||||
}
|
||||
|
||||
/// Renders robots.txt
|
||||
|
@ -646,7 +644,6 @@ impl Site {
|
|||
|
||||
/// Renders all taxonomies with at least one non-draft post
|
||||
pub fn render_taxonomies(&self) -> Result<()> {
|
||||
// TODO: make parallel?
|
||||
for taxonomy in &self.taxonomies {
|
||||
self.render_taxonomy(taxonomy)?;
|
||||
}
|
||||
|
@ -669,24 +666,26 @@ impl Site {
|
|||
.items
|
||||
.par_iter()
|
||||
.map(|item| {
|
||||
let path = output_path.join(&item.slug);
|
||||
if taxonomy.kind.is_paginated() {
|
||||
self.render_paginated(
|
||||
&path,
|
||||
&Paginator::from_taxonomy(&taxonomy, item, &self.library),
|
||||
)?;
|
||||
} else {
|
||||
let single_output =
|
||||
taxonomy.render_term(item, &self.tera, &self.config, &self.library)?;
|
||||
create_directory(&path)?;
|
||||
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?;
|
||||
}
|
||||
|
||||
if taxonomy.kind.rss {
|
||||
self.render_rss_feed(
|
||||
item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(),
|
||||
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))),
|
||||
)?;
|
||||
}
|
||||
|
||||
if taxonomy.kind.is_paginated() {
|
||||
self.render_paginated(
|
||||
&output_path,
|
||||
&Paginator::from_taxonomy(&taxonomy, item, &self.library),
|
||||
)
|
||||
} else {
|
||||
let single_output =
|
||||
taxonomy.render_term(item, &self.tera, &self.config, &self.library)?;
|
||||
let path = output_path.join(&item.slug);
|
||||
create_directory(&path)?;
|
||||
create_file(&path.join("index.html"), &self.inject_livereload(single_output))
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.collect::<Result<()>>()
|
||||
|
@ -720,6 +719,18 @@ impl Site {
|
|||
.iter()
|
||||
.map(|s| SitemapEntry::new(s.permalink.clone(), None))
|
||||
.collect::<Vec<_>>();
|
||||
for section in
|
||||
self.library.sections_values().iter().filter(|s| s.meta.paginate_by.is_some())
|
||||
{
|
||||
let number_pagers = (section.pages.len() as f64
|
||||
/ section.meta.paginate_by.unwrap() as f64)
|
||||
.ceil() as isize;
|
||||
for i in 1..number_pagers + 1 {
|
||||
let permalink =
|
||||
format!("{}{}/{}/", section.permalink, section.meta.paginate_path, i);
|
||||
sections.push(SitemapEntry::new(permalink, None))
|
||||
}
|
||||
}
|
||||
sections.sort_by(|a, b| a.permalink.cmp(&b.permalink));
|
||||
context.insert("sections", §ions);
|
||||
|
||||
|
@ -733,12 +744,29 @@ impl Site {
|
|||
self.config.make_permalink(&format!("{}/{}", &name, item.slug)),
|
||||
None,
|
||||
));
|
||||
|
||||
if taxonomy.kind.is_paginated() {
|
||||
let number_pagers = (item.pages.len() as f64
|
||||
/ taxonomy.kind.paginate_by.unwrap() as f64)
|
||||
.ceil() as isize;
|
||||
for i in 1..number_pagers + 1 {
|
||||
let permalink = self.config.make_permalink(&format!(
|
||||
"{}/{}/{}/{}",
|
||||
name,
|
||||
item.slug,
|
||||
taxonomy.kind.paginate_path(),
|
||||
i
|
||||
));
|
||||
terms.push(SitemapEntry::new(permalink, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terms.sort_by(|a, b| a.permalink.cmp(&b.permalink));
|
||||
taxonomies.push(terms);
|
||||
}
|
||||
context.insert("taxonomies", &taxonomies);
|
||||
|
||||
context.insert("taxonomies", &taxonomies);
|
||||
context.insert("config", &self.config);
|
||||
|
||||
let sitemap = &render_template("sitemap.xml", &self.tera, &context, &self.config.theme)?;
|
||||
|
@ -771,7 +799,7 @@ impl Site {
|
|||
|
||||
pages.par_sort_unstable_by(sort_actual_pages_by_date);
|
||||
|
||||
context.insert("last_build_date", &pages[0].meta.date.clone().map(|d| d.to_string()));
|
||||
context.insert("last_build_date", &pages[0].meta.date.clone());
|
||||
// limit to the last n elements if the limit is set; otherwise use all.
|
||||
let num_entries = self.config.rss_limit.unwrap_or(pages.len());
|
||||
let p = pages
|
||||
|
@ -794,7 +822,7 @@ impl Site {
|
|||
let feed = &render_template("rss.xml", &self.tera, &context, &self.config.theme)?;
|
||||
|
||||
if let Some(ref base) = base_path {
|
||||
let mut output_path = self.output_path.clone().to_path_buf();
|
||||
let mut output_path = self.output_path.clone();
|
||||
for component in base.components() {
|
||||
output_path.push(component);
|
||||
if !output_path.exists() {
|
||||
|
@ -805,16 +833,13 @@ impl Site {
|
|||
} else {
|
||||
create_file(&self.output_path.join("rss.xml"), feed)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders a single section
|
||||
pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> {
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
let public = self.output_path.clone();
|
||||
|
||||
let mut output_path = public.to_path_buf();
|
||||
let mut output_path = self.output_path.clone();
|
||||
for component in §ion.file.components {
|
||||
output_path.push(component);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
extern crate config;
|
||||
extern crate site;
|
||||
extern crate tempfile;
|
||||
|
||||
|
@ -7,6 +8,7 @@ use std::fs::File;
|
|||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
|
||||
use config::Taxonomy;
|
||||
use site::Site;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
@ -465,6 +467,13 @@ fn can_build_site_with_pagination_for_section() {
|
|||
"posts/page/4/index.html",
|
||||
"Last: https://replace-this-with-your-url.com/posts/page/5/"
|
||||
));
|
||||
|
||||
// sitemap contains the pager pages
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"sitemap.xml",
|
||||
"<loc>https://replace-this-with-your-url.com/posts/page/4/</loc>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -510,6 +519,93 @@ fn can_build_site_with_pagination_for_index() {
|
|||
assert!(file_contains!(public, "index.html", "Last: https://replace-this-with-your-url.com/"));
|
||||
assert_eq!(file_contains!(public, "index.html", "has_prev"), false);
|
||||
assert_eq!(file_contains!(public, "index.html", "has_next"), false);
|
||||
|
||||
// sitemap contains the pager pages
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"sitemap.xml",
|
||||
"<loc>https://replace-this-with-your-url.com/page/1/</loc>"
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_build_site_with_pagination_for_taxonomy() {
|
||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
|
||||
path.push("test_site");
|
||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||
site.config.taxonomies.push(Taxonomy {
|
||||
name: "tags".to_string(),
|
||||
paginate_by: Some(2),
|
||||
paginate_path: None,
|
||||
rss: true,
|
||||
});
|
||||
site.load().unwrap();
|
||||
|
||||
for (i, (_, page)) in site.library.pages_mut().iter_mut().enumerate() {
|
||||
page.meta.taxonomies = {
|
||||
let mut taxonomies = HashMap::new();
|
||||
taxonomies
|
||||
.insert("tags".to_string(), vec![if i % 2 == 0 { "A" } else { "B" }.to_string()]);
|
||||
taxonomies
|
||||
};
|
||||
}
|
||||
site.populate_taxonomies().unwrap();
|
||||
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let public = &tmp_dir.path().join("public");
|
||||
site.set_output_path(&public);
|
||||
site.build().unwrap();
|
||||
|
||||
assert!(Path::new(&public).exists());
|
||||
|
||||
assert!(file_exists!(public, "index.html"));
|
||||
assert!(file_exists!(public, "sitemap.xml"));
|
||||
assert!(file_exists!(public, "robots.txt"));
|
||||
assert!(file_exists!(public, "a-fixed-url/index.html"));
|
||||
assert!(file_exists!(public, "posts/python/index.html"));
|
||||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
|
||||
assert!(file_exists!(public, "posts/with-assets/index.html"));
|
||||
|
||||
// Tags
|
||||
assert!(file_exists!(public, "tags/index.html"));
|
||||
// With RSS
|
||||
assert!(file_exists!(public, "tags/a/rss.xml"));
|
||||
assert!(file_exists!(public, "tags/b/rss.xml"));
|
||||
// And pagination!
|
||||
assert!(file_exists!(public, "tags/a/page/1/index.html"));
|
||||
assert!(file_exists!(public, "tags/b/page/1/index.html"));
|
||||
assert!(file_exists!(public, "tags/a/page/2/index.html"));
|
||||
assert!(file_exists!(public, "tags/b/page/2/index.html"));
|
||||
|
||||
// should redirect to posts/
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"tags/a/page/1/index.html",
|
||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/tags/a/\""
|
||||
));
|
||||
assert!(file_contains!(public, "tags/a/index.html", "Num pagers: 6"));
|
||||
assert!(file_contains!(public, "tags/a/index.html", "Page size: 2"));
|
||||
assert!(file_contains!(public, "tags/a/index.html", "Current index: 1"));
|
||||
assert!(!file_contains!(public, "tags/a/index.html", "has_prev"));
|
||||
assert!(file_contains!(public, "tags/a/index.html", "has_next"));
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"tags/a/index.html",
|
||||
"First: https://replace-this-with-your-url.com/tags/a/"
|
||||
));
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"tags/a/index.html",
|
||||
"Last: https://replace-this-with-your-url.com/tags/a/page/6/"
|
||||
));
|
||||
assert_eq!(file_contains!(public, "tags/a/index.html", "has_prev"), false);
|
||||
|
||||
// sitemap contains the pager pages
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"sitemap.xml",
|
||||
"<loc>https://replace-this-with-your-url.com/tags/a/page/6/</loc>"
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -144,7 +144,7 @@ pub fn make_get_taxonomy(all_taxonomies: &[Taxonomy], library: &Library) -> Glob
|
|||
None => {
|
||||
return Err(
|
||||
format!("`get_taxonomy` received an unknown taxonomy as kind: {}", kind).into()
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -180,12 +180,12 @@ pub fn make_get_taxonomy_url(all_taxonomies: &[Taxonomy]) -> GlobalFn {
|
|||
"`get_taxonomy_url` received an unknown taxonomy as kind: {}",
|
||||
kind
|
||||
)
|
||||
.into())
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref permalink) = container.get(&name) {
|
||||
return Ok(to_value(permalink.clone()).unwrap());
|
||||
if let Some(permalink) = container.get(&name) {
|
||||
return Ok(to_value(permalink).unwrap());
|
||||
}
|
||||
|
||||
Err(format!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind).into())
|
||||
|
@ -226,7 +226,7 @@ pub fn make_resize_image(imageproc: Arc<Mutex<imageproc::Processor>>) -> GlobalF
|
|||
return Err(format!("`resize_image`: Cannot find path: {}", path).into());
|
||||
}
|
||||
|
||||
let imageop = imageproc::ImageOp::from_args(path.clone(), &op, width, height, quality)
|
||||
let imageop = imageproc::ImageOp::from_args(path, &op, width, height, quality)
|
||||
.map_err(|e| format!("`resize_image`: {}", e))?;
|
||||
let url = imageproc.insert(imageop);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ create a **page** at `[base_url]/about`).
|
|||
If the file is given any name *other* than `index.md` or `_index.md`, then it will
|
||||
create a page with that name (without the `.md`). So naming a file in the root of your
|
||||
content directory `about.md` would also create a page at `[base_url]/about`.
|
||||
Another exception to that rule is that a filename starting with a YYYY-mm-dd date followed by
|
||||
Another exception to that rule is that a filename starting with a datetime (YYYY-mm-dd or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by
|
||||
an underscore (`_`) or a dash (`-`) will use that date as the page date, unless already set
|
||||
in the front-matter. The page name will be anything after `_`/`-` so a filename like `2018-10-10-hello-world.md` will
|
||||
be available at `[base_url]/hello-world`
|
||||
|
|
|
@ -26,6 +26,7 @@ Here is a full list of the supported languages and the short names you can use:
|
|||
- Plain Text -> ["txt"]
|
||||
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
|
||||
- Crystal -> ["cr"]
|
||||
- Dart -> ["dart"]
|
||||
- Elixir -> ["ex", "exs"]
|
||||
- fsharp -> ["fs"]
|
||||
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
|
||||
|
|
|
@ -14,7 +14,9 @@ Zola is available on [Brew](https://brew.sh):
|
|||
$ brew install zola
|
||||
```
|
||||
|
||||
## Linux
|
||||
## From source
|
||||
To build it from source, you will need to have Git, [Rust (at least 1.30) and Cargo](https://www.rust-lang.org/)
|
||||
installed. You will also need additional dependencies to compile [libsass](https://github.com/sass/libsass):
|
||||
|
||||
### Arch Linux
|
||||
|
||||
|
|
|
@ -7,10 +7,9 @@ Two things can get paginated: a section or a taxonomy term.
|
|||
|
||||
A paginated section gets the same `section` variable as a normal
|
||||
[section page](./documentation/templates/pages-sections.md#section-variables) minus its pages
|
||||
while a paginated taxonomy gets the a `taxonomy` variable of type `TaxonomyConfig`, equivalent
|
||||
to the taxonomy definition in the `config.toml`.
|
||||
while
|
||||
|
||||
In addition, a paginated page gets a `paginator` variable of the `Pager` type:
|
||||
Both get a paginated page gets a `paginator` variable of the `Pager` type:
|
||||
|
||||
```ts
|
||||
// How many items per page
|
||||
|
@ -33,3 +32,17 @@ pages: Array<Page>;
|
|||
// Which page are we on
|
||||
current_index: Number;
|
||||
```
|
||||
|
||||
## Section
|
||||
|
||||
A paginated section gets the same `section` variable as a normal
|
||||
[section page](./documentation/templates/pages-sections.md#section-variables) minus its pages.
|
||||
|
||||
## Taxonomy term
|
||||
|
||||
A paginated taxonomy gets two variables:
|
||||
|
||||
- a `taxonomy` variable of type `TaxonomyConfig`
|
||||
- a `term` variable of type `TaxonomyTerm`.
|
||||
|
||||
See the [taxonomies page](./documentation/templates/taxonomies.md) for a detailed version of the types.
|
||||
|
|
|
@ -17,8 +17,35 @@ permalink: String;
|
|||
pages: Array<Page>;
|
||||
```
|
||||
|
||||
## Non-paginated taxonomies
|
||||
If a taxonomy is not paginated, the templates get the following variables:
|
||||
and a `TaxonomyConfig`:
|
||||
|
||||
```ts
|
||||
name: String,
|
||||
slug: String,
|
||||
paginate_by: Number?;
|
||||
paginate_path: String?;
|
||||
rss: Bool;
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### Taxonomy list (`list.html`)
|
||||
|
||||
This template is never paginated and therefore get the following variables in all cases.
|
||||
|
||||
```ts
|
||||
// The site config
|
||||
config: Config;
|
||||
// The data of the taxonomy, from the config
|
||||
taxonomy: TaxonomyConfig;
|
||||
// The current full permalink for that page
|
||||
current_url: String;
|
||||
// The current path for that page
|
||||
current_path: String;
|
||||
// All terms for that taxonomy
|
||||
terms: Array<TaxonomyTerm>;
|
||||
```
|
||||
|
||||
|
||||
### Single term (`single.html`)
|
||||
```ts
|
||||
|
@ -34,18 +61,5 @@ current_path: String;
|
|||
term: TaxonomyTerm;
|
||||
```
|
||||
|
||||
### Taxonomy list (`list.html`)
|
||||
```ts
|
||||
// The site config
|
||||
config: Config;
|
||||
// The data of the taxonomy, from the config
|
||||
taxonomy: TaxonomyConfig;
|
||||
// The current full permalink for that page
|
||||
current_url: String;
|
||||
// The current path for that page
|
||||
current_path: String;
|
||||
// All terms for that taxonomy
|
||||
terms: Array<TaxonomyTerm>;
|
||||
```
|
||||
|
||||
## Paginated taxonomies
|
||||
A paginated taxonomy term will also get a `paginator` variable, see the [pagination page](./documentation/templates/pagination.md)
|
||||
for more details on that.
|
||||
|
|
|
@ -12,6 +12,7 @@ apps:
|
|||
zola:
|
||||
command: zola
|
||||
plugs:
|
||||
- home
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
|
|
202
sublime_syntaxes/Dart.sublime-syntax
Normal file
202
sublime_syntaxes/Dart.sublime-syntax
Normal file
|
@ -0,0 +1,202 @@
|
|||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Dart
|
||||
file_extensions:
|
||||
- dart
|
||||
scope: source.dart
|
||||
contexts:
|
||||
main:
|
||||
- match: ^(#!.*)$
|
||||
scope: meta.preprocessor.script.dart
|
||||
- match: ^\w*\b(library|import|part of|part|export)\b
|
||||
captures:
|
||||
0: keyword.other.import.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.dart
|
||||
- match: ;
|
||||
captures:
|
||||
0: punctuation.terminator.dart
|
||||
pop: true
|
||||
- include: strings
|
||||
- include: comments
|
||||
- match: \b(as|show|hide)\b
|
||||
scope: keyword.other.import.dart
|
||||
- include: comments
|
||||
- include: punctuation
|
||||
- include: annotations
|
||||
- include: keywords
|
||||
- include: constants-and-special-vars
|
||||
- include: strings
|
||||
annotations:
|
||||
- match: "@[a-zA-Z]+"
|
||||
scope: storage.type.annotation.dart
|
||||
comments:
|
||||
- match: /\*\*/
|
||||
scope: comment.block.empty.dart
|
||||
captures:
|
||||
0: punctuation.definition.comment.dart
|
||||
- include: comments-doc-oldschool
|
||||
- include: comments-doc
|
||||
- include: comments-inline
|
||||
comments-doc:
|
||||
- match: ///
|
||||
scope: comment.block.documentation.dart
|
||||
comments-doc-oldschool:
|
||||
- match: /\*\*
|
||||
push:
|
||||
- meta_scope: comment.block.documentation.dart
|
||||
- match: \*/
|
||||
pop: true
|
||||
- include: dartdoc
|
||||
comments-inline:
|
||||
- match: /\*
|
||||
push:
|
||||
- meta_scope: comment.block.dart
|
||||
- match: \*/
|
||||
pop: true
|
||||
- match: ((//).*)$
|
||||
captures:
|
||||
1: comment.line.double-slash.dart
|
||||
constants-and-special-vars:
|
||||
- match: (?<!\$)\b(true|false|null)\b(?!\$)
|
||||
scope: constant.language.dart
|
||||
- match: (?<!\$)\b(this|super)\b(?!\$)
|
||||
scope: variable.language.dart
|
||||
- match: '(?<!\$)\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b(?!\$)'
|
||||
scope: constant.numeric.dart
|
||||
- match: "(?<![a-zA-Z0-9_$])[_$]*[A-Z][a-zA-Z0-9_$]*"
|
||||
scope: support.class.dart
|
||||
- match: '([_$]*[a-z][a-zA-Z0-9_$]*)(\(|\s+=>)'
|
||||
captures:
|
||||
1: entity.name.function.dart
|
||||
dartdoc:
|
||||
- match: '(\[.*?\])'
|
||||
captures:
|
||||
0: variable.name.source.dart
|
||||
- match: " .*"
|
||||
captures:
|
||||
0: variable.name.source.dart
|
||||
- match: "```.*?$"
|
||||
push:
|
||||
- meta_content_scope: variable.other.source.dart
|
||||
- match: "```"
|
||||
pop: true
|
||||
- match: (`.*?`)
|
||||
captures:
|
||||
0: variable.other.source.dart
|
||||
- match: (`.*?`)
|
||||
captures:
|
||||
0: variable.other.source.dart
|
||||
- match: (\* (( ).*))$
|
||||
captures:
|
||||
2: variable.other.source.dart
|
||||
- match: (\* .*)$
|
||||
keywords:
|
||||
- match: (?<!\$)\bas\b(?!\$)
|
||||
scope: keyword.cast.dart
|
||||
- match: (?<!\$)\b(try|on|catch|finally|throw|rethrow)\b(?!\$)
|
||||
scope: keyword.control.catch-exception.dart
|
||||
- match: (?<!\$)\b(break|case|continue|default|do|else|for|if|in|return|switch|while)\b(?!\$)
|
||||
scope: keyword.control.dart
|
||||
- match: (?<!\$)\b(sync(\*)?|async(\*)?|await|yield(\*)?)\b(?!\$)
|
||||
scope: keyword.control.dart
|
||||
- match: (?<!\$)\bassert\b(?!\$)
|
||||
scope: keyword.control.dart
|
||||
- match: (?<!\$)\b(new)\b(?!\$)
|
||||
scope: keyword.control.new.dart
|
||||
- match: (?<!\$)\b(abstract|class|enum|extends|external|factory|implements|get|mixin|native|operator|set|typedef|with)\b(?!\$)
|
||||
scope: keyword.declaration.dart
|
||||
- match: (?<!\$)\b(is\!?)\b(?!\$)
|
||||
scope: keyword.operator.dart
|
||||
- match: '\?|:'
|
||||
scope: keyword.operator.ternary.dart
|
||||
- match: (<<|>>>?|~|\^|\||&)
|
||||
scope: keyword.operator.bitwise.dart
|
||||
- match: ((&|\^|\||<<|>>>?)=)
|
||||
scope: keyword.operator.assignment.bitwise.dart
|
||||
- match: (=>)
|
||||
scope: keyword.operator.closure.dart
|
||||
- match: (==|!=|<=?|>=?)
|
||||
scope: keyword.operator.comparison.dart
|
||||
- match: '(([+*/%-]|\~)=)'
|
||||
scope: keyword.operator.assignment.arithmetic.dart
|
||||
- match: (=)
|
||||
scope: keyword.operator.assignment.dart
|
||||
- match: (\-\-|\+\+)
|
||||
scope: keyword.operator.increment-decrement.dart
|
||||
- match: (\-|\+|\*|\/|\~\/|%)
|
||||
scope: keyword.operator.arithmetic.dart
|
||||
- match: (!|&&|\|\|)
|
||||
scope: keyword.operator.logical.dart
|
||||
- match: (?<!\$)\b(static|final|const)\b(?!\$)
|
||||
scope: storage.modifier.dart
|
||||
- match: (?<!\$)\b(?:void|bool|num|int|double|dynamic|var)\b(?!\$)
|
||||
scope: storage.type.primitive.dart
|
||||
punctuation:
|
||||
- match: ","
|
||||
scope: punctuation.comma.dart
|
||||
- match: ;
|
||||
scope: punctuation.terminator.dart
|
||||
- match: \.
|
||||
scope: punctuation.dot.dart
|
||||
string-interp:
|
||||
- match: '\$((\w+)|\{([^{}]+)\})'
|
||||
captures:
|
||||
2: variable.parameter.dart
|
||||
3: variable.parameter.dart
|
||||
- match: \\.
|
||||
scope: constant.character.escape.dart
|
||||
strings:
|
||||
- match: (?<!r)"""
|
||||
push:
|
||||
- meta_scope: string.interpolated.triple.double.dart
|
||||
- match: '"""(?!")'
|
||||
pop: true
|
||||
- include: string-interp
|
||||
- match: (?<!r)'''
|
||||
push:
|
||||
- meta_scope: string.interpolated.triple.single.dart
|
||||
- match: "'''(?!')"
|
||||
pop: true
|
||||
- include: string-interp
|
||||
- match: r"""
|
||||
push:
|
||||
- meta_scope: string.quoted.triple.double.dart
|
||||
- match: '"""(?!")'
|
||||
pop: true
|
||||
- match: r'''
|
||||
push:
|
||||
- meta_scope: string.quoted.triple.single.dart
|
||||
- match: "'''(?!')"
|
||||
pop: true
|
||||
- match: (?<!\|r)"
|
||||
push:
|
||||
- meta_scope: string.interpolated.double.dart
|
||||
- match: '"'
|
||||
pop: true
|
||||
- match: \n
|
||||
scope: invalid.string.newline
|
||||
- include: string-interp
|
||||
- match: r"
|
||||
push:
|
||||
- meta_scope: string.quoted.double.dart
|
||||
- match: '"'
|
||||
pop: true
|
||||
- match: \n
|
||||
scope: invalid.string.newline
|
||||
- match: (?<!\|r)'
|
||||
push:
|
||||
- meta_scope: string.interpolated.single.dart
|
||||
- match: "'"
|
||||
pop: true
|
||||
- match: \n
|
||||
scope: invalid.string.newline
|
||||
- include: string-interp
|
||||
- match: r'
|
||||
push:
|
||||
- meta_scope: string.quoted.single.dart
|
||||
- match: "'"
|
||||
pop: true
|
||||
- match: \n
|
||||
scope: invalid.string.newline
|
Binary file not shown.
|
@ -1,7 +1,21 @@
|
|||
Tag: {{ term.name }}
|
||||
{% if not paginator %}
|
||||
Tag: {{ term.name }}
|
||||
|
||||
{% for page in term.pages %}
|
||||
{% for page in term.pages %}
|
||||
<article>
|
||||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
|
||||
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Tag: {{ term.name }}
|
||||
{% for page in paginator.pages %}
|
||||
{{page.title|safe}}
|
||||
{% endfor %}
|
||||
Num pagers: {{ paginator.number_pagers }}
|
||||
Page size: {{ paginator.paginate_by }}
|
||||
Current index: {{ paginator.current_index }}
|
||||
First: {{ paginator.first | safe }}
|
||||
Last: {{ paginator.last | safe }}
|
||||
{% if paginator.previous %}has_prev{% endif%}
|
||||
{% if paginator.next %}has_next{% endif%}
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue