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.

249 lines
8.9KB

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