Reverse pagination (#1147)
* mention code block output change * Update snap * Update themes gallery (#1082) Co-authored-by: GitHub Action <action@github.com> * Deployment guide for Vercel * Change wording a bit * Update themes gallery (#1122) Co-authored-by: GitHub Action <action@github.com> * Add feed autodiscovery documentation (#1123) * Add feed autodiscovery documentation * Fix link in template * Docs/configuration update (#1126) * Update configuration documentation - Attempt to split the configuration file into sections to make it more readable and avoid configuration mistakes (#1056). - Move translation instructions to the right part. - Add a bit more explanations to the extra section. * Take into account @Keats feedbacks * Remove short notice about translation usage - A i18n page should be created to better explain it. * add fix for (#1135) Taxonomies with identical slugs now get merged (#1136) * add test and implementation for reverse pagination * incorporate review changes Co-authored-by: Michael Plotke <bdjnks@gmail.com> Co-authored-by: Vincent Prouillet <balthek@gmail.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Samyak Bakliwal <w3bcode@gmail.com> Co-authored-by: René Ribaud <uggla@free.fr>
This commit is contained in:
parent
5ec3a9ca65
commit
c143d95c4e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,3 +27,4 @@ stage
|
|||
shell.nix
|
||||
# vim temporary files
|
||||
**/.*.sw*
|
||||
.swp
|
||||
|
|
|
@ -71,7 +71,10 @@ pub fn split_section_content<'c>(
|
|||
|
||||
/// Split a file between the front matter and its content
|
||||
/// Returns a parsed `PageFrontMatter` and the rest of the content
|
||||
pub fn split_page_content<'c>(file_path: &Path, content: &'c str) -> Result<(PageFrontMatter, &'c str)> {
|
||||
pub fn split_page_content<'c>(
|
||||
file_path: &Path,
|
||||
content: &'c str,
|
||||
) -> Result<(PageFrontMatter, &'c str)> {
|
||||
let (front_matter, content) = split_content(file_path, content)?;
|
||||
let meta = PageFrontMatter::parse(&front_matter).map_err(|e| {
|
||||
Error::chain(
|
||||
|
|
|
@ -37,8 +37,6 @@ pub struct PageFrontMatter {
|
|||
/// Can't be an empty string if present
|
||||
pub path: Option<String>,
|
||||
pub taxonomies: HashMap<String, Vec<String>>,
|
||||
/// Integer to use to order content. Lowest is at the bottom, highest first
|
||||
pub order: Option<usize>,
|
||||
/// Integer to use to order content. Highest is at the bottom, lowest first
|
||||
pub weight: Option<usize>,
|
||||
/// All aliases for that page. Zola will create HTML templates that will
|
||||
|
@ -112,10 +110,6 @@ impl PageFrontMatter {
|
|||
self.datetime_tuple = self.datetime.map(|dt| (dt.year(), dt.month(), dt.day()));
|
||||
}
|
||||
|
||||
pub fn order(&self) -> usize {
|
||||
self.order.unwrap()
|
||||
}
|
||||
|
||||
pub fn weight(&self) -> usize {
|
||||
self.weight.unwrap()
|
||||
}
|
||||
|
@ -134,7 +128,6 @@ impl Default for PageFrontMatter {
|
|||
slug: None,
|
||||
path: None,
|
||||
taxonomies: HashMap::new(),
|
||||
order: None,
|
||||
weight: None,
|
||||
aliases: Vec::new(),
|
||||
in_search_index: true,
|
||||
|
|
|
@ -28,6 +28,9 @@ pub struct SectionFrontMatter {
|
|||
/// How many pages to be displayed per paginated page. No pagination will happen if this isn't set
|
||||
#[serde(skip_serializing)]
|
||||
pub paginate_by: Option<usize>,
|
||||
/// Whether to reverse the order of the pages before segmenting into pagers
|
||||
#[serde(skip_serializing)]
|
||||
pub paginate_reversed: bool,
|
||||
/// Path to be used by pagination: the page number will be appended after it. Defaults to `page`.
|
||||
#[serde(skip_serializing)]
|
||||
pub paginate_path: String,
|
||||
|
@ -100,6 +103,7 @@ impl Default for SectionFrontMatter {
|
|||
weight: 0,
|
||||
template: None,
|
||||
paginate_by: None,
|
||||
paginate_reversed: false,
|
||||
paginate_path: DEFAULT_PAGINATE_PATH.to_string(),
|
||||
render: true,
|
||||
redirect_to: None,
|
||||
|
|
|
@ -150,6 +150,11 @@ impl<'a> SerializingPage<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// currently only used in testing
|
||||
pub fn get_title(&'a self) -> &'a Option<String> {
|
||||
&self.title
|
||||
}
|
||||
|
||||
/// Same as from_page but does not fill sibling pages
|
||||
pub fn from_page_basic(page: &'a Page, library: Option<&'a Library>) -> Self {
|
||||
let mut year = None;
|
||||
|
|
|
@ -12,6 +12,8 @@ use crate::content::{Section, SerializingPage, SerializingSection};
|
|||
use crate::library::Library;
|
||||
use crate::taxonomies::{Taxonomy, TaxonomyItem};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum PaginationRoot<'a> {
|
||||
Section(&'a Section),
|
||||
|
@ -45,11 +47,13 @@ impl<'a> Pager<'a> {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Paginator<'a> {
|
||||
/// All pages in the section/taxonomy
|
||||
all_pages: &'a [DefaultKey],
|
||||
all_pages: Cow<'a, [DefaultKey]>,
|
||||
/// Pages split in chunks of `paginate_by`
|
||||
pub pagers: Vec<Pager<'a>>,
|
||||
/// How many content pages on a paginated page at max
|
||||
paginate_by: usize,
|
||||
/// whether to reverse before grouping
|
||||
paginate_reversed: bool,
|
||||
/// The thing we are creating the paginator for: section or taxonomy
|
||||
root: PaginationRoot<'a>,
|
||||
// Those below can be obtained from the root but it would make the code more complex than needed
|
||||
|
@ -66,10 +70,12 @@ impl<'a> Paginator<'a> {
|
|||
/// It will always at least create one pager (the first) even if there are not enough pages to paginate
|
||||
pub fn from_section(section: &'a Section, library: &'a Library) -> Paginator<'a> {
|
||||
let paginate_by = section.meta.paginate_by.unwrap();
|
||||
let paginate_reversed = section.meta.paginate_reversed;
|
||||
let mut paginator = Paginator {
|
||||
all_pages: §ion.pages,
|
||||
all_pages: Cow::from(§ion.pages[..]),
|
||||
pagers: Vec::with_capacity(section.pages.len() / paginate_by),
|
||||
paginate_by,
|
||||
paginate_reversed,
|
||||
root: PaginationRoot::Section(section),
|
||||
permalink: section.permalink.clone(),
|
||||
path: section.path.clone(),
|
||||
|
@ -91,9 +97,10 @@ impl<'a> Paginator<'a> {
|
|||
) -> Paginator<'a> {
|
||||
let paginate_by = taxonomy.kind.paginate_by.unwrap();
|
||||
let mut paginator = Paginator {
|
||||
all_pages: &item.pages,
|
||||
all_pages: Cow::Borrowed(&item.pages),
|
||||
pagers: Vec::with_capacity(item.pages.len() / paginate_by),
|
||||
paginate_by,
|
||||
paginate_reversed: false,
|
||||
root: PaginationRoot::Taxonomy(taxonomy, item),
|
||||
permalink: item.permalink.clone(),
|
||||
path: format!("/{}/{}/", taxonomy.kind.name, item.slug),
|
||||
|
@ -106,6 +113,7 @@ impl<'a> Paginator<'a> {
|
|||
template: format!("{}/single.html", taxonomy.kind.name),
|
||||
};
|
||||
|
||||
// taxonomy paginators have no sorting so we won't have to reverse
|
||||
paginator.fill_pagers(library);
|
||||
paginator
|
||||
}
|
||||
|
@ -116,8 +124,12 @@ impl<'a> Paginator<'a> {
|
|||
// the pages in the current pagers
|
||||
let mut current_page = vec![];
|
||||
|
||||
for key in self.all_pages {
|
||||
let page = library.get_page_by_key(*key);
|
||||
if self.paginate_reversed {
|
||||
self.all_pages.to_mut().reverse();
|
||||
}
|
||||
|
||||
for key in self.all_pages.to_mut().iter_mut() {
|
||||
let page = library.get_page_by_key(key.clone());
|
||||
current_page.push(page.to_serialized_basic(library));
|
||||
|
||||
if current_page.len() == self.paginate_by {
|
||||
|
@ -246,10 +258,11 @@ mod tests {
|
|||
|
||||
use super::Paginator;
|
||||
|
||||
fn create_section(is_index: bool) -> Section {
|
||||
fn create_section(is_index: bool, paginate_reversed: bool) -> Section {
|
||||
let mut f = SectionFrontMatter::default();
|
||||
f.paginate_by = Some(2);
|
||||
f.paginate_path = "page".to_string();
|
||||
f.paginate_reversed = paginate_reversed;
|
||||
let mut s = Section::new("content/_index.md", f, &PathBuf::new());
|
||||
if !is_index {
|
||||
s.path = "/posts/".to_string();
|
||||
|
@ -262,15 +275,22 @@ mod tests {
|
|||
s
|
||||
}
|
||||
|
||||
fn create_library(is_index: bool) -> (Section, Library) {
|
||||
let mut library = Library::new(3, 0, false);
|
||||
library.insert_page(Page::default());
|
||||
library.insert_page(Page::default());
|
||||
library.insert_page(Page::default());
|
||||
fn create_library(
|
||||
is_index: bool,
|
||||
num_pages: usize,
|
||||
paginate_reversed: bool,
|
||||
) -> (Section, Library) {
|
||||
let mut library = Library::new(num_pages, 0, false);
|
||||
for i in 1..=num_pages {
|
||||
let mut page = Page::default();
|
||||
page.meta.title = Some(i.to_string());
|
||||
library.insert_page(page);
|
||||
}
|
||||
|
||||
let mut draft = Page::default();
|
||||
draft.meta.draft = true;
|
||||
library.insert_page(draft);
|
||||
let mut section = create_section(is_index);
|
||||
let mut section = create_section(is_index, paginate_reversed);
|
||||
section.pages = library.pages().keys().collect();
|
||||
library.insert_section(section.clone());
|
||||
|
||||
|
@ -279,7 +299,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_can_create_paginator() {
|
||||
let (section, library) = create_library(false);
|
||||
let (section, library) = create_library(false, 3, false);
|
||||
let paginator = Paginator::from_section(§ion, &library);
|
||||
assert_eq!(paginator.pagers.len(), 2);
|
||||
|
||||
|
@ -294,9 +314,56 @@ mod tests {
|
|||
assert_eq!(paginator.pagers[1].path, "/posts/page/2/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_create_reversed_paginator() {
|
||||
// 6 pages, 5 normal and 1 draft
|
||||
let (section, library) = create_library(false, 5, true);
|
||||
let paginator = Paginator::from_section(§ion, &library);
|
||||
assert_eq!(paginator.pagers.len(), 3);
|
||||
|
||||
assert_eq!(paginator.pagers[0].index, 1);
|
||||
assert_eq!(paginator.pagers[0].pages.len(), 2);
|
||||
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts/");
|
||||
assert_eq!(paginator.pagers[0].path, "/posts/");
|
||||
assert_eq!(
|
||||
vec!["".to_string(), "5".to_string()],
|
||||
paginator.pagers[0]
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| p.get_title().as_ref().unwrap_or(&"".to_string()).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
|
||||
assert_eq!(paginator.pagers[1].index, 2);
|
||||
assert_eq!(paginator.pagers[1].pages.len(), 2);
|
||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2/");
|
||||
assert_eq!(paginator.pagers[1].path, "/posts/page/2/");
|
||||
assert_eq!(
|
||||
vec!["4".to_string(), "3".to_string()],
|
||||
paginator.pagers[1]
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| p.get_title().as_ref().unwrap_or(&"".to_string()).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
|
||||
assert_eq!(paginator.pagers[2].index, 3);
|
||||
assert_eq!(paginator.pagers[2].pages.len(), 2);
|
||||
assert_eq!(paginator.pagers[2].permalink, "https://vincent.is/posts/page/3/");
|
||||
assert_eq!(paginator.pagers[2].path, "/posts/page/3/");
|
||||
assert_eq!(
|
||||
vec!["2".to_string(), "1".to_string()],
|
||||
paginator.pagers[2]
|
||||
.pages
|
||||
.iter()
|
||||
.map(|p| p.get_title().as_ref().unwrap_or(&"".to_string()).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_create_paginator_for_index() {
|
||||
let (section, library) = create_library(true);
|
||||
let (section, library) = create_library(true, 3, false);
|
||||
let paginator = Paginator::from_section(§ion, &library);
|
||||
assert_eq!(paginator.pagers.len(), 2);
|
||||
|
||||
|
@ -313,7 +380,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_can_build_paginator_context() {
|
||||
let (section, library) = create_library(false);
|
||||
let (section, library) = create_library(false, 3, false);
|
||||
let paginator = Paginator::from_section(§ion, &library);
|
||||
assert_eq!(paginator.pagers.len(), 2);
|
||||
|
||||
|
@ -337,7 +404,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_can_create_paginator_for_taxonomy() {
|
||||
let (_, library) = create_library(false);
|
||||
let (_, library) = create_library(false, 3, false);
|
||||
let taxonomy_def = TaxonomyConfig {
|
||||
name: "tags".to_string(),
|
||||
paginate_by: Some(2),
|
||||
|
@ -367,7 +434,7 @@ mod tests {
|
|||
// https://github.com/getzola/zola/issues/866
|
||||
#[test]
|
||||
fn works_with_empty_paginate_path() {
|
||||
let (mut section, library) = create_library(false);
|
||||
let (mut section, library) = create_library(false, 3, false);
|
||||
section.meta.paginate_path = String::new();
|
||||
let paginator = Paginator::from_section(§ion, &library);
|
||||
assert_eq!(paginator.pagers.len(), 2);
|
||||
|
|
|
@ -19,7 +19,7 @@ fn can_parse_site() {
|
|||
let library = site.library.read().unwrap();
|
||||
|
||||
// Correct number of pages (sections do not count as pages, draft are ignored)
|
||||
assert_eq!(library.pages().len(), 23);
|
||||
assert_eq!(library.pages().len(), 32);
|
||||
let posts_path = path.join("content").join("posts");
|
||||
|
||||
// Make sure the page with a url doesn't have any sections
|
||||
|
@ -32,11 +32,11 @@ fn can_parse_site() {
|
|||
assert_eq!(asset_folder_post.file.components, vec!["posts".to_string()]);
|
||||
|
||||
// That we have the right number of sections
|
||||
assert_eq!(library.sections().len(), 11);
|
||||
assert_eq!(library.sections().len(), 12);
|
||||
|
||||
// And that the sections are correct
|
||||
let index_section = library.get_section(&path.join("content").join("_index.md")).unwrap();
|
||||
assert_eq!(index_section.subsections.len(), 4);
|
||||
assert_eq!(index_section.subsections.len(), 5);
|
||||
assert_eq!(index_section.pages.len(), 3);
|
||||
assert!(index_section.ancestors.is_empty());
|
||||
|
||||
|
@ -582,7 +582,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
|||
"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", "Num pagers: 8"));
|
||||
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"));
|
||||
|
@ -595,7 +595,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
|||
assert!(file_contains!(
|
||||
public,
|
||||
"tags/a/index.html",
|
||||
"Last: https://replace-this-with-your-url.com/tags/a/page/6/"
|
||||
"Last: https://replace-this-with-your-url.com/tags/a/page/8/"
|
||||
));
|
||||
assert_eq!(file_contains!(public, "tags/a/index.html", "has_prev"), false);
|
||||
|
||||
|
@ -603,7 +603,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
|||
assert!(file_contains!(
|
||||
public,
|
||||
"sitemap.xml",
|
||||
"<loc>https://replace-this-with-your-url.com/tags/a/page/6/</loc>"
|
||||
"<loc>https://replace-this-with-your-url.com/tags/a/page/8/</loc>"
|
||||
));
|
||||
|
||||
// current_path
|
||||
|
@ -721,7 +721,11 @@ fn can_build_site_with_html_minified() {
|
|||
|
||||
assert!(&public.exists());
|
||||
assert!(file_exists!(public, "index.html"));
|
||||
assert!(file_contains!(public, "index.html", "<!DOCTYPE html><html lang=en><head><meta charset=UTF-8>"));
|
||||
assert!(file_contains!(
|
||||
public,
|
||||
"index.html",
|
||||
"<!DOCTYPE html><html lang=en><head><meta charset=UTF-8>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -156,6 +156,7 @@ This will be sort all pages by their `weight` field, from lightest weight
|
|||
page gets `page.lighter` and `page.heavier` variables that contain the
|
||||
pages with lighter and heavier weights, respectively.
|
||||
|
||||
### Reversed sorting
|
||||
When iterating through pages, you may wish to use the Tera `reverse` filter,
|
||||
which reverses the order of the pages. For example, after using the `reverse` filter,
|
||||
pages sorted by weight will be sorted from lightest (at the top) to heaviest
|
||||
|
@ -164,6 +165,8 @@ to newest (at the bottom).
|
|||
|
||||
`reverse` has no effect on `page.later`/`page.earlier` or `page.heavier`/`page.lighter`.
|
||||
|
||||
If the section is paginated the `paginate_reversed=true` in the front matter of the relevant section should be set instead of using the filter.
|
||||
|
||||
## Sorting subsections
|
||||
Sorting sections is a bit less flexible: sections can only be sorted by `weight`,
|
||||
and do not have variables that point to the heavier/lighter sections.
|
||||
|
|
|
@ -132,7 +132,6 @@ include_content = true
|
|||
# become too big to load on the site. Defaults to not being set.
|
||||
# truncate_content_length = 100
|
||||
|
||||
|
||||
# Optional translation object. Keys should be language codes.
|
||||
# Optional translation object. The key if present should be a language code.
|
||||
# Example:
|
||||
|
|
4
test_site/content/reverse-paginated/1.md
Normal file
4
test_site/content/reverse-paginated/1.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 1"
|
||||
weight=1
|
||||
+++
|
4
test_site/content/reverse-paginated/2.md
Normal file
4
test_site/content/reverse-paginated/2.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 2"
|
||||
weight=2
|
||||
+++
|
4
test_site/content/reverse-paginated/3.md
Normal file
4
test_site/content/reverse-paginated/3.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 3"
|
||||
weight=3
|
||||
+++
|
4
test_site/content/reverse-paginated/4.md
Normal file
4
test_site/content/reverse-paginated/4.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 4"
|
||||
weight=4
|
||||
+++
|
4
test_site/content/reverse-paginated/5.md
Normal file
4
test_site/content/reverse-paginated/5.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 5"
|
||||
weight=5
|
||||
+++
|
4
test_site/content/reverse-paginated/6.md
Normal file
4
test_site/content/reverse-paginated/6.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 6"
|
||||
weight=6
|
||||
+++
|
4
test_site/content/reverse-paginated/7.md
Normal file
4
test_site/content/reverse-paginated/7.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 7"
|
||||
weight=7
|
||||
+++
|
4
test_site/content/reverse-paginated/8.md
Normal file
4
test_site/content/reverse-paginated/8.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 8"
|
||||
weight=8
|
||||
+++
|
4
test_site/content/reverse-paginated/9.md
Normal file
4
test_site/content/reverse-paginated/9.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title="Page number: 9"
|
||||
weight=9
|
||||
+++
|
6
test_site/content/reverse-paginated/_index.md
Normal file
6
test_site/content/reverse-paginated/_index.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
paginate_by = 2
|
||||
template = "section_paginated.html"
|
||||
sort_by = "weight"
|
||||
paginate_reversed = true
|
||||
+++
|
Loading…
Reference in a new issue