zola/components/library/src/pagination/mod.rs

339 lines
13 KiB
Rust
Raw Normal View History

2017-05-03 08:52:49 +00:00
use std::collections::HashMap;
2017-07-01 07:47:41 +00:00
use tera::{Tera, Context, to_value, Value};
2018-10-02 14:42:34 +00:00
use slotmap::{Key};
2017-05-03 08:52:49 +00:00
use errors::{Result, ResultExt};
2017-07-01 07:47:41 +00:00
use config::Config;
2017-08-23 10:17:24 +00:00
use utils::templates::render_template;
2018-10-02 14:42:34 +00:00
use content::{Section, SerializingSection};
use taxonomies::{TaxonomyItem, Taxonomy};
use library::Library;
2018-07-16 08:54:05 +00:00
#[derive(Clone, Debug, PartialEq)]
enum PaginationRoot<'a> {
Section(&'a Section),
Taxonomy(&'a Taxonomy),
}
2017-05-03 08:52:49 +00:00
/// A list of all the pages in the paginator with their index and links
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Pager<'a> {
/// The page number in the paginator (1-indexed)
pub index: usize,
2017-05-03 08:52:49 +00:00
/// Permalink to that page
permalink: String,
/// Path to that page
path: String,
/// All pages for the pager
2018-10-02 14:42:34 +00:00
pages: Vec<&'a Value>,
2017-05-03 08:52:49 +00:00
}
impl<'a> Pager<'a> {
2018-10-02 14:42:34 +00:00
fn new(index: usize, pages: Vec<&'a Value>, permalink: String, path: String) -> Pager<'a> {
2017-05-03 08:52:49 +00:00
Pager {
index,
permalink,
path,
pages,
2017-05-03 08:52:49 +00:00
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Paginator<'a> {
2018-10-02 14:42:34 +00:00
/// All pages in the section/taxonomy
all_pages: &'a [Key],
2017-05-03 08:52:49 +00:00
/// 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,
2018-07-16 08:54:05 +00:00
/// 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
pub permalink: String,
path: String,
pub paginate_path: String,
2018-10-02 14:42:34 +00:00
/// Whether this is the index section, we need it for the template name
2018-07-16 08:54:05 +00:00
is_index: bool,
2017-05-03 08:52:49 +00:00
}
impl<'a> Paginator<'a> {
2018-07-16 08:54:05 +00:00
/// Create a new paginator from a section
2018-10-02 14:42:34 +00:00
/// 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> {
2017-05-03 08:52:49 +00:00
let paginate_by = section.meta.paginate_by.unwrap();
2018-07-16 08:54:05 +00:00
let mut paginator = Paginator {
2018-10-02 14:42:34 +00:00
all_pages: &section.pages,
pagers: Vec::with_capacity(section.pages.len() / paginate_by),
2018-07-16 08:54:05 +00:00
paginate_by,
root: PaginationRoot::Section(section),
permalink: section.permalink.clone(),
path: section.path.clone(),
paginate_path: section.meta.paginate_path.clone(),
is_index: section.is_index(),
};
2018-10-02 14:42:34 +00:00
paginator.fill_pagers(library);
2018-07-16 08:54:05 +00:00
paginator
}
/// Create a new paginator from a taxonomy
2018-10-02 14:42:34 +00:00
/// It will always at least create one pager (the first) even if there are not enough pages to paginate
pub fn from_taxonomy(taxonomy: &'a Taxonomy, item: &'a TaxonomyItem, library: &'a Library) -> Paginator<'a> {
let paginate_by = taxonomy.kind.paginate_by.unwrap();
2018-07-16 08:54:05 +00:00
let mut paginator = Paginator {
all_pages: &item.pages,
2018-10-02 14:42:34 +00:00
pagers: Vec::with_capacity(item.pages.len() / paginate_by),
2018-07-16 08:54:05 +00:00
paginate_by,
root: PaginationRoot::Taxonomy(taxonomy),
permalink: item.permalink.clone(),
path: format!("{}/{}", taxonomy.kind.name, item.slug),
paginate_path: taxonomy.kind.paginate_path.clone().unwrap_or_else(|| "pages".to_string()),
is_index: false,
};
2018-10-02 14:42:34 +00:00
paginator.fill_pagers(library);
2018-07-16 08:54:05 +00:00
paginator
}
2018-10-02 14:42:34 +00:00
fn fill_pagers(&mut self, library: &'a Library) {
// the list of pagers
2017-05-03 08:52:49 +00:00
let mut pages = vec![];
2018-10-02 14:42:34 +00:00
// the pages in the current pagers
2017-05-03 08:52:49 +00:00
let mut current_page = vec![];
2018-03-14 17:22:24 +00:00
2018-10-02 14:42:34 +00:00
for key in self.all_pages {
current_page.push(library.get_cached_page_value_by_key(key));
2017-05-03 08:52:49 +00:00
2018-07-16 08:54:05 +00:00
if current_page.len() == self.paginate_by {
2017-05-03 08:52:49 +00:00
pages.push(current_page);
current_page = vec![];
}
}
2018-10-02 14:42:34 +00:00
2017-05-03 08:52:49 +00:00
if !current_page.is_empty() {
pages.push(current_page);
}
let mut pagers = vec![];
2018-10-02 14:42:34 +00:00
for (index, page) in pages.into_iter().enumerate() {
2017-05-03 08:52:49 +00:00
// First page has no pagination path
if index == 0 {
2018-10-02 14:42:34 +00:00
pagers.push(Pager::new(1, page, self.permalink.clone(), self.path.clone()));
2017-05-03 08:52:49 +00:00
continue;
}
2018-07-16 08:54:05 +00:00
let page_path = format!("{}/{}/", self.paginate_path, index + 1);
let permalink = format!("{}{}", self.permalink, page_path);
let pager_path = if self.is_index {
page_path
2018-09-30 19:15:09 +00:00
} else if self.path.ends_with('/') {
format!("{}{}", self.path, page_path)
2017-05-03 08:52:49 +00:00
} else {
2018-09-30 19:15:09 +00:00
format!("{}/{}", self.path, page_path)
2017-05-03 08:52:49 +00:00
};
2018-07-16 08:54:05 +00:00
2017-05-03 08:52:49 +00:00
pagers.push(Pager::new(
index + 1,
2018-10-02 14:42:34 +00:00
page,
2017-05-03 08:52:49 +00:00
permalink,
pager_path,
2017-05-03 08:52:49 +00:00
));
}
// We always have the index one at least
if pagers.is_empty() {
2018-07-16 08:54:05 +00:00
pagers.push(Pager::new(1, vec![], self.permalink.clone(), self.path.clone()));
}
2018-07-16 08:54:05 +00:00
self.pagers = pagers;
2017-05-03 08:52:49 +00:00
}
pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> {
let mut paginator = HashMap::new();
// the pager index is 1-indexed so we want a 0-indexed one for indexing there
let pager_index = current_pager.index - 1;
// Global variables
paginator.insert("paginate_by", to_value(self.paginate_by).unwrap());
2018-07-16 08:54:05 +00:00
paginator.insert("first", to_value(&self.permalink).unwrap());
2017-05-03 08:52:49 +00:00
let last_pager = &self.pagers[self.pagers.len() - 1];
paginator.insert("last", to_value(&last_pager.permalink).unwrap());
// Variables for this specific page
if pager_index > 0 {
let prev_pager = &self.pagers[pager_index - 1];
paginator.insert("previous", to_value(&prev_pager.permalink).unwrap());
} else {
2018-09-30 21:49:32 +00:00
paginator.insert("previous", Value::Null);
2017-05-03 08:52:49 +00:00
}
if pager_index < self.pagers.len() - 1 {
let next_pager = &self.pagers[pager_index + 1];
paginator.insert("next", to_value(&next_pager.permalink).unwrap());
} else {
2018-09-30 21:49:32 +00:00
paginator.insert("next", Value::Null);
2017-05-03 08:52:49 +00:00
}
2018-09-30 21:49:32 +00:00
paginator.insert("number_pagers", to_value(&self.pagers.len()).unwrap());
paginator.insert("base_url", to_value(&format!("{}{}/", self.permalink, self.paginate_path)).unwrap());
2017-05-03 08:52:49 +00:00
paginator.insert("pages", to_value(&current_pager.pages).unwrap());
paginator.insert("current_index", to_value(current_pager.index).unwrap());
paginator
}
pub fn render_pager(&self, pager: &Pager, config: &Config, tera: &Tera) -> Result<String> {
2017-05-03 08:52:49 +00:00
let mut context = Context::new();
2018-09-09 17:43:14 +00:00
context.insert("config", &config);
2018-07-16 08:54:05 +00:00
let template_name = match self.root {
PaginationRoot::Section(s) => {
2018-10-02 14:42:34 +00:00
context.insert("section", &SerializingSection::from_section_basic(s));
2018-07-16 08:54:05 +00:00
s.get_template_name()
2018-07-31 13:17:31 +00:00
}
2018-07-16 08:54:05 +00:00
PaginationRoot::Taxonomy(t) => {
2018-09-09 17:43:14 +00:00
context.insert("taxonomy", &t.kind);
2018-07-16 08:54:05 +00:00
format!("{}/single.html", t.kind.name)
2018-07-31 13:17:31 +00:00
}
2018-07-16 08:54:05 +00:00
};
2018-09-09 17:43:14 +00:00
context.insert("current_url", &pager.permalink);
context.insert("current_path", &pager.path);
context.insert("paginator", &self.build_paginator_context(pager));
2017-05-03 08:52:49 +00:00
2018-07-16 08:54:05 +00:00
render_template(&template_name, tera, &context, &config.theme)
.chain_err(|| format!("Failed to render pager {}", pager.index))
2017-05-03 08:52:49 +00:00
}
}
#[cfg(test)]
mod tests {
2017-07-01 07:47:41 +00:00
use tera::to_value;
2017-05-03 08:52:49 +00:00
use front_matter::SectionFrontMatter;
2017-05-14 05:14:58 +00:00
use content::{Page, Section};
2018-07-31 13:17:31 +00:00
use config::Taxonomy as TaxonomyConfig;
2018-07-16 08:54:05 +00:00
use taxonomies::{Taxonomy, TaxonomyItem};
2018-10-02 14:42:34 +00:00
use library::Library;
2017-05-03 08:52:49 +00:00
2017-07-01 07:47:41 +00:00
use super::Paginator;
2017-05-03 08:52:49 +00:00
fn create_section(is_index: bool) -> Section {
let mut f = SectionFrontMatter::default();
2017-05-03 08:52:49 +00:00
f.paginate_by = Some(2);
2018-03-14 17:22:24 +00:00
f.paginate_path = "page".to_string();
2017-05-03 08:52:49 +00:00
let mut s = Section::new("content/_index.md", f);
if !is_index {
s.path = "posts/".to_string();
s.permalink = "https://vincent.is/posts/".to_string();
s.file.components = vec!["posts".to_string()];
2017-05-03 08:52:49 +00:00
} else {
s.permalink = "https://vincent.is/".to_string();
2017-05-03 08:52:49 +00:00
}
s
}
2018-10-02 14:42:34 +00:00
fn create_library(is_index: bool) -> (Section, Library) {
let mut library = Library::new(3, 0);
library.insert_page(Page::default());
library.insert_page(Page::default());
library.insert_page(Page::default());
let mut section = create_section(is_index);
section.pages = library.pages().keys().collect();
library.insert_section(section.clone());
library.cache_all_pages();
library.cache_all_sections();
(section, library)
}
2017-05-03 08:52:49 +00:00
#[test]
fn test_can_create_paginator() {
2018-10-02 14:42:34 +00:00
let (section, library) = create_library(false);
let paginator = Paginator::from_section(&section, &library);
2017-05-03 08:52:49 +00:00
assert_eq!(paginator.pagers.len(), 2);
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/");
2017-05-03 08:52:49 +00:00
assert_eq!(paginator.pagers[1].index, 2);
assert_eq!(paginator.pagers[1].pages.len(), 1);
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2/");
assert_eq!(paginator.pagers[1].path, "posts/page/2/");
2017-05-03 08:52:49 +00:00
}
#[test]
fn test_can_create_paginator_for_index() {
2018-10-02 14:42:34 +00:00
let (section, library) = create_library(true);
let paginator = Paginator::from_section(&section, &library);
2017-05-03 08:52:49 +00:00
assert_eq!(paginator.pagers.len(), 2);
assert_eq!(paginator.pagers[0].index, 1);
assert_eq!(paginator.pagers[0].pages.len(), 2);
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/");
2017-05-03 08:52:49 +00:00
assert_eq!(paginator.pagers[0].path, "");
assert_eq!(paginator.pagers[1].index, 2);
assert_eq!(paginator.pagers[1].pages.len(), 1);
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2/");
assert_eq!(paginator.pagers[1].path, "page/2/");
2017-05-03 08:52:49 +00:00
}
#[test]
fn test_can_build_paginator_context() {
2018-10-02 14:42:34 +00:00
let (section, library) = create_library(false);
let paginator = Paginator::from_section(&section, &library);
2017-05-03 08:52:49 +00:00
assert_eq!(paginator.pagers.len(), 2);
let context = paginator.build_paginator_context(&paginator.pagers[0]);
assert_eq!(context["paginate_by"], to_value(2).unwrap());
assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
2017-05-03 08:52:49 +00:00
assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2/").unwrap());
2017-05-03 08:52:49 +00:00
assert_eq!(context["current_index"], to_value(1).unwrap());
let context = paginator.build_paginator_context(&paginator.pagers[1]);
assert_eq!(context["paginate_by"], to_value(2).unwrap());
assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
2017-05-03 08:52:49 +00:00
assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
2017-05-03 08:52:49 +00:00
assert_eq!(context["current_index"], to_value(2).unwrap());
}
2018-07-16 08:54:05 +00:00
#[test]
fn test_can_create_paginator_for_taxonomy() {
2018-10-02 14:42:34 +00:00
let (_, library) = create_library(false);
2018-07-16 08:54:05 +00:00
let taxonomy_def = TaxonomyConfig {
name: "tags".to_string(),
paginate_by: Some(2),
2018-07-16 08:54:05 +00:00
..TaxonomyConfig::default()
};
let taxonomy_item = TaxonomyItem {
name: "Something".to_string(),
slug: "something".to_string(),
permalink: "https://vincent.is/tags/something/".to_string(),
2018-10-02 14:42:34 +00:00
pages: library.pages().keys().collect(),
2018-07-16 08:54:05 +00:00
};
2018-07-31 13:17:31 +00:00
let taxonomy = Taxonomy { kind: taxonomy_def, items: vec![taxonomy_item.clone()] };
2018-10-02 14:42:34 +00:00
let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library);
2018-07-16 08:54:05 +00:00
assert_eq!(paginator.pagers.len(), 2);
assert_eq!(paginator.pagers[0].index, 1);
assert_eq!(paginator.pagers[0].pages.len(), 2);
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/tags/something/");
assert_eq!(paginator.pagers[0].path, "tags/something");
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/");
}
2017-05-03 08:52:49 +00:00
}