Merge pull request #994 from chris-morgan/misc

Chris Morgan’s whole bunch of miscellaneous work for landing
This commit is contained in:
Vincent Prouillet 2020-04-21 18:21:48 +02:00 committed by GitHub
commit 0ac70cb242
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 365 additions and 218 deletions

View file

@ -1,5 +1,24 @@
# Changelog # Changelog
## 0.11.0 (unreleased)
### Breaking
- RSS feed support has been altered to allow, *and default to*, Atom feeds, Atom being technically superior and just as widely-supported in normal use cases.
- New config value `feed_filename`, defaulting to `atom.xml` (change to `rss.xml` to reinstate the old behaviour)
- Config value `rss_limit` is renamed to `feed_limit`
- Config value `languages.*.rss` is renamed to `languages.*.feed`
- Config value `generate_rss` is renamed to `generate_feed`
Users with existing feeds should either set `feed_filename = "rss.xml"` in config.toml to keep things the same, or set up a 3xx redirect from rss.xml to atom.xml so that existing feed consumers arent broken.
- The feed template variable `last_build_date` is renamed to `last_updated` to more accurately reflect its semantics
- The sitemap templates `SitemapEntry` types `date` field has been renamed to `updated` to reflect that it will use the `updated` front-matter field if available, rather than `date`
### Other
- Add `updated` front-matter field for pages, which sitemap templates will use for the `SitemapEntry.date` field instead of the `date` front-matter field, and which the default Atom feed template will use
- Add `lang` to the feed template context
- Add `taxonomy` and `term` to the feed template context for taxonomy feeds
## 0.10.2 (unreleased) ## 0.10.2 (unreleased)
- Fix link checker not looking for anchor with capital id/name - Fix link checker not looking for anchor with capital id/name
@ -17,8 +36,6 @@
### Breaking ### Breaking
- Remove `toc` variable in section/page context and pass it to `page.toc` and `section.toc` instead so they are - Remove `toc` variable in section/page context and pass it to `page.toc` and `section.toc` instead so they are
accessible everywhere accessible everywhere
- [Slugification](https://en.wikipedia.org/wiki/Slug_(web_publishing)#Slug) of paths, taxonomies and anchors is now configurable. By default, everything will still be slugified like in previous versions.
See documentation for information on how to disable it.
### Other ### Other
- Add zenburn syntax highlighting theme - Add zenburn syntax highlighting theme

View file

@ -47,15 +47,15 @@ impl Default for Slugify {
pub struct Language { pub struct Language {
/// The language code /// The language code
pub code: String, pub code: String,
/// Whether to generate a RSS feed for that language, defaults to `false` /// Whether to generate a feed for that language, defaults to `false`
pub rss: bool, pub feed: bool,
/// Whether to generate search index for that language, defaults to `false` /// Whether to generate search index for that language, defaults to `false`
pub search: bool, pub search: bool,
} }
impl Default for Language { impl Default for Language {
fn default() -> Self { fn default() -> Self {
Language { code: String::new(), rss: false, search: false } Language { code: String::new(), feed: false, search: false }
} }
} }
@ -68,8 +68,8 @@ pub struct Taxonomy {
/// by this much /// by this much
pub paginate_by: Option<usize>, pub paginate_by: Option<usize>,
pub paginate_path: Option<String>, pub paginate_path: Option<String>,
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false /// Whether to generate a feed only for each taxonomy term, defaults to false
pub rss: bool, pub feed: bool,
/// The language for that taxonomy, only used in multilingual sites. /// The language for that taxonomy, only used in multilingual sites.
/// Defaults to the config `default_language` if not set /// Defaults to the config `default_language` if not set
pub lang: String, pub lang: String,
@ -99,7 +99,7 @@ impl Default for Taxonomy {
name: String::new(), name: String::new(),
paginate_by: None, paginate_by: None,
paginate_path: None, paginate_path: None,
rss: false, feed: false,
lang: String::new(), lang: String::new(),
} }
} }
@ -155,10 +155,13 @@ pub struct Config {
/// Defaults to "base16-ocean-dark" /// Defaults to "base16-ocean-dark"
pub highlight_theme: String, pub highlight_theme: String,
/// Whether to generate RSS. Defaults to false /// Whether to generate a feed. Defaults to false.
pub generate_rss: bool, pub generate_feed: bool,
/// The number of articles to include in the RSS feed. Defaults to including all items. /// The number of articles to include in the feed. Defaults to including all items.
pub rss_limit: Option<usize>, pub feed_limit: Option<usize>,
/// The filename to use for feeds. Used to find the template, too.
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
pub feed_filename: String,
/// If set, files from static/ will be hardlinked instead of copied to the output dir. /// If set, files from static/ will be hardlinked instead of copied to the output dir.
pub hard_link_static: bool, pub hard_link_static: bool,
@ -276,7 +279,8 @@ impl Config {
/// Makes a url, taking into account that the base url might have a trailing slash /// Makes a url, taking into account that the base url might have a trailing slash
pub fn make_permalink(&self, path: &str) -> String { pub fn make_permalink(&self, path: &str) -> String {
let trailing_bit = if path.ends_with('/') || path.ends_with("rss.xml") || path.is_empty() { let trailing_bit =
if path.ends_with('/') || path.ends_with(&self.feed_filename) || path.is_empty() {
"" ""
} else { } else {
"/" "/"
@ -384,8 +388,9 @@ impl Default for Config {
highlight_theme: "base16-ocean-dark".to_string(), highlight_theme: "base16-ocean-dark".to_string(),
default_language: "en".to_string(), default_language: "en".to_string(),
languages: Vec::new(), languages: Vec::new(),
generate_rss: false, generate_feed: false,
rss_limit: None, feed_limit: None,
feed_filename: "atom.xml".to_string(),
hard_link_static: false, hard_link_static: false,
taxonomies: Vec::new(), taxonomies: Vec::new(),
compile_sass: false, compile_sass: false,
@ -493,10 +498,10 @@ hello = "world"
// https://github.com/Keats/gutenberg/issues/486 // https://github.com/Keats/gutenberg/issues/486
#[test] #[test]
fn doesnt_add_trailing_slash_to_rss() { fn doesnt_add_trailing_slash_to_feed() {
let mut config = Config::default(); let mut config = Config::default();
config.base_url = "http://vincent.is/".to_string(); config.base_url = "http://vincent.is/".to_string();
assert_eq!(config.make_permalink("rss.xml"), "http://vincent.is/rss.xml"); assert_eq!(config.make_permalink("atom.xml"), "http://vincent.is/atom.xml");
} }
#[test] #[test]

0
components/errors/src/lib.rs Executable file → Normal file
View file

View file

@ -16,6 +16,9 @@ pub struct PageFrontMatter {
pub title: Option<String>, pub title: Option<String>,
/// Description in <meta> that appears when linked, e.g. on twitter /// Description in <meta> that appears when linked, e.g. on twitter
pub description: Option<String>, pub description: Option<String>,
/// Updated date
#[serde(default, deserialize_with = "from_toml_datetime")]
pub updated: Option<String>,
/// Date if we want to order pages (ie blog post) /// Date if we want to order pages (ie blog post)
#[serde(default, deserialize_with = "from_toml_datetime")] #[serde(default, deserialize_with = "from_toml_datetime")]
pub date: Option<String>, pub date: Option<String>,
@ -117,6 +120,7 @@ impl Default for PageFrontMatter {
PageFrontMatter { PageFrontMatter {
title: None, title: None,
description: None, description: None,
updated: None,
date: None, date: None,
datetime: None, datetime: None,
datetime_tuple: None, datetime_tuple: None,

View file

@ -195,7 +195,7 @@ mod tests {
#[test] #[test]
fn can_find_valid_language_in_page() { fn can_find_valid_language_in_page() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page( let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"), &Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"),
&PathBuf::new(), &PathBuf::new(),
@ -208,7 +208,7 @@ mod tests {
#[test] #[test]
fn can_find_valid_language_in_page_with_assets() { fn can_find_valid_language_in_page_with_assets() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page( let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"), &Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"),
&PathBuf::new(), &PathBuf::new(),
@ -234,7 +234,7 @@ mod tests {
#[test] #[test]
fn errors_on_unknown_language_in_page_with_i18n_on() { fn errors_on_unknown_language_in_page_with_i18n_on() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("it"), rss: false, search: false }); config.languages.push(Language { code: String::from("it"), feed: false, search: false });
let mut file = FileInfo::new_page( let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"), &Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"),
&PathBuf::new(), &PathBuf::new(),
@ -246,7 +246,7 @@ mod tests {
#[test] #[test]
fn can_find_valid_language_in_section() { fn can_find_valid_language_in_section() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_section( let mut file = FileInfo::new_section(
&Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"), &Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"),
&PathBuf::new(), &PathBuf::new(),
@ -273,7 +273,7 @@ mod tests {
#[test] #[test]
fn correct_canonical_after_find_language() { fn correct_canonical_after_find_language() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page( let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"), &Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"),
&PathBuf::new(), &PathBuf::new(),

View file

@ -770,7 +770,7 @@ Hello world
#[test] #[test]
fn can_specify_language_in_filename() { fn can_specify_language_in_filename() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
+++ +++
@ -787,7 +787,7 @@ Bonjour le monde"#
#[test] #[test]
fn can_specify_language_in_filename_with_date() { fn can_specify_language_in_filename_with_date() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
+++ +++
@ -806,7 +806,7 @@ Bonjour le monde"#
#[test] #[test]
fn i18n_frontmatter_path_overrides_default_permalink() { fn i18n_frontmatter_path_overrides_default_permalink() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
path = "bonjour" path = "bonjour"

View file

@ -350,7 +350,7 @@ mod tests {
#[test] #[test]
fn can_specify_language_in_filename() { fn can_specify_language_in_filename() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
+++ +++
@ -372,7 +372,7 @@ Bonjour le monde"#
#[test] #[test]
fn can_make_links_to_translated_sections_without_double_trailing_slash() { fn can_make_links_to_translated_sections_without_double_trailing_slash() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
+++ +++
@ -389,7 +389,7 @@ Bonjour le monde"#
#[test] #[test]
fn can_make_links_to_translated_subsections_with_trailing_slash() { fn can_make_links_to_translated_subsections_with_trailing_slash() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false }); config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#" let content = r#"
+++ +++
+++ +++

View file

@ -63,6 +63,7 @@ pub struct SerializingPage<'a> {
ancestors: Vec<String>, ancestors: Vec<String>,
title: &'a Option<String>, title: &'a Option<String>,
description: &'a Option<String>, description: &'a Option<String>,
updated: &'a Option<String>,
date: &'a Option<String>, date: &'a Option<String>,
year: Option<i32>, year: Option<i32>,
month: Option<u32>, month: Option<u32>,
@ -126,6 +127,7 @@ impl<'a> SerializingPage<'a> {
title: &page.meta.title, title: &page.meta.title,
description: &page.meta.description, description: &page.meta.description,
extra: &page.meta.extra, extra: &page.meta.extra,
updated: &page.meta.updated,
date: &page.meta.date, date: &page.meta.date,
year, year,
month, month,
@ -182,6 +184,7 @@ impl<'a> SerializingPage<'a> {
title: &page.meta.title, title: &page.meta.title,
description: &page.meta.description, description: &page.meta.description,
extra: &page.meta.extra, extra: &page.meta.extra,
updated: &page.meta.updated,
date: &page.meta.date, date: &page.meta.date,
year, year,
month, month,

View file

@ -6,7 +6,7 @@ use slotmap::DefaultKey;
use crate::content::Page; use crate::content::Page;
/// Used by the RSS feed /// Used by the feed
/// There to not have to import sorting stuff in the site crate /// There to not have to import sorting stuff in the site crate
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering { pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering {

View file

@ -458,7 +458,7 @@ mod tests {
#[test] #[test]
fn can_make_taxonomies_in_multiple_languages() { fn can_make_taxonomies_in_multiple_languages() {
let mut config = Config::default(); let mut config = Config::default();
config.languages.push(Language { rss: false, code: "fr".to_string(), search: false }); config.languages.push(Language { feed: false, code: "fr".to_string(), search: false });
let mut library = Library::new(2, 0, true); let mut library = Library::new(2, 0, true);
config.taxonomies = vec![ config.taxonomies = vec![
@ -569,7 +569,7 @@ mod tests {
let mut config = Config::default(); let mut config = Config::default();
config.slugify.taxonomies = SlugifyStrategy::Safe; config.slugify.taxonomies = SlugifyStrategy::Safe;
config.languages.push(Language { config.languages.push(Language {
rss: false, feed: false,
code: "fr".to_string(), code: "fr".to_string(),
..Language::default() ..Language::default()
}); });
@ -602,7 +602,7 @@ mod tests {
let mut config = Config::default(); let mut config = Config::default();
config.slugify.taxonomies = SlugifyStrategy::On; config.slugify.taxonomies = SlugifyStrategy::On;
config.languages.push(Language { config.languages.push(Language {
rss: false, feed: false,
code: "fr".to_string(), code: "fr".to_string(),
..Language::default() ..Language::default()
}); });

View file

@ -395,7 +395,17 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
match filename { match filename {
"sitemap.xml" => site.render_sitemap(), "sitemap.xml" => site.render_sitemap(),
"rss.xml" => site.render_rss_feed(site.library.read().unwrap().pages_values(), None), filename if filename == site.config.feed_filename => {
// FIXME: this is insufficient; for multilingual sites, its rendering the wrong
// content into the root feed, and its not regenerating any of the other feeds (other
// languages or taxonomies with feed enabled).
site.render_feed(
site.library.read().unwrap().pages_values(),
None,
&site.config.default_language,
None,
)
}
"split_sitemap_index.xml" => site.render_sitemap(), "split_sitemap_index.xml" => site.render_sitemap(),
"robots.txt" => site.render_robots(), "robots.txt" => site.render_robots(),
"single.html" | "list.html" => site.render_taxonomies(), "single.html" | "list.html" => site.render_taxonomies(),

View file

@ -108,7 +108,7 @@ base_url = "https://replace-this-with-your-url.com"
theme = "sample" theme = "sample"
taxonomies = [ taxonomies = [
{name = "tags", rss = true}, {name = "tags", feed = true},
{name = "categories"} {name = "categories"}
] ]

View file

@ -35,12 +35,19 @@ fn bench_render_sitemap(b: &mut test::Bencher) {
} }
#[bench] #[bench]
fn bench_render_rss_feed(b: &mut test::Bencher) { fn bench_render_feed(b: &mut test::Bencher) {
let mut site = setup_site("big-blog"); let mut site = setup_site("big-blog");
let tmp_dir = tempdir().expect("create temp dir"); let tmp_dir = tempdir().expect("create temp dir");
let public = &tmp_dir.path().join("public"); let public = &tmp_dir.path().join("public");
site.set_output_path(&public); site.set_output_path(&public);
b.iter(|| site.render_rss_feed(site.library.read().unwrap().pages_values(), None).unwrap()); b.iter(|| {
site.render_feed(
site.library.read().unwrap().pages_values(),
None,
&site.config.default_language,
None,
).unwrap();
});
} }
#[bench] #[bench]

View file

@ -8,13 +8,15 @@ use std::sync::{Arc, Mutex, RwLock};
use glob::glob; use glob::glob;
use rayon::prelude::*; use rayon::prelude::*;
use sass_rs::{compile_file, Options as SassOptions, OutputStyle}; use sass_rs::{compile_file, Options as SassOptions, OutputStyle};
use serde_derive::Serialize;
use tera::{Context, Tera}; use tera::{Context, Tera};
use config::{get_config, Config}; use config::{get_config, Config, Taxonomy as TaxonomyConfig};
use errors::{bail, Error, ErrorKind, Result}; use errors::{bail, Error, ErrorKind, Result};
use front_matter::InsertAnchor; use front_matter::InsertAnchor;
use library::{ use library::{
find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy, find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy,
TaxonomyItem,
}; };
use link_checker::check_url; use link_checker::check_url;
use templates::{global_fns, render_redirect_template, ZOLA_TERA}; use templates::{global_fns, render_redirect_template, ZOLA_TERA};
@ -45,6 +47,23 @@ pub struct Site {
include_drafts: bool, include_drafts: bool,
} }
#[derive(Debug, Clone, PartialEq, Serialize)]
struct SerializedTaxonomyItem<'a> {
name: &'a str,
slug: &'a str,
permalink: &'a str,
}
impl<'a> SerializedTaxonomyItem<'a> {
pub fn from_item(item: &'a TaxonomyItem) -> Self {
SerializedTaxonomyItem {
name: &item.name,
slug: &item.slug,
permalink: &item.permalink,
}
}
}
impl Site { impl Site {
/// Parse a site at the given path. Defaults to the current dir /// Parse a site at the given path. Defaults to the current dir
/// Passing in a path is used in tests and when --root argument is passed /// Passing in a path is used in tests and when --root argument is passed
@ -626,15 +645,17 @@ impl Site {
} }
/// Inject live reload script tag if in live reload mode /// Inject live reload script tag if in live reload mode
fn inject_livereload(&self, html: String) -> String { fn inject_livereload(&self, mut html: String) -> String {
if let Some(port) = self.live_reload { if let Some(port) = self.live_reload {
return html.replace( let script = format!(
"</body>", r#"<script src="/livereload.js?port={}&amp;mindelay=10"></script>"#,
&format!( port,
r#"<script src="/livereload.js?port={}&amp;mindelay=10"></script></body>"#,
port
),
); );
if let Some(index) = html.rfind("</body>") {
html.insert_str(index, &script);
} else {
html.push_str(&script);
}
} }
html html
@ -743,8 +764,9 @@ impl Site {
self.render_sitemap()?; self.render_sitemap()?;
let library = self.library.read().unwrap(); let library = self.library.read().unwrap();
if self.config.generate_rss { if self.config.generate_feed {
let pages = if self.config.is_multilingual() { let is_multilingual = self.config.is_multilingual();
let pages = if is_multilingual {
library library
.pages_values() .pages_values()
.iter() .iter()
@ -754,16 +776,26 @@ impl Site {
} else { } else {
library.pages_values() library.pages_values()
}; };
self.render_rss_feed(pages, None)?; self.render_feed(
pages,
None,
&self.config.default_language,
None,
)?;
} }
for lang in &self.config.languages { for lang in &self.config.languages {
if !lang.rss { if !lang.feed {
continue; continue;
} }
let pages = let pages =
library.pages_values().iter().filter(|p| p.lang == lang.code).cloned().collect(); library.pages_values().iter().filter(|p| p.lang == lang.code).cloned().collect();
self.render_rss_feed(pages, Some(&PathBuf::from(lang.code.clone())))?; self.render_feed(
pages,
Some(&PathBuf::from(lang.code.clone())),
&lang.code,
None,
)?;
} }
self.render_404()?; self.render_404()?;
@ -981,10 +1013,16 @@ impl Site {
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?; create_file(&path.join("index.html"), &self.inject_livereload(single_output))?;
} }
if taxonomy.kind.rss { if taxonomy.kind.feed {
self.render_rss_feed( self.render_feed(
item.pages.iter().map(|p| library.get_page_by_key(*p)).collect(), item.pages.iter().map(|p| library.get_page_by_key(*p)).collect(),
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))), Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))),
if self.config.is_multilingual() && !taxonomy.kind.lang.is_empty() {
&taxonomy.kind.lang
} else {
&self.config.default_language
},
Some((&taxonomy.kind, &item)),
) )
} else { } else {
Ok(()) Ok(())
@ -1043,30 +1081,39 @@ impl Site {
Ok(()) Ok(())
} }
/// Renders a RSS feed for the given path and at the given path /// Renders a feed for the given path and at the given path
/// If both arguments are `None`, it will render only the RSS feed for the whole /// If both arguments are `None`, it will render only the feed for the whole
/// site at the root folder. /// site at the root folder.
pub fn render_rss_feed( pub fn render_feed(
&self, &self,
all_pages: Vec<&Page>, all_pages: Vec<&Page>,
base_path: Option<&PathBuf>, base_path: Option<&PathBuf>,
lang: &str,
taxonomy_and_item: Option<(&TaxonomyConfig, &TaxonomyItem)>,
) -> Result<()> { ) -> Result<()> {
ensure_directory_exists(&self.output_path)?; ensure_directory_exists(&self.output_path)?;
let mut context = Context::new(); let mut context = Context::new();
let mut pages = all_pages.into_iter().filter(|p| p.meta.date.is_some()).collect::<Vec<_>>(); let mut pages = all_pages.into_iter().filter(|p| p.meta.date.is_some()).collect::<Vec<_>>();
// Don't generate a RSS feed if none of the pages has a date // Don't generate a feed if none of the pages has a date
if pages.is_empty() { if pages.is_empty() {
return Ok(()); return Ok(());
} }
pages.par_sort_unstable_by(sort_actual_pages_by_date); pages.par_sort_unstable_by(sort_actual_pages_by_date);
context.insert("last_build_date", &pages[0].meta.date.clone()); context.insert(
"last_updated",
pages.iter()
.filter_map(|page| page.meta.updated.as_ref())
.chain(pages[0].meta.date.as_ref())
.max() // I love lexicographically sorted date strings
.unwrap(), // Guaranteed because of pages[0].meta.date
);
let library = self.library.read().unwrap(); let library = self.library.read().unwrap();
// limit to the last n elements if the limit is set; otherwise use all. // limit to the last n elements if the limit is set; otherwise use all.
let num_entries = self.config.rss_limit.unwrap_or_else(|| pages.len()); let num_entries = self.config.feed_limit.unwrap_or_else(|| pages.len());
let p = pages let p = pages
.iter() .iter()
.take(num_entries) .take(num_entries)
@ -1075,16 +1122,28 @@ impl Site {
context.insert("pages", &p); context.insert("pages", &p);
context.insert("config", &self.config); context.insert("config", &self.config);
context.insert("lang", lang);
let rss_feed_url = if let Some(ref base) = base_path { let feed_filename = &self.config.feed_filename;
self.config.make_permalink(&base.join("rss.xml").to_string_lossy().replace('\\', "/")) let feed_url = if let Some(ref base) = base_path {
self.config.make_permalink(
&base
.join(feed_filename)
.to_string_lossy()
.replace('\\', "/"),
)
} else { } else {
self.config.make_permalink("rss.xml") self.config.make_permalink(feed_filename)
}; };
context.insert("feed_url", &rss_feed_url); context.insert("feed_url", &feed_url);
let feed = &render_template("rss.xml", &self.tera, context, &self.config.theme)?; if let Some((taxonomy, item)) = taxonomy_and_item {
context.insert("taxonomy", taxonomy);
context.insert("term", &SerializedTaxonomyItem::from_item(item));
}
let feed = &render_template(feed_filename, &self.tera, context, &self.config.theme)?;
if let Some(ref base) = base_path { if let Some(ref base) = base_path {
let mut output_path = self.output_path.clone(); let mut output_path = self.output_path.clone();
@ -1094,9 +1153,9 @@ impl Site {
create_directory(&output_path)?; create_directory(&output_path)?;
} }
} }
create_file(&output_path.join("rss.xml"), feed)?; create_file(&output_path.join(feed_filename), feed)?;
} else { } else {
create_file(&self.output_path.join("rss.xml"), feed)?; create_file(&self.output_path.join(feed_filename), feed)?;
} }
Ok(()) Ok(())
} }

View file

@ -14,7 +14,7 @@ use tera::{Map, Value};
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct SitemapEntry<'a> { pub struct SitemapEntry<'a> {
pub permalink: Cow<'a, str>, pub permalink: Cow<'a, str>,
pub date: Option<String>, pub updated: Option<String>,
pub extra: Option<&'a Map<String, Value>>, pub extra: Option<&'a Map<String, Value>>,
} }
@ -33,8 +33,8 @@ impl<'a> PartialEq for SitemapEntry<'a> {
impl<'a> Eq for SitemapEntry<'a> {} impl<'a> Eq for SitemapEntry<'a> {}
impl<'a> SitemapEntry<'a> { impl<'a> SitemapEntry<'a> {
pub fn new(permalink: Cow<'a, str>, date: Option<String>) -> Self { pub fn new(permalink: Cow<'a, str>, updated: Option<String>) -> Self {
SitemapEntry { permalink, date, extra: None } SitemapEntry { permalink, updated, extra: None }
} }
pub fn add_extra(&mut self, extra: &'a Map<String, Value>) { pub fn add_extra(&mut self, extra: &'a Map<String, Value>) {
@ -65,11 +65,10 @@ pub fn find_entries<'a>(
.pages_values() .pages_values()
.iter() .iter()
.map(|p| { .map(|p| {
let date = match p.meta.date { let mut entry = SitemapEntry::new(
Some(ref d) => Some(d.to_string()), Cow::Borrowed(&p.permalink),
None => None, p.meta.updated.clone().or_else(|| p.meta.date.clone()),
}; );
let mut entry = SitemapEntry::new(Cow::Borrowed(&p.permalink), date);
entry.add_extra(&p.meta.extra); entry.add_extra(&p.meta.extra);
entry entry
}) })

View file

@ -152,7 +152,7 @@ fn can_build_site_without_live_reload() {
// We do have categories // We do have categories
assert_eq!(file_exists!(public, "categories/index.html"), true); assert_eq!(file_exists!(public, "categories/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/index.html"), true); assert_eq!(file_exists!(public, "categories/a-category/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/rss.xml"), true); assert_eq!(file_exists!(public, "categories/a-category/atom.xml"), true);
// But no tags // But no tags
assert_eq!(file_exists!(public, "tags/index.html"), false); assert_eq!(file_exists!(public, "tags/index.html"), false);
@ -232,7 +232,7 @@ fn can_build_site_with_live_reload_and_drafts() {
// We do have categories // We do have categories
assert_eq!(file_exists!(public, "categories/index.html"), true); assert_eq!(file_exists!(public, "categories/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/index.html"), true); assert_eq!(file_exists!(public, "categories/a-category/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/rss.xml"), true); assert_eq!(file_exists!(public, "categories/a-category/atom.xml"), true);
// But no tags // But no tags
assert_eq!(file_exists!(public, "tags/index.html"), false); assert_eq!(file_exists!(public, "tags/index.html"), false);
@ -294,11 +294,11 @@ fn can_build_site_with_taxonomies() {
assert!(file_exists!(public, "categories/index.html")); assert!(file_exists!(public, "categories/index.html"));
assert!(file_exists!(public, "categories/a/index.html")); assert!(file_exists!(public, "categories/a/index.html"));
assert!(file_exists!(public, "categories/b/index.html")); assert!(file_exists!(public, "categories/b/index.html"));
assert!(file_exists!(public, "categories/a/rss.xml")); assert!(file_exists!(public, "categories/a/atom.xml"));
assert!(file_contains!( assert!(file_contains!(
public, public,
"categories/a/rss.xml", "categories/a/atom.xml",
"https://replace-this-with-your-url.com/categories/a/rss.xml" "https://replace-this-with-your-url.com/categories/a/atom.xml"
)); ));
// Extending from a theme works // Extending from a theme works
assert!(file_contains!(public, "categories/a/index.html", "EXTENDED")); assert!(file_contains!(public, "categories/a/index.html", "EXTENDED"));
@ -513,7 +513,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
name: "tags".to_string(), name: "tags".to_string(),
paginate_by: Some(2), paginate_by: Some(2),
paginate_path: None, paginate_path: None,
rss: true, feed: true,
lang: site.config.default_language.clone(), lang: site.config.default_language.clone(),
}); });
site.load().unwrap(); site.load().unwrap();
@ -547,9 +547,9 @@ fn can_build_site_with_pagination_for_taxonomy() {
// Tags // Tags
assert!(file_exists!(public, "tags/index.html")); assert!(file_exists!(public, "tags/index.html"));
// With RSS // With Atom
assert!(file_exists!(public, "tags/a/rss.xml")); assert!(file_exists!(public, "tags/a/atom.xml"));
assert!(file_exists!(public, "tags/b/rss.xml")); assert!(file_exists!(public, "tags/b/atom.xml"));
// And pagination! // And pagination!
assert!(file_exists!(public, "tags/a/page/1/index.html")); 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/b/page/1/index.html"));
@ -588,15 +588,15 @@ fn can_build_site_with_pagination_for_taxonomy() {
} }
#[test] #[test]
fn can_build_rss_feed() { fn can_build_feed() {
let (_, _tmp_dir, public) = build_site("test_site"); let (_, _tmp_dir, public) = build_site("test_site");
assert!(&public.exists()); assert!(&public.exists());
assert!(file_exists!(public, "rss.xml")); assert!(file_exists!(public, "atom.xml"));
// latest article is posts/extra-syntax.md // latest article is posts/extra-syntax.md
assert!(file_contains!(public, "rss.xml", "Extra Syntax")); assert!(file_contains!(public, "atom.xml", "Extra Syntax"));
// Next is posts/simple.md // Next is posts/simple.md
assert!(file_contains!(public, "rss.xml", "Simple article with shortcodes")); assert!(file_contains!(public, "atom.xml", "Simple article with shortcodes"));
} }
#[test] #[test]

View file

@ -115,15 +115,17 @@ fn can_build_multilingual_site() {
assert!(file_contains!(public, "sitemap.xml", "https://example.com/fr/blog/something-else/")); assert!(file_contains!(public, "sitemap.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_contains!(public, "sitemap.xml", "https://example.com/it/blog/something-else/")); assert!(file_contains!(public, "sitemap.xml", "https://example.com/it/blog/something-else/"));
// one rss per language // one feed per language
assert!(file_exists!(public, "rss.xml")); assert!(file_exists!(public, "atom.xml"));
assert!(file_contains!(public, "rss.xml", "https://example.com/blog/something-else/")); assert!(file_contains!(public, "atom.xml", "https://example.com/blog/something-else/"));
assert!(!file_contains!(public, "rss.xml", "https://example.com/fr/blog/something-else/")); assert!(!file_contains!(public, "atom.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_exists!(public, "fr/rss.xml")); assert!(file_contains!(public, "atom.xml", r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">"#));
assert!(!file_contains!(public, "fr/rss.xml", "https://example.com/blog/something-else/")); assert!(file_exists!(public, "fr/atom.xml"));
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/")); assert!(!file_contains!(public, "fr/atom.xml", "https://example.com/blog/something-else/"));
// Italian doesn't have RSS enabled assert!(file_contains!(public, "fr/atom.xml", "https://example.com/fr/blog/something-else/"));
assert!(!file_exists!(public, "it/rss.xml")); assert!(file_contains!(public, "fr/atom.xml", r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">"#));
// Italian doesn't have feed enabled
assert!(!file_exists!(public, "it/atom.xml"));
// Taxonomies are per-language // Taxonomies are per-language
// English // English
@ -131,7 +133,9 @@ fn can_build_multilingual_site() {
assert!(file_contains!(public, "authors/index.html", "Queen")); assert!(file_contains!(public, "authors/index.html", "Queen"));
assert!(!file_contains!(public, "authors/index.html", "Vincent")); assert!(!file_contains!(public, "authors/index.html", "Vincent"));
assert!(!file_exists!(public, "auteurs/index.html")); assert!(!file_exists!(public, "auteurs/index.html"));
assert!(file_exists!(public, "authors/queen-elizabeth/rss.xml")); assert!(file_exists!(public, "authors/queen-elizabeth/atom.xml"));
assert!(file_contains!(public, "authors/queen-elizabeth/atom.xml", r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">"#));
assert!(file_contains!(public, "authors/queen-elizabeth/atom.xml", r#"<title> - Queen Elizabeth</title>"#));
assert!(file_exists!(public, "tags/index.html")); assert!(file_exists!(public, "tags/index.html"));
assert!(file_contains!(public, "tags/index.html", "hello")); assert!(file_contains!(public, "tags/index.html", "hello"));
@ -142,7 +146,7 @@ fn can_build_multilingual_site() {
assert!(file_exists!(public, "fr/auteurs/index.html")); assert!(file_exists!(public, "fr/auteurs/index.html"));
assert!(!file_contains!(public, "fr/auteurs/index.html", "Queen")); assert!(!file_contains!(public, "fr/auteurs/index.html", "Queen"));
assert!(file_contains!(public, "fr/auteurs/index.html", "Vincent")); assert!(file_contains!(public, "fr/auteurs/index.html", "Vincent"));
assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/rss.xml")); assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/atom.xml"));
assert!(file_exists!(public, "fr/tags/index.html")); assert!(file_exists!(public, "fr/tags/index.html"));
assert!(file_contains!(public, "fr/tags/index.html", "bonjour")); assert!(file_contains!(public, "fr/tags/index.html", "bonjour"));

View file

@ -1,10 +1,3 @@
<!doctype html> <!doctype html>
<html> <title>404 Not Found</title>
<head> <h1>404 Not Found</h1>
<title>File Not Found: 404.</title>
</head>
<body>
<h1>Oops!</h1>
<h2>File Not Found: 404.</h2>
</body>
</html>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}">
<title>{{ config.title }}
{%- if term %} - {{ term.name }}
{%- endif -%}
</title>
{%- if config.description %}
<subtitle>{{ config.description }}</subtitle>
{%- endif %}
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
<link href="{{ config.base_url | safe }}"/>
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>{{ last_updated | date(format="%+") }}</updated>
<id>{{ feed_url | safe }}</id>
{%- for page in pages %}
<entry
{%- if page.lang != lang %} xml:lang="{{ page.lang }}"{% endif -%}
>
<title>{{ page.title }}</title>
<published>{{ page.date | date(format="%+") }}</published>
<updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated>
<link href="{{ page.permalink | safe }}" type="text/html"/>
<id>{{ page.permalink | safe }}</id>
<content type="html">{{ page.content }}</description>
</item>
{%- endfor %}
</feed>

View file

@ -1,12 +1,6 @@
<!DOCTYPE html> <!doctype html>
<html> <meta charset="utf-8">
<head> <link rel="canonical" href="{{ url | safe }}">
<title>Redirect</title> <meta http-equiv="refresh" content="0;url={{ url | safe }}">
<link rel="canonical" href="{{ url | safe }}" /> <title>Redirect</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <p><a href="{{ url | safe }}">Click here</a> to be redirected.</p>
<meta http-equiv="refresh" content="0;url={{ url | safe }}" />
</head>
<body>
<p><a href="{{ url | safe }}">Click here</a> to be redirected.</p>
</body>
</html>

View file

@ -7,8 +7,8 @@
<generator>Zola</generator> <generator>Zola</generator>
<language>{{ config.default_language }}</language> <language>{{ config.default_language }}</language>
<atom:link href="{{ feed_url | safe }}" rel="self" type="application/rss+xml"/> <atom:link href="{{ feed_url | safe }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate> <lastBuildDate>{{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{% for page in pages %} {%- for page in pages %}
<item> <item>
<title>{{ page.title }}</title> <title>{{ page.title }}</title>
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate> <pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
@ -16,6 +16,6 @@
<guid>{{ page.permalink | escape_xml | safe }}</guid> <guid>{{ page.permalink | escape_xml | safe }}</guid>
<description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description> <description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description>
</item> </item>
{% endfor %} {%- endfor %}
</channel> </channel>
</rss> </rss>

View file

@ -1,7 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}> <div {% if class %}class="{{class}}"{% endif %}>
<iframe src="https://www.streamable.com/e/{{id}}" <iframe src="https://www.streamable.com/e/{{id}}" scrolling="no" frameborder="0" allowfullscreen mozallowfullscreen webkitallowfullscreen></iframe>
scrolling="no"
frameborder="0"
allowfullscreen mozallowfullscreen webkitallowfullscreen>
</iframe>
</div> </div>

View file

@ -1,4 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}> <div {% if class %}class="{{class}}"{% endif %}>
<iframe src="//player.vimeo.com/video/{{id}}" webkitallowfullscreen mozallowfullscreen allowfullscreen> <iframe src="//player.vimeo.com/video/{{id}}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</iframe>
</div> </div>

View file

@ -1,4 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}> <div {% if class %}class="{{class}}"{% endif %}>
<iframe src="https://www.youtube.com/embed/{{id}}{% if autoplay %}?autoplay=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen> <iframe src="https://www.youtube.com/embed/{{id}}{% if autoplay %}?autoplay=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</iframe>
</div> </div>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for sitemap_entry in entries %} {%- for sitemap_entry in entries %}
<url> <url>
<loc>{{ sitemap_entry.permalink | escape_xml | safe }}</loc> <loc>{{ sitemap_entry.permalink | escape_xml | safe }}</loc>
{% if sitemap_entry.date %} {%- if sitemap_entry.updated %}
<lastmod>{{ sitemap_entry.date }}</lastmod> <lastmod>{{ sitemap_entry.updated }}</lastmod>
{% endif %} {%- endif %}
</url> </url>
{% endfor %} {%- endfor %}
</urlset> </urlset>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for sitemap in sitemaps %} {%- for sitemap in sitemaps %}
<sitemap> <sitemap>
<loc>{{ sitemap }}</loc> <loc>{{ sitemap }}</loc>
</sitemap> </sitemap>
{% endfor %} {%- endfor %}
</sitemapindex> </sitemapindex>

View file

@ -11,6 +11,7 @@ lazy_static! {
let mut tera = Tera::default(); let mut tera = Tera::default();
tera.add_raw_templates(vec![ tera.add_raw_templates(vec![
("__zola_builtins/404.html", include_str!("builtins/404.html")), ("__zola_builtins/404.html", include_str!("builtins/404.html")),
("__zola_builtins/atom.xml", include_str!("builtins/atom.xml")),
("__zola_builtins/rss.xml", include_str!("builtins/rss.xml")), ("__zola_builtins/rss.xml", include_str!("builtins/rss.xml")),
("__zola_builtins/sitemap.xml", include_str!("builtins/sitemap.xml")), ("__zola_builtins/sitemap.xml", include_str!("builtins/sitemap.xml")),
("__zola_builtins/robots.txt", include_str!("builtins/robots.txt")), ("__zola_builtins/robots.txt", include_str!("builtins/robots.txt")),

View file

@ -5,8 +5,9 @@ weight = 50
## Heading id and anchor insertion ## Heading id and anchor insertion
While rendering the Markdown content, a unique id will automatically be assigned to each heading. While rendering the Markdown content, a unique id will automatically be assigned to each heading.
This id is created by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug) if `slugify_paths` is enabled. This id is created by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug) if `slugify.anchors` is set to `"on"` (the default).
if `slugify_paths` is disabled, whitespaces are replaced by `_` and the following characters are stripped: `#`, `%`, `<`, `>`, `[`, `]`, `(`, `)`, \`, `^`, `{`, `|`, `}`. If `slugify.paths` is set to `"safe"`, whitespaces are replaced by `_` and the following characters are stripped: `#`, `%`, `<`, `>`, `[`, `]`, `(`, `)`, \`, `^`, `{`, `|`, `}`.
If `slugify.paths` is set to `"off"`, no modifications are made, and you may be left with nominally illegal ids.
A number is appended at the end if the slug already exists for that article A number is appended at the end if the slug already exists for that article
For example: For example:

View file

@ -11,9 +11,9 @@ to your `config.toml`. For example:
```toml ```toml
languages = [ languages = [
{code = "fr", rss = true}, # there will be a RSS feed for French content {code = "fr", feed = true}, # there will be a feed for French content
{code = "fr", search = true}, # there will be a Search Index for French content {code = "fr", search = true}, # there will be a Search Index for French content
{code = "it"}, # there won't be a RSS feed for Italian content {code = "it"}, # there won't be a feed for Italian content
] ]
``` ```

View file

@ -35,12 +35,13 @@ For any page within your content folder, its output path will be defined by eith
- its filename - its filename
Either way, these proposed path will be sanitized before being used. Either way, these proposed path will be sanitized before being used.
If `slugify_paths` is enabled in the site's config - the default - paths are [slugified](https://en.wikipedia.org/wiki/Clean_URL#Slug). If `slugify.paths` is set to `"on"` in the site's config - the default - paths are [slugified](https://en.wikipedia.org/wiki/Clean_URL#Slug).
Otherwise, a simpler sanitation is performed, outputting only valid NTFS paths. If it is set to `"safe"`, only sanitation is performed, with the following characters being removed: `<`, `>`, `:`, `/`, `|`, `?`, `*`, `#`, `\\`, `(`, `)`, `[`, `]` as well as newlines and tabulations. This ensures that the path can be represented on all operating systems.
The following characters are removed: `<`, `>`, `:`, `/`, `|`, `?`, `*`, `#`, `\\`, `(`, `)`, `[`, `]` as well as newlines and tabulations.
Additionally, trailing whitespace and dots are removed and whitespaces are replaced by `_`. Additionally, trailing whitespace and dots are removed and whitespaces are replaced by `_`.
**NOTE:** To produce URLs containing non-English characters (UTF8), `slugify_paths` needs to be set to `false`. If `slugify.paths` is set to `"off"`, no modifications are made.
If you want URLs containing non-ASCII characters, `slugify.paths` needs to be set to `"safe"` or `"off"`.
### Path from frontmatter ### Path from frontmatter
@ -56,7 +57,7 @@ slug = "femmes-libres-libération-kurde"
This is my article. This is my article.
``` ```
This frontmatter will output the article to `[base_url]/zines/femmes-libres-libération-kurde` with `slugify_paths` disabled, and to `[base_url]/zines/femmes-libres-liberation-kurde` with `slugify_enabled` enabled. This frontmatter will output the article to `[base_url]/zines/femmes-libres-libération-kurde` with `slugify.paths` set to `"safe"` or `"off"`, and to `[base_url]/zines/femmes-libres-liberation-kurde` with the default value for `slugify.paths` of `"on"`.
### Path from filename ### Path from filename
@ -66,7 +67,7 @@ When the article's output path is not specified in the frontmatter, it is extrac
If the path found starts with a datetime string (`YYYY-mm-dd` or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by an underscore (`_`) or a dash (`-`), this date is removed from the output path and will be used as the page date (unless already set in the front-matter). Note that the full RFC3339 datetime contains colons, which is not a valid character in a filename on Windows. If the path found starts with a datetime string (`YYYY-mm-dd` or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by an underscore (`_`) or a dash (`-`), this date is removed from the output path and will be used as the page date (unless already set in the front-matter). Note that the full RFC3339 datetime contains colons, which is not a valid character in a filename on Windows.
The output path extracted from the file path is then slugified or not depending on the `slugify_paths` config, as explained previously. The output path extracted from the file path is then slugified or not, depending on the `slugify.paths` config, as explained previously.
**Example:** The file `content/blog/2018-10-10-hello-world.md` will generated a page available at will be available at `[base_url]/hello-world`. **Example:** The file `content/blog/2018-10-10-hello-world.md` will generated a page available at will be available at `[base_url]/hello-world`.
@ -92,6 +93,10 @@ description = ""
# Setting this overrides a date set in the filename. # Setting this overrides a date set in the filename.
date = date =
# The last updated date of the post, if different from the date.
# Same format as `date`.
updated =
# The weight as defined on the Section page of the documentation. # The weight as defined on the Section page of the documentation.
# If the section variable `sort_by` is set to `weight`, then any page that lacks a `weight` # If the section variable `sort_by` is set to `weight`, then any page that lacks a `weight`
# will not be rendered. # will not be rendered.

View file

@ -13,7 +13,7 @@ A taxonomy has five variables:
- `paginate_by`: if this is set to a number, each term page will be paginated by this much. - `paginate_by`: if this is set to a number, each term page will be paginated by this much.
- `paginate_path`: if set, this path will be used by the paginated page and the page number will be appended after it. - `paginate_path`: if set, this path will be used by the paginated page and the page number will be appended after it.
For example the default would be page/1. For example the default would be page/1.
- `rss`: if set to `true`, an RSS feed will be generated for each term. - `feed`: if set to `true`, a feed will be generated for each term.
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for - `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for
**Example 1:** (one language) **Example 1:** (one language)
@ -52,7 +52,7 @@ categories = ["programming"]
In a similar manner to how section and pages calculate their output path: In a similar manner to how section and pages calculate their output path:
- the taxonomy name is never slugified - the taxonomy name is never slugified
- the taxonomy entry (eg. as specific tag) is slugified when `slugify_paths` is enabled in the configuration - the taxonomy term (eg. as specific tag) is slugified when `slugify.taxonomies` is enabled (`"on"`, the default) in the configuration
The taxonomy pages are then available at the following paths: The taxonomy pages are then available at the following paths:

View file

@ -17,11 +17,11 @@ used by Zola as well as their default values are listed below:
# The base URL of the site; the only required configuration variable. # The base URL of the site; the only required configuration variable.
base_url = "mywebsite.com" base_url = "mywebsite.com"
# The site title and description; used in RSS by default. # The site title and description; used in feeds by default.
title = "" title = ""
description = "" description = ""
# The default language; used in RSS. # The default language; used in feeds.
default_language = "en" default_language = "en"
# The site theme to use. # The site theme to use.
@ -34,12 +34,17 @@ highlight_code = false
# See below for list of allowed values. # See below for list of allowed values.
highlight_theme = "base16-ocean-dark" highlight_theme = "base16-ocean-dark"
# When set to "true", an RSS feed is automatically generated. # When set to "true", a feed is automatically generated.
generate_rss = false generate_feed = false
# The number of articles to include in the RSS feed. All items are included if # The filename to use for the feed. Used as the template filename, too.
# Defaults to "atom.xml", which has a builtin template that renders an Atom 1.0 feed.
# There is also a builtin template "rss.xml" that renders an RSS 2.0 feed.
# feed_filename = "atom.xml"
# The number of articles to include in the feed. All items are included if
# this limit is not set (the default). # this limit is not set (the default).
# rss_limit = 20 # feed_limit = 20
# When set to "true", files in the `static` directory are hard-linked. Useful for large # When set to "true", files in the `static` directory are hard-linked. Useful for large
# static files. Note that for this to work, both `static` and the # static files. Note that for this to work, both `static` and the
@ -50,10 +55,10 @@ generate_rss = false
# The taxonomies to be rendered for the site and their configuration. # The taxonomies to be rendered for the site and their configuration.
# Example: # Example:
# taxonomies = [ # taxonomies = [
# {name = "tags", rss = true}, # each tag will have its own RSS feed # {name = "tags", feed = true}, # each tag will have its own feed
# {name = "tags", lang = "fr"}, # you can have taxonomies with the same name in multiple languages # {name = "tags", lang = "fr"}, # you can have taxonomies with the same name in multiple languages
# {name = "categories", paginate_by = 5}, # 5 items per page for a term # {name = "categories", paginate_by = 5}, # 5 items per page for a term
# {name = "authors"}, # Basic definition: no RSS or pagination # {name = "authors"}, # Basic definition: no feed or pagination
# ] # ]
# #
taxonomies = [] taxonomies = []
@ -61,9 +66,9 @@ taxonomies = []
# The additional languages for the site. # The additional languages for the site.
# Example: # Example:
# languages = [ # languages = [
# {code = "fr", rss = true}, # there will be a RSS feed for French content # {code = "fr", feed = true}, # there will be a feed for French content
# {code = "fr", search = true}, # there will be a Search Index for French content # {code = "fr", search = true}, # there will be a Search Index for French content
# {code = "it"}, # there won't be a RSS feed for Italian content # {code = "it"}, # there won't be a feed for Italian content
# ] # ]
# #
languages = [] languages = []

View file

@ -0,0 +1,34 @@
+++
title = "Feeds"
weight = 50
aliases = ["/documentation/templates/rss/"]
+++
If the site `config.toml` file sets `generate_feed = true`, then Zola will
generate a feed file for the site, named according to the `feed_filename`
setting in `config.toml`, which defaults to `atom.xml`. Given the feed filename
`atom.xml`, the generated file will live at `base_url/atom.xml`, based upon the
`atom.xml` file in the `templates` directory, or the built-in Atom template.
`feed_filename` can be set to any value, but built-in templates are provided
for `atom.xml` (in the preferred Atom 1.0 format), and `rss.xml` (in the RSS
2.0 format). If you choose a different filename (e.g. `feed.xml`), you will
need to provide a template yourself.
**Only pages with a date will be available.**
The feed template gets five variables:
- `config`: the site config
- `feed_url`: the full url to that specific feed
- `last_updated`: the most recent `updated` or `date` field of any post
- `pages`: see [page variables](@/documentation/templates/pages-sections.md#page-variables)
for a detailed description of what this contains
- `lang`: the language code that applies to all of the pages in the feed,
if the site is multilingual, or `config.default_language` if it is not
Feeds for taxonomy terms get two more variables, using types from the
[taxonomies templates](@/documentation/templates/taxonomies.md):
- `taxonomy`: of type `TaxonomyConfig`
- `term`: of type `TaxonomyTerm`, but without `term.pages` (use `pages` instead)

View file

@ -13,7 +13,7 @@ to learn more about it first.
All templates live in the `templates` directory. If you are not sure what variables are available in a template, All templates live in the `templates` directory. If you are not sure what variables are available in a template,
you can place `{{ __tera_context }}` in the template to print the whole context. you can place `{{ __tera_context }}` in the template to print the whole context.
A few variables are available on all templates except RSS and the sitemap: A few variables are available on all templates except feeds and the sitemap:
- `config`: the [configuration](@/documentation/getting-started/configuration.md) without any modifications - `config`: the [configuration](@/documentation/getting-started/configuration.md) without any modifications
- `current_path`: the path (full URL without `base_url`) of the current page, never starting with a `/` - `current_path`: the path (full URL without `base_url`) of the current page, never starting with a `/`
@ -36,12 +36,13 @@ section variables. The `page.html` template has access to the page variables.
The page and section variables are described in more detail in the next section. The page and section variables are described in more detail in the next section.
## Built-in templates ## Built-in templates
Zola comes with three built-in templates: `rss.xml`, `sitemap.xml` and Zola comes with four built-in templates: `atom.xml` and `rss.xml` (described in
`robots.txt` (each is described in its own section of this documentation). [Feeds](@/documentation/templates/feeds.md)), `sitemap.xml` (described in [Sitemap](@/documentation/templates/sitemap.md)),
and `robots.txt` (described in [Robots.txt](@/documentation/templates/robots.md)).
Additionally, themes can add their own templates, which will be applied if not Additionally, themes can add their own templates, which will be applied if not
overridden. You can override built-in or theme templates by creating a template with overridden. You can override built-in or theme templates by creating a template with
the same name in the correct path. For example, you can override the RSS template by the same name in the correct path. For example, you can override the Atom template by
creating a `templates/rss.xml` file. creating a `templates/atom.xml` file.
## Custom templates ## Custom templates
In addition to the standard `index.html`, `section.html` and `page.html` templates, In addition to the standard `index.html`, `section.html` and `page.html` templates,

View file

@ -19,6 +19,8 @@ content: String;
title: String?; title: String?;
description: String?; description: String?;
date: String?; date: String?;
// `updated` will be the same as `date` if `date` is specified but `updated` is not in front-matter
updated: String?;
slug: String; slug: String;
path: String; path: String;
draft: Bool; draft: Bool;

View file

@ -1,18 +0,0 @@
+++
title = "RSS"
weight = 50
+++
If the site `config.toml` file sets `generate_rss = true`, then Zola will
generate an `rss.xml` page for the site, which will live at `base_url/rss.xml`. To
generate the `rss.xml` page, Zola will look for an `rss.xml` file in the `templates`
directory or, if one does not exist, it will use the use the built-in rss template.
**Only pages with a date will be available.**
The RSS template gets three variables in addition to `config`:
- `feed_url`: the full url to that specific feed
- `last_build_date`: the date of the latest post
- `pages`: see [page variables](@/documentation/templates/pages-sections.md#page-variables) for
a detailed description of what this contains

View file

@ -25,7 +25,7 @@ A `SitemapEntry` has the following fields:
```ts ```ts
permalink: String; permalink: String;
date: String?; updated: String?;
extra: Hashmap<String, Any>?; extra: Hashmap<String, Any>?;
``` ```

View file

@ -21,10 +21,10 @@ and `TaxonomyConfig` has the following fields:
```ts ```ts
name: String, name: String,
slug: String,
paginate_by: Number?; paginate_by: Number?;
paginate_path: String?; paginate_path: String?;
rss: Bool; feed: Bool;
lang: String;
``` ```
@ -64,5 +64,5 @@ term: TaxonomyTerm;
lang: String; lang: String;
``` ```
A paginated taxonomy term will also get a `paginator` variable; see the [pagination page] A paginated taxonomy term will also get a `paginator` variable; see the
(@/documentation/templates/pagination.md) for more details. [pagination page](@/documentation/templates/pagination.md) for more details.

View file

@ -100,8 +100,8 @@ Zulma has 3 taxonomies already set internally: `tags`, `cateogories` and `author
```toml ```toml
taxonomies = [ taxonomies = [
{name = "categories"}, {name = "categories"},
{name = "tags", paginate_by = 5, rss = true}, {name = "tags", paginate_by = 5, feed = true},
{name = "authors", rss = true}, {name = "authors", feed = true},
] ]
``` ```

View file

@ -55,9 +55,9 @@ The theme requires tags and categories taxonomies to be enabled in your `config.
```toml ```toml
taxonomies = [ taxonomies = [
# You can enable/disable RSS # You can enable/disable feeds
{name = "categories", rss = true}, {name = "categories", feed = true},
{name = "tags", rss = true}, {name = "tags", feed = true},
] ]
``` ```
If you want to paginate taxonomies pages, you will need to overwrite the templates If you want to paginate taxonomies pages, you will need to overwrite the templates

View file

@ -48,9 +48,9 @@ The theme requires tags and categories taxonomies to be enabled in your `config.
```toml ```toml
taxonomies = [ taxonomies = [
# You can enable/disable RSS # You can enable/disable feeds
{name = "categories", rss = true}, {name = "categories", feed = true},
{name = "tags", rss = true}, {name = "tags", feed = true},
] ]
``` ```
If you want to paginate taxonomies pages, you will need to overwrite the templates If you want to paginate taxonomies pages, you will need to overwrite the templates

0
sublime_themes/dracula.tmTheme Executable file → Normal file
View file

View file

@ -2,12 +2,12 @@ title = "My site"
base_url = "https://replace-this-with-your-url.com" base_url = "https://replace-this-with-your-url.com"
highlight_code = true highlight_code = true
compile_sass = true compile_sass = true
generate_rss = true generate_feed = true
theme = "sample" theme = "sample"
slugify_paths = true slugify_paths = true
taxonomies = [ taxonomies = [
{name = "categories", rss = true}, {name = "categories", feed = true},
] ]
extra_syntaxes = ["syntaxes"] extra_syntaxes = ["syntaxes"]

View file

@ -13,18 +13,18 @@ build_search_index = true
default_language = "en" default_language = "en"
generate_rss = true generate_feed = true
taxonomies = [ taxonomies = [
{name = "authors", rss = true}, {name = "authors", feed = true},
{name = "auteurs", lang = "fr"}, {name = "auteurs", lang = "fr"},
{name = "tags"}, {name = "tags"},
{name = "tags", lang = "fr"}, {name = "tags", lang = "fr"},
] ]
languages = [ languages = [
{code = "fr", rss = true}, {code = "fr", feed = true},
{code = "it", rss = false, search = true }, {code = "it", feed = false, search = true },
] ]
[extra] [extra]