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.

366 lines
13KB

  1. #[macro_use]
  2. extern crate serde_derive;
  3. extern crate tera;
  4. extern crate errors;
  5. extern crate config;
  6. extern crate content;
  7. extern crate utils;
  8. extern crate taxonomies;
  9. #[cfg(test)]
  10. extern crate front_matter;
  11. use std::collections::HashMap;
  12. use tera::{Tera, Context, to_value, Value};
  13. use errors::{Result, ResultExt};
  14. use config::Config;
  15. use content::{Page, Section};
  16. use utils::templates::render_template;
  17. use taxonomies::{Taxonomy, TaxonomyItem};
  18. #[derive(Clone, Debug, PartialEq)]
  19. enum PaginationRoot<'a> {
  20. Section(&'a Section),
  21. Taxonomy(&'a Taxonomy),
  22. }
  23. /// A list of all the pages in the paginator with their index and links
  24. #[derive(Clone, Debug, PartialEq, Serialize)]
  25. pub struct Pager<'a> {
  26. /// The page number in the paginator (1-indexed)
  27. index: usize,
  28. /// Permalink to that page
  29. permalink: String,
  30. /// Path to that page
  31. path: String,
  32. /// All pages for the pager
  33. pages: Vec<&'a Page>,
  34. }
  35. impl<'a> Pager<'a> {
  36. fn new(index: usize, pages: Vec<&'a Page>, permalink: String, path: String) -> Pager<'a> {
  37. Pager {
  38. index,
  39. permalink,
  40. path,
  41. pages,
  42. }
  43. }
  44. /// Returns a manually cloned Pager with the pages removed
  45. /// for use as template context
  46. fn clone_without_pages(&self) -> Pager<'a> {
  47. Pager {
  48. index: self.index,
  49. permalink: self.permalink.clone(),
  50. path: self.path.clone(),
  51. pages: vec![],
  52. }
  53. }
  54. }
  55. #[derive(Clone, Debug, PartialEq)]
  56. pub struct Paginator<'a> {
  57. /// All pages in the section
  58. all_pages: &'a [Page],
  59. /// Pages split in chunks of `paginate_by`
  60. pub pagers: Vec<Pager<'a>>,
  61. /// How many content pages on a paginated page at max
  62. paginate_by: usize,
  63. /// The thing we are creating the paginator for: section or taxonomy
  64. root: PaginationRoot<'a>,
  65. // Those below can be obtained from the root but it would make the code more complex than needed
  66. pub permalink: String,
  67. path: String,
  68. pub paginate_path: String,
  69. is_index: bool,
  70. }
  71. impl<'a> Paginator<'a> {
  72. /// Create a new paginator from a section
  73. /// It will always at least create one pager (the first) even if there are no pages to paginate
  74. pub fn from_section(all_pages: &'a [Page], section: &'a Section) -> Paginator<'a> {
  75. let paginate_by = section.meta.paginate_by.unwrap();
  76. let mut paginator = Paginator {
  77. all_pages,
  78. pagers: vec![],
  79. paginate_by,
  80. root: PaginationRoot::Section(section),
  81. permalink: section.permalink.clone(),
  82. path: section.path.clone(),
  83. paginate_path: section.meta.paginate_path.clone(),
  84. is_index: section.is_index(),
  85. };
  86. paginator.fill_pagers();
  87. paginator
  88. }
  89. /// Create a new paginator from a taxonomy
  90. /// It will always at least create one pager (the first) even if there are no pages to paginate
  91. pub fn from_taxonomy(taxonomy: &'a Taxonomy, item: &'a TaxonomyItem) -> Paginator<'a> {
  92. let paginate_by = taxonomy.kind.paginate_by.unwrap();
  93. let mut paginator = Paginator {
  94. all_pages: &item.pages,
  95. pagers: vec![],
  96. paginate_by,
  97. root: PaginationRoot::Taxonomy(taxonomy),
  98. permalink: item.permalink.clone(),
  99. path: format!("{}/{}", taxonomy.kind.name, item.slug),
  100. paginate_path: taxonomy.kind.paginate_path.clone().unwrap_or_else(|| "pages".to_string()),
  101. is_index: false,
  102. };
  103. paginator.fill_pagers();
  104. paginator
  105. }
  106. fn fill_pagers(&mut self) {
  107. let mut pages = vec![];
  108. let mut current_page = vec![];
  109. for page in self.all_pages {
  110. current_page.push(page);
  111. if current_page.len() == self.paginate_by {
  112. pages.push(current_page);
  113. current_page = vec![];
  114. }
  115. }
  116. if !current_page.is_empty() {
  117. pages.push(current_page);
  118. }
  119. let mut pagers = vec![];
  120. for (index, page) in pages.iter().enumerate() {
  121. // First page has no pagination path
  122. if index == 0 {
  123. pagers.push(Pager::new(1, page.clone(), self.permalink.clone(), self.path.clone()));
  124. continue;
  125. }
  126. let page_path = format!("{}/{}/", self.paginate_path, index + 1);
  127. let permalink = format!("{}{}", self.permalink, page_path);
  128. let pager_path = if self.is_index {
  129. page_path
  130. } else {
  131. if self.path.ends_with("/") {
  132. format!("{}{}", self.path, page_path)
  133. } else {
  134. format!("{}/{}", self.path, page_path)
  135. }
  136. };
  137. pagers.push(Pager::new(
  138. index + 1,
  139. page.clone(),
  140. permalink,
  141. pager_path,
  142. ));
  143. }
  144. // We always have the index one at least
  145. if pagers.is_empty() {
  146. pagers.push(Pager::new(1, vec![], self.permalink.clone(), self.path.clone()));
  147. }
  148. self.pagers = pagers;
  149. }
  150. pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> {
  151. let mut paginator = HashMap::new();
  152. // the pager index is 1-indexed so we want a 0-indexed one for indexing there
  153. let pager_index = current_pager.index - 1;
  154. // Global variables
  155. paginator.insert("paginate_by", to_value(self.paginate_by).unwrap());
  156. paginator.insert("first", to_value(&self.permalink).unwrap());
  157. let last_pager = &self.pagers[self.pagers.len() - 1];
  158. paginator.insert("last", to_value(&last_pager.permalink).unwrap());
  159. paginator.insert(
  160. "pagers",
  161. to_value(
  162. &self.pagers.iter().map(|p| p.clone_without_pages()).collect::<Vec<_>>()
  163. ).unwrap(),
  164. );
  165. // Variables for this specific page
  166. if pager_index > 0 {
  167. let prev_pager = &self.pagers[pager_index - 1];
  168. paginator.insert("previous", to_value(&prev_pager.permalink).unwrap());
  169. } else {
  170. paginator.insert("previous", to_value::<Option<()>>(None).unwrap());
  171. }
  172. if pager_index < self.pagers.len() - 1 {
  173. let next_pager = &self.pagers[pager_index + 1];
  174. paginator.insert("next", to_value(&next_pager.permalink).unwrap());
  175. } else {
  176. paginator.insert("next", to_value::<Option<()>>(None).unwrap());
  177. }
  178. paginator.insert("pages", to_value(&current_pager.pages).unwrap());
  179. paginator.insert("current_index", to_value(current_pager.index).unwrap());
  180. paginator
  181. }
  182. pub fn render_pager(&self, pager: &Pager, config: &Config, tera: &Tera) -> Result<String> {
  183. let mut context = Context::new();
  184. context.insert("config", &config);
  185. let template_name = match self.root {
  186. PaginationRoot::Section(s) => {
  187. context.insert("section", &s);
  188. s.get_template_name()
  189. }
  190. PaginationRoot::Taxonomy(t) => {
  191. context.insert("taxonomy", &t.kind);
  192. format!("{}/single.html", t.kind.name)
  193. }
  194. };
  195. context.insert("current_url", &pager.permalink);
  196. context.insert("current_path", &pager.path);
  197. context.insert("paginator", &self.build_paginator_context(pager));
  198. render_template(&template_name, tera, &context, &config.theme)
  199. .chain_err(|| format!("Failed to render pager {}", pager.index))
  200. }
  201. }
  202. #[cfg(test)]
  203. mod tests {
  204. use tera::to_value;
  205. use front_matter::SectionFrontMatter;
  206. use content::{Page, Section};
  207. use config::Taxonomy as TaxonomyConfig;
  208. use taxonomies::{Taxonomy, TaxonomyItem};
  209. use super::Paginator;
  210. fn create_section(is_index: bool) -> Section {
  211. let mut f = SectionFrontMatter::default();
  212. f.paginate_by = Some(2);
  213. f.paginate_path = "page".to_string();
  214. let mut s = Section::new("content/_index.md", f);
  215. if !is_index {
  216. s.path = "posts/".to_string();
  217. s.permalink = "https://vincent.is/posts/".to_string();
  218. s.file.components = vec!["posts".to_string()];
  219. } else {
  220. s.permalink = "https://vincent.is/".to_string();
  221. }
  222. s
  223. }
  224. #[test]
  225. fn test_can_create_paginator() {
  226. let pages = vec![
  227. Page::default(),
  228. Page::default(),
  229. Page::default(),
  230. ];
  231. let section = create_section(false);
  232. let paginator = Paginator::from_section(pages.as_slice(), &section);
  233. assert_eq!(paginator.pagers.len(), 2);
  234. assert_eq!(paginator.pagers[0].index, 1);
  235. assert_eq!(paginator.pagers[0].pages.len(), 2);
  236. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts/");
  237. assert_eq!(paginator.pagers[0].path, "posts/");
  238. assert_eq!(paginator.pagers[1].index, 2);
  239. assert_eq!(paginator.pagers[1].pages.len(), 1);
  240. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2/");
  241. assert_eq!(paginator.pagers[1].path, "posts/page/2/");
  242. }
  243. #[test]
  244. fn test_can_create_paginator_for_index() {
  245. let pages = vec![
  246. Page::default(),
  247. Page::default(),
  248. Page::default(),
  249. ];
  250. let section = create_section(true);
  251. let paginator = Paginator::from_section(pages.as_slice(), &section);
  252. assert_eq!(paginator.pagers.len(), 2);
  253. assert_eq!(paginator.pagers[0].index, 1);
  254. assert_eq!(paginator.pagers[0].pages.len(), 2);
  255. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/");
  256. assert_eq!(paginator.pagers[0].path, "");
  257. assert_eq!(paginator.pagers[1].index, 2);
  258. assert_eq!(paginator.pagers[1].pages.len(), 1);
  259. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2/");
  260. assert_eq!(paginator.pagers[1].path, "page/2/");
  261. }
  262. #[test]
  263. fn test_can_build_paginator_context() {
  264. let pages = vec![
  265. Page::default(),
  266. Page::default(),
  267. Page::default(),
  268. ];
  269. let section = create_section(false);
  270. let paginator = Paginator::from_section(pages.as_slice(), &section);
  271. assert_eq!(paginator.pagers.len(), 2);
  272. let context = paginator.build_paginator_context(&paginator.pagers[0]);
  273. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  274. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  275. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  276. assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
  277. assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2/").unwrap());
  278. assert_eq!(context["current_index"], to_value(1).unwrap());
  279. let context = paginator.build_paginator_context(&paginator.pagers[1]);
  280. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  281. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  282. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  283. assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
  284. assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
  285. assert_eq!(context["current_index"], to_value(2).unwrap());
  286. }
  287. #[test]
  288. fn test_can_create_paginator_for_taxonomy() {
  289. let pages = vec![
  290. Page::default(),
  291. Page::default(),
  292. Page::default(),
  293. ];
  294. let taxonomy_def = TaxonomyConfig {
  295. name: "tags".to_string(),
  296. paginate_by: Some(2),
  297. ..TaxonomyConfig::default()
  298. };
  299. let taxonomy_item = TaxonomyItem {
  300. name: "Something".to_string(),
  301. slug: "something".to_string(),
  302. permalink: "https://vincent.is/tags/something/".to_string(),
  303. pages,
  304. };
  305. let taxonomy = Taxonomy { kind: taxonomy_def, items: vec![taxonomy_item.clone()] };
  306. let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item);
  307. assert_eq!(paginator.pagers.len(), 2);
  308. assert_eq!(paginator.pagers[0].index, 1);
  309. assert_eq!(paginator.pagers[0].pages.len(), 2);
  310. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/tags/something/");
  311. assert_eq!(paginator.pagers[0].path, "tags/something");
  312. assert_eq!(paginator.pagers[1].index, 2);
  313. assert_eq!(paginator.pagers[1].pages.len(), 1);
  314. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/pages/2/");
  315. assert_eq!(paginator.pagers[1].path, "tags/something/pages/2/");
  316. }
  317. }