You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
8.9KB

  1. use std::collections::HashMap;
  2. use tera::{Context, to_value, Value};
  3. use errors::{Result, ResultExt};
  4. use page::Page;
  5. use section::Section;
  6. use site::Site;
  7. /// A list of all the pages in the paginator with their index and links
  8. #[derive(Clone, Debug, PartialEq, Serialize)]
  9. pub struct Pager<'a> {
  10. /// The page number in the paginator (1-indexed)
  11. index: usize,
  12. /// Permalink to that page
  13. permalink: String,
  14. /// Path to that page
  15. path: String,
  16. /// All pages for the pager
  17. pages: Vec<&'a Page>
  18. }
  19. impl<'a> Pager<'a> {
  20. fn new(index: usize, pages: Vec<&'a Page>, permalink: String, path: String) -> Pager<'a> {
  21. Pager {
  22. index: index,
  23. permalink: permalink,
  24. path: path,
  25. pages: pages,
  26. }
  27. }
  28. }
  29. #[derive(Clone, Debug, PartialEq)]
  30. pub struct Paginator<'a> {
  31. /// All pages in the section
  32. all_pages: &'a [Page],
  33. /// Pages split in chunks of `paginate_by`
  34. pub pagers: Vec<Pager<'a>>,
  35. /// How many content pages on a paginated page at max
  36. paginate_by: usize,
  37. /// The section struct we're building the paginator for
  38. section: &'a Section,
  39. }
  40. impl<'a> Paginator<'a> {
  41. pub fn new(all_pages: &'a [Page], section: &'a Section) -> Paginator<'a> {
  42. let paginate_by = section.meta.paginate_by.unwrap();
  43. let paginate_path = match section.meta.paginate_path {
  44. Some(ref p) => p,
  45. None => unreachable!(),
  46. };
  47. let mut pages = vec![];
  48. let mut current_page = vec![];
  49. for page in all_pages {
  50. current_page.push(page);
  51. if current_page.len() == paginate_by {
  52. pages.push(current_page);
  53. current_page = vec![];
  54. }
  55. }
  56. if !current_page.is_empty() {
  57. pages.push(current_page);
  58. }
  59. let mut pagers = vec![];
  60. for index in 0..pages.len() {
  61. // First page has no pagination path
  62. if index == 0 {
  63. pagers.push(Pager::new(1, pages[index].clone(), section.permalink.clone(), section.path.clone()));
  64. continue;
  65. }
  66. let page_path = format!("{}/{}", paginate_path, index + 1);
  67. let permalink = if section.permalink.ends_with('/') {
  68. format!("{}{}", section.permalink, page_path)
  69. } else {
  70. format!("{}/{}", section.permalink, page_path)
  71. };
  72. pagers.push(Pager::new(
  73. index + 1,
  74. pages[index].clone(),
  75. permalink,
  76. if section.is_index() { format!("{}", page_path) } else { format!("{}/{}", section.path, page_path) }
  77. ));
  78. }
  79. //println!("{:?}", pagers);
  80. Paginator {
  81. all_pages: all_pages,
  82. pagers: pagers,
  83. paginate_by: paginate_by,
  84. section: section,
  85. }
  86. }
  87. pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> {
  88. let mut paginator = HashMap::new();
  89. // the pager index is 1-indexed so we want a 0-indexed one for indexing there
  90. let pager_index = current_pager.index - 1;
  91. // Global variables
  92. paginator.insert("paginate_by", to_value(self.paginate_by).unwrap());
  93. paginator.insert("first", to_value(&self.section.permalink).unwrap());
  94. let last_pager = &self.pagers[self.pagers.len() - 1];
  95. paginator.insert("last", to_value(&last_pager.permalink).unwrap());
  96. paginator.insert("pagers", to_value(&self.pagers).unwrap());
  97. // Variables for this specific page
  98. if pager_index > 0 {
  99. let prev_pager = &self.pagers[pager_index - 1];
  100. paginator.insert("previous", to_value(&prev_pager.permalink).unwrap());
  101. } else {
  102. paginator.insert("previous", to_value::<Option<()>>(None).unwrap());
  103. }
  104. if pager_index < self.pagers.len() - 1 {
  105. let next_pager = &self.pagers[pager_index + 1];
  106. paginator.insert("next", to_value(&next_pager.permalink).unwrap());
  107. } else {
  108. paginator.insert("next", to_value::<Option<()>>(None).unwrap());
  109. }
  110. paginator.insert("pages", to_value(&current_pager.pages).unwrap());
  111. paginator.insert("current_index", to_value(current_pager.index).unwrap());
  112. paginator
  113. }
  114. pub fn render_pager(&self, pager: &Pager, site: &Site) -> Result<String> {
  115. let mut context = Context::new();
  116. context.add("config", &site.config);
  117. context.add("section", self.section);
  118. context.add("current_url", &pager.permalink);
  119. context.add("current_path", &pager.path);
  120. context.add("paginator", &self.build_paginator_context(pager));
  121. if self.section.is_index() {
  122. context.add("section", &site.sections);
  123. }
  124. site.tera.render(&self.section.get_template_name(), &context)
  125. .chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file_path.display()))
  126. }
  127. }
  128. #[cfg(test)]
  129. mod tests {
  130. use tera::{to_value};
  131. use front_matter::FrontMatter;
  132. use page::Page;
  133. use section::Section;
  134. use super::{Paginator};
  135. fn create_section(is_index: bool) -> Section {
  136. let mut f = FrontMatter::default();
  137. f.paginate_by = Some(2);
  138. f.paginate_path = Some("page".to_string());
  139. let mut s = Section::new("content/_index.md", f);
  140. if !is_index {
  141. s.path = "posts".to_string();
  142. s.permalink = "https://vincent.is/posts".to_string();
  143. s.components = vec!["posts".to_string()];
  144. } else {
  145. s.permalink = "https://vincent.is".to_string();
  146. }
  147. s
  148. }
  149. #[test]
  150. fn test_can_create_paginator() {
  151. let pages = vec![
  152. Page::new(FrontMatter::default()),
  153. Page::new(FrontMatter::default()),
  154. Page::new(FrontMatter::default()),
  155. ];
  156. let section = create_section(false);
  157. let paginator = Paginator::new(pages.as_slice(), &section);
  158. assert_eq!(paginator.pagers.len(), 2);
  159. assert_eq!(paginator.pagers[0].index, 1);
  160. assert_eq!(paginator.pagers[0].pages.len(), 2);
  161. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts");
  162. assert_eq!(paginator.pagers[0].path, "posts");
  163. assert_eq!(paginator.pagers[1].index, 2);
  164. assert_eq!(paginator.pagers[1].pages.len(), 1);
  165. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2");
  166. assert_eq!(paginator.pagers[1].path, "posts/page/2");
  167. }
  168. #[test]
  169. fn test_can_create_paginator_for_index() {
  170. let pages = vec![
  171. Page::new(FrontMatter::default()),
  172. Page::new(FrontMatter::default()),
  173. Page::new(FrontMatter::default()),
  174. ];
  175. let section = create_section(true);
  176. let paginator = Paginator::new(pages.as_slice(), &section);
  177. assert_eq!(paginator.pagers.len(), 2);
  178. assert_eq!(paginator.pagers[0].index, 1);
  179. assert_eq!(paginator.pagers[0].pages.len(), 2);
  180. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is");
  181. assert_eq!(paginator.pagers[0].path, "");
  182. assert_eq!(paginator.pagers[1].index, 2);
  183. assert_eq!(paginator.pagers[1].pages.len(), 1);
  184. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2");
  185. assert_eq!(paginator.pagers[1].path, "page/2");
  186. }
  187. #[test]
  188. fn test_can_build_paginator_context() {
  189. let pages = vec![
  190. Page::new(FrontMatter::default()),
  191. Page::new(FrontMatter::default()),
  192. Page::new(FrontMatter::default()),
  193. ];
  194. let section = create_section(false);
  195. let paginator = Paginator::new(pages.as_slice(), &section);
  196. assert_eq!(paginator.pagers.len(), 2);
  197. let context = paginator.build_paginator_context(&paginator.pagers[0]);
  198. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  199. assert_eq!(context["first"], to_value("https://vincent.is/posts").unwrap());
  200. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2").unwrap());
  201. assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
  202. assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2").unwrap());
  203. assert_eq!(context["current_index"], to_value(1).unwrap());
  204. let context = paginator.build_paginator_context(&paginator.pagers[1]);
  205. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  206. assert_eq!(context["first"], to_value("https://vincent.is/posts").unwrap());
  207. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2").unwrap());
  208. assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
  209. assert_eq!(context["previous"], to_value("https://vincent.is/posts").unwrap());
  210. assert_eq!(context["current_index"], to_value(2).unwrap());
  211. }
  212. }