|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- use std::collections::HashMap;
- use tera::{Context, to_value, Value};
-
- use errors::{Result, ResultExt};
- use content::{Page, Section};
- use site::Site;
-
-
- /// 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)
- index: usize,
- /// Permalink to that page
- permalink: String,
- /// Path to that page
- path: String,
- /// All pages for the pager
- pages: Vec<&'a Page>
- }
-
- impl<'a> Pager<'a> {
- fn new(index: usize, pages: Vec<&'a Page>, permalink: String, path: String) -> Pager<'a> {
- Pager {
- index,
- permalink,
- path,
- pages,
- }
- }
- }
-
- #[derive(Clone, Debug, PartialEq)]
- pub struct Paginator<'a> {
- /// All pages in the section
- all_pages: &'a [Page],
- /// 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,
- /// The section struct we're building the paginator for
- section: &'a Section,
- }
-
- impl<'a> Paginator<'a> {
- /// Create a new paginator
- /// It will always at least create one pager (the first) even if there are no pages to paginate
- pub fn new(all_pages: &'a [Page], section: &'a Section) -> Paginator<'a> {
- let paginate_by = section.meta.paginate_by.unwrap();
- let paginate_path = match section.meta.paginate_path {
- Some(ref p) => p,
- None => unreachable!(),
- };
-
- let mut pages = vec![];
- let mut current_page = vec![];
- for page in all_pages {
- current_page.push(page);
-
- if current_page.len() == paginate_by {
- pages.push(current_page);
- current_page = vec![];
- }
- }
- if !current_page.is_empty() {
- pages.push(current_page);
- }
-
- let mut pagers = vec![];
- for (index, page) in pages.iter().enumerate() {
- // First page has no pagination path
- if index == 0 {
- pagers.push(Pager::new(1, page.clone(), section.permalink.clone(), section.path.clone()));
- continue;
- }
-
- let page_path = format!("{}/{}", paginate_path, index + 1);
- let permalink = if section.permalink.ends_with('/') {
- format!("{}{}", section.permalink, page_path)
- } else {
- format!("{}/{}", section.permalink, page_path)
- };
- pagers.push(Pager::new(
- index + 1,
- page.clone(),
- permalink,
- if section.is_index() { page_path } else { format!("{}/{}", section.path, page_path) }
- ));
- }
-
- // We always have the index one at least
- if pagers.is_empty() {
- pagers.push(Pager::new(1, vec![], section.permalink.clone(), section.path.clone()));
- }
-
- Paginator {
- all_pages: all_pages,
- pagers: pagers,
- paginate_by: paginate_by,
- section: section,
- }
- }
-
- 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());
- paginator.insert("first", to_value(&self.section.permalink).unwrap());
- let last_pager = &self.pagers[self.pagers.len() - 1];
- paginator.insert("last", to_value(&last_pager.permalink).unwrap());
- paginator.insert("pagers", to_value(&self.pagers).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 {
- paginator.insert("previous", to_value::<Option<()>>(None).unwrap());
- }
-
- 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 {
- paginator.insert("next", to_value::<Option<()>>(None).unwrap());
- }
- paginator.insert("pages", to_value(¤t_pager.pages).unwrap());
- paginator.insert("current_index", to_value(current_pager.index).unwrap());
-
- paginator
- }
-
- pub fn render_pager(&self, pager: &Pager, site: &Site) -> Result<String> {
- let mut context = Context::new();
- context.add("config", &site.config);
- context.add("section", self.section);
- context.add("current_url", &pager.permalink);
- context.add("current_path", &pager.path);
- context.add("paginator", &self.build_paginator_context(pager));
- if self.section.is_index() {
- context.add("section", &site.sections);
- }
-
- site.tera.render(&self.section.get_template_name(), &context)
- .chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display()))
- }
- }
-
- #[cfg(test)]
- mod tests {
- use tera::{to_value};
-
- use front_matter::SectionFrontMatter;
- use content::{Page, Section};
-
- use super::{Paginator};
-
- fn create_section(is_index: bool) -> Section {
- let mut f = SectionFrontMatter::default();
- f.paginate_by = Some(2);
- f.paginate_path = Some("page".to_string());
- 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()];
- } else {
- s.permalink = "https://vincent.is".to_string();
- }
- s
- }
-
- #[test]
- fn test_can_create_paginator() {
- let pages = vec![
- Page::default(),
- Page::default(),
- Page::default(),
- ];
- let section = create_section(false);
- let paginator = Paginator::new(pages.as_slice(), §ion);
- 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");
-
- 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");
- }
-
- #[test]
- fn test_can_create_paginator_for_index() {
- let pages = vec![
- Page::default(),
- Page::default(),
- Page::default(),
- ];
- let section = create_section(true);
- let paginator = Paginator::new(pages.as_slice(), §ion);
- 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");
- 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");
- }
-
- #[test]
- fn test_can_build_paginator_context() {
- let pages = vec![
- Page::default(),
- Page::default(),
- Page::default(),
- ];
- let section = create_section(false);
- let paginator = Paginator::new(pages.as_slice(), §ion);
- 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());
- assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
- assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2").unwrap());
- 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());
- assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
- assert_eq!(context["previous"], to_value("https://vincent.is/posts").unwrap());
- assert_eq!(context["current_index"], to_value(2).unwrap());
- }
- }
|