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.

338 lines
13KB

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