Page and config authors (#2024) (#2092)

The W3C feed validator fails to validate RSS 2.0 and Atom 1.0 feed
elements that do not contain a valid author. This change adds an
`authors: Vec<String>` to pages, as well as an `author: Option<String>`
to Config that will act as a default to use in RSS and Atom templates if
no page-level authors are specified.
This commit is contained in:
Seth Morabito 2023-02-11 06:09:12 -08:00 committed by Vincent Prouillet
parent b2f8a94b8d
commit f4a1e99b98
10 changed files with 165 additions and 42 deletions

View file

@ -58,6 +58,8 @@ pub struct Config {
/// If set, files from static/ will be hardlinked instead of copied to the output dir.
pub hard_link_static: bool,
pub taxonomies: Vec<taxonomies::TaxonomyConfig>,
/// The default author for pages.
pub author: Option<String>,
/// Whether to compile the `sass` directory and output the css files into the static folder
pub compile_sass: bool,
@ -103,6 +105,7 @@ pub struct SerializedConfig<'a> {
generate_feed: bool,
feed_filename: &'a str,
taxonomies: &'a [taxonomies::TaxonomyConfig],
author: &'a Option<String>,
build_search_index: bool,
extra: &'a HashMap<String, Toml>,
markdown: &'a markup::Markdown,
@ -324,6 +327,7 @@ impl Config {
generate_feed: options.generate_feed,
feed_filename: &options.feed_filename,
taxonomies: &options.taxonomies,
author: &self.author,
build_search_index: options.build_search_index,
extra: &self.extra,
markdown: &self.markdown,
@ -373,6 +377,7 @@ impl Default for Config {
feed_filename: "atom.xml".to_string(),
hard_link_static: false,
taxonomies: Vec::new(),
author: None,
compile_sass: false,
minify_html: false,
mode: Mode::Build,
@ -858,4 +863,15 @@ highlight_theme = "css"
let serialised = config.serialize(&config.default_language);
assert_eq!(serialised.markdown.highlight_theme, config.markdown.highlight_theme);
}
#[test]
fn sets_default_author_if_present() {
let config = r#"
title = "My Site"
base_url = "example.com"
author = "person@example.com (Some Person)"
"#;
let config = Config::parse(config).unwrap();
assert_eq!(config.author, Some("person@example.com (Some Person)".to_owned()))
}
}

View file

@ -49,6 +49,8 @@ pub struct PageFrontMatter {
pub taxonomies: HashMap<String, Vec<String>>,
/// Integer to use to order content. Highest is at the bottom, lowest first
pub weight: Option<usize>,
/// The authors of the page.
pub authors: Vec<String>,
/// All aliases for that page. Zola will create HTML templates that will
/// redirect to this
#[serde(skip_serializing)]
@ -153,6 +155,7 @@ impl Default for PageFrontMatter {
path: None,
taxonomies: HashMap::new(),
weight: None,
authors: Vec::new(),
aliases: Vec::new(),
template: None,
extra: Map::new(),
@ -502,4 +505,27 @@ taxonomies:
println!("{:?}", res);
assert!(res.is_err());
}
#[test_case(&RawFrontMatter::Toml(r#"
authors = ["person1@example.com (Person One)", "person2@example.com (Person Two)"]
"#); "toml")]
#[test_case(&RawFrontMatter::Yaml(r#"
title: Hello World
authors:
- person1@example.com (Person One)
- person2@example.com (Person Two)
"#); "yaml")]
fn can_parse_authors(content: &RawFrontMatter) {
let res = PageFrontMatter::parse(content);
assert!(res.is_ok());
let res2 = res.unwrap();
assert_eq!(res2.authors.len(), 2);
assert_eq!(
vec!(
"person1@example.com (Person One)".to_owned(),
"person2@example.com (Person Two)".to_owned()
),
res2.authors
);
}
}

View file

@ -345,6 +345,32 @@ Hello world"#;
assert_eq!(page.content, "<p>Hello world</p>\n".to_string());
}
#[test]
fn can_parse_author() {
let config = Config::default_for_test();
let content = r#"
+++
title = "Hello"
description = "hey there"
authors = ["person@example.com (A. Person)"]
+++
Hello world"#;
let res = Page::parse(Path::new("post.md"), content, &config, &PathBuf::new());
assert!(res.is_ok());
let mut page = res.unwrap();
page.render_markdown(
&HashMap::default(),
&Tera::default(),
&config,
InsertAnchor::None,
&HashMap::new(),
)
.unwrap();
assert_eq!(1, page.meta.authors.len());
assert_eq!("person@example.com (A. Person)", page.meta.authors.get(0).unwrap());
}
#[test]
fn test_can_make_url_from_sections_and_slug() {
let content = r#"

View file

@ -55,6 +55,7 @@ pub struct SerializingPage<'a> {
month: Option<u8>,
day: Option<u8>,
taxonomies: &'a HashMap<String, Vec<String>>,
authors: &'a [String],
extra: &'a Map<String, Value>,
path: &'a str,
components: &'a [String],
@ -119,6 +120,7 @@ impl<'a> SerializingPage<'a> {
month,
day,
taxonomies: &page.meta.taxonomies,
authors: &page.meta.authors,
path: &page.path,
components: &page.components,
summary: &page.summary,

View file

@ -836,6 +836,31 @@ fn panics_on_invalid_external_domain() {
site.load().expect("link check test_site");
}
#[test]
fn can_find_site_and_page_authors() {
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push("test_site");
let config_file = path.join("config.toml");
let mut site = Site::new(&path, config_file).unwrap();
site.load().unwrap();
let library = site.library.read().unwrap();
// The config has a global default author set.
let author = site.config.author;
assert_eq!(Some("config@example.com (Config Author)".to_string()), author);
let posts_path = path.join("content").join("posts");
let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap();
let p1 = &library.pages[&posts_section.pages[0]];
let p2 = &library.pages[&posts_section.pages[1]];
// Only the first page has had an author added.
assert_eq!(1, p1.meta.authors.len());
assert_eq!("page@example.com (Page Author)", p1.meta.authors.get(0).unwrap());
assert_eq!(0, p2.meta.authors.len());
}
// Follows test_site/themes/sample/templates/current_path.html
fn current_path(path: &str) -> String {
format!("[current_path]({})", path)

View file

@ -1,32 +1,43 @@
<?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 }}
<title>{{ config.title }}
{%- if term %} - {{ term.name }}
{%- elif section.title %} - {{ section.title }}
{%- endif -%}
</title>
{%- if config.description %}
<subtitle>{{ config.description }}</subtitle>
{%- endif %}
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
<link href="
{%- endif -%}
</title>
{%- if config.description %}
<subtitle>{{ config.description }}</subtitle>
{%- endif %}
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
<link href="
{%- if section -%}
{{ section.permalink | escape_xml | safe }}
{%- else -%}
{{ config.base_url | escape_xml | safe }}
{%- endif -%}
"/>
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>{{ last_updated | date(format="%+") }}</updated>
<id>{{ feed_url | safe }}</id>
{%- for page in pages %}
<entry xml:lang="{{ page.lang }}">
<title>{{ page.title }}</title>
<published>{{ page.date | date(format="%+") }}</published>
<updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated>
<link rel="alternate" href="{{ page.permalink | safe }}" type="text/html"/>
<id>{{ page.permalink | safe }}</id>
<content type="html">{{ page.content }}</content>
</entry>
{%- endfor %}
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>{{ last_updated | date(format="%+") }}</updated>
<id>{{ feed_url | safe }}</id>
{%- for page in pages %}
<entry xml:lang="{{ page.lang }}">
<title>{{ page.title }}</title>
<published>{{ page.date | date(format="%+") }}</published>
<updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated>
<author>
<name>
{%- if page.authors -%}
{{ page.authors[0] }}
{%- elif config.author -%}
{{ config.author }}
{%- else -%}
Unknown
{%- endif -%}
</name>
</author>
<link rel="alternate" href="{{ page.permalink | safe }}" type="text/html"/>
<id>{{ page.permalink | safe }}</id>
<content type="html">{{ page.content }}</content>
</entry>
{%- endfor %}
</feed>

View file

@ -6,25 +6,35 @@
{%- elif section.title %} - {{ section.title }}
{%- endif -%}
</title>
<link>{%- if section -%}
{{ section.permalink | escape_xml | safe }}
{%- else -%}
{{ config.base_url | escape_xml | safe }}
{%- endif -%}
</link>
<description>{{ config.description }}</description>
<generator>Zola</generator>
<language>{{ lang }}</language>
<atom:link href="{{ feed_url | safe }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{%- for page in pages %}
<item>
<title>{{ page.title }}</title>
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
<link>{{ page.permalink | escape_xml | safe }}</link>
<guid>{{ page.permalink | escape_xml | safe }}</guid>
<description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description>
</item>
{%- endfor %}
<link>
{%- if section -%}
{{ section.permalink | escape_xml | safe }}
{%- else -%}
{{ config.base_url | escape_xml | safe }}
{%- endif -%}
</link>
<description>{{ config.description }}</description>
<generator>Zola</generator>
<language>{{ lang }}</language>
<atom:link href="{{ feed_url | safe }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{%- for page in pages %}
<item>
<title>{{ page.title }}</title>
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
<author>
{%- if page.authors -%}
{{ page.authors[0] }}
{%- elif config.author -%}
{{ config.author }}
{%- else -%}
Unknown
{%- endif -%}
</author>
<link>{{ page.permalink | escape_xml | safe }}</link>
<guid>{{ page.permalink | escape_xml | safe }}</guid>
<description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description>
</item>
{%- endfor %}
</channel>
</rss>

View file

@ -126,6 +126,10 @@ path = ""
# current one. This takes an array of paths, not URLs.
aliases = []
# A list of page authors. If a site feed is enabled, the first author (if any)
# will be used as the page's author in the default feed template.
authors = []
# When set to "true", the page will be in the search index. This is only used if
# `build_search_index` is set to "true" in the Zola configuration and the parent section
# hasn't set `in_search_index` to "false" in its front matter.

View file

@ -11,6 +11,8 @@ taxonomies = [
ignored_content = ["*/ignored.md"]
author = "config@example.com (Config Author)"
[markdown]
highlight_code = true
highlight_theme = "custom_gruvbox"

View file

@ -2,4 +2,5 @@
title = "A transparent page"
description = ""
date = 2018-10-10
authors = ["page@example.com (Page Author)"]
+++