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.

358 lines
13KB

  1. use std::collections::HashMap;
  2. use slotmap::Key;
  3. use tera::{to_value, Context, Tera, Value};
  4. use config::Config;
  5. use errors::{Error, Result};
  6. use utils::templates::render_template;
  7. use content::{Section, SerializingPage, SerializingSection};
  8. use library::Library;
  9. use taxonomies::{Taxonomy, TaxonomyItem};
  10. #[derive(Clone, Debug, PartialEq)]
  11. enum PaginationRoot<'a> {
  12. Section(&'a Section),
  13. Taxonomy(&'a Taxonomy, &'a TaxonomyItem),
  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(
  29. index: usize,
  30. pages: Vec<SerializingPage<'a>>,
  31. permalink: String,
  32. path: String,
  33. ) -> Pager<'a> {
  34. Pager { index, permalink, path, pages }
  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. template: String,
  52. /// Whether this is the index section, we need it for the template name
  53. is_index: bool,
  54. }
  55. impl<'a> Paginator<'a> {
  56. /// Create a new paginator from a section
  57. /// It will always at least create one pager (the first) even if there are not enough pages to paginate
  58. pub fn from_section(section: &'a Section, library: &'a Library) -> Paginator<'a> {
  59. let paginate_by = section.meta.paginate_by.unwrap();
  60. let mut paginator = Paginator {
  61. all_pages: &section.pages,
  62. pagers: Vec::with_capacity(section.pages.len() / paginate_by),
  63. paginate_by,
  64. root: PaginationRoot::Section(section),
  65. permalink: section.permalink.clone(),
  66. path: section.path.clone(),
  67. paginate_path: section.meta.paginate_path.clone(),
  68. is_index: section.is_index(),
  69. template: section.get_template_name().to_string(),
  70. };
  71. paginator.fill_pagers(library);
  72. paginator
  73. }
  74. /// Create a new paginator from a taxonomy
  75. /// It will always at least create one pager (the first) even if there are not enough pages to paginate
  76. pub fn from_taxonomy(
  77. taxonomy: &'a Taxonomy,
  78. item: &'a TaxonomyItem,
  79. library: &'a Library,
  80. ) -> Paginator<'a> {
  81. let paginate_by = taxonomy.kind.paginate_by.unwrap();
  82. let mut paginator = Paginator {
  83. all_pages: &item.pages,
  84. pagers: Vec::with_capacity(item.pages.len() / paginate_by),
  85. paginate_by,
  86. root: PaginationRoot::Taxonomy(taxonomy, item),
  87. permalink: item.permalink.clone(),
  88. path: format!("{}/{}", taxonomy.kind.name, item.slug),
  89. paginate_path: taxonomy
  90. .kind
  91. .paginate_path
  92. .clone()
  93. .unwrap_or_else(|| "page".to_string()),
  94. is_index: false,
  95. template: format!("{}/single.html", taxonomy.kind.name),
  96. };
  97. paginator.fill_pagers(library);
  98. paginator
  99. }
  100. fn fill_pagers(&mut self, library: &'a Library) {
  101. // the list of pagers
  102. let mut pages = vec![];
  103. // the pages in the current pagers
  104. let mut current_page = vec![];
  105. for key in self.all_pages {
  106. let page = library.get_page_by_key(*key);
  107. if page.is_draft() {
  108. continue;
  109. }
  110. current_page.push(page.to_serialized_basic(library));
  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.into_iter().enumerate() {
  121. // First page has no pagination path
  122. if index == 0 {
  123. pagers.push(Pager::new(1, page, 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 if self.path.ends_with('/') {
  131. format!("{}{}", self.path, page_path)
  132. } else {
  133. format!("{}/{}", self.path, page_path)
  134. };
  135. pagers.push(Pager::new(index + 1, page, permalink, pager_path));
  136. }
  137. // We always have the index one at least
  138. if pagers.is_empty() {
  139. pagers.push(Pager::new(1, vec![], self.permalink.clone(), self.path.clone()));
  140. }
  141. self.pagers = pagers;
  142. }
  143. pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> {
  144. let mut paginator = HashMap::new();
  145. // the pager index is 1-indexed so we want a 0-indexed one for indexing there
  146. let pager_index = current_pager.index - 1;
  147. // Global variables
  148. paginator.insert("paginate_by", to_value(self.paginate_by).unwrap());
  149. paginator.insert("first", to_value(&self.permalink).unwrap());
  150. let last_pager = &self.pagers[self.pagers.len() - 1];
  151. paginator.insert("last", to_value(&last_pager.permalink).unwrap());
  152. // Variables for this specific page
  153. if pager_index > 0 {
  154. let prev_pager = &self.pagers[pager_index - 1];
  155. paginator.insert("previous", to_value(&prev_pager.permalink).unwrap());
  156. } else {
  157. paginator.insert("previous", Value::Null);
  158. }
  159. if pager_index < self.pagers.len() - 1 {
  160. let next_pager = &self.pagers[pager_index + 1];
  161. paginator.insert("next", to_value(&next_pager.permalink).unwrap());
  162. } else {
  163. paginator.insert("next", Value::Null);
  164. }
  165. paginator.insert("number_pagers", to_value(&self.pagers.len()).unwrap());
  166. paginator.insert(
  167. "base_url",
  168. to_value(&format!("{}{}/", self.permalink, self.paginate_path)).unwrap(),
  169. );
  170. paginator.insert("pages", to_value(&current_pager.pages).unwrap());
  171. paginator.insert("current_index", to_value(current_pager.index).unwrap());
  172. paginator
  173. }
  174. pub fn render_pager(
  175. &self,
  176. pager: &Pager,
  177. config: &Config,
  178. tera: &Tera,
  179. library: &Library,
  180. ) -> Result<String> {
  181. let mut context = Context::new();
  182. context.insert("config", &config);
  183. match self.root {
  184. PaginationRoot::Section(s) => {
  185. context
  186. .insert("section", &SerializingSection::from_section_basic(s, Some(library)));
  187. }
  188. PaginationRoot::Taxonomy(t, item) => {
  189. context.insert("taxonomy", &t.kind);
  190. context.insert("term", &item.serialize(library));
  191. }
  192. };
  193. context.insert("current_url", &pager.permalink);
  194. context.insert("current_path", &pager.path);
  195. context.insert("paginator", &self.build_paginator_context(pager));
  196. render_template(&self.template, tera, context, &config.theme)
  197. .map_err(|e| Error::chain(format!("Failed to render pager {}", pager.index), e))
  198. }
  199. }
  200. #[cfg(test)]
  201. mod tests {
  202. use std::path::PathBuf;
  203. use tera::to_value;
  204. use config::Taxonomy as TaxonomyConfig;
  205. use content::{Page, Section};
  206. use front_matter::SectionFrontMatter;
  207. use library::Library;
  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, &PathBuf::new());
  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. fn create_library(is_index: bool) -> (Section, Library) {
  225. let mut library = Library::new(3, 0, false);
  226. library.insert_page(Page::default());
  227. library.insert_page(Page::default());
  228. library.insert_page(Page::default());
  229. let mut draft = Page::default();
  230. draft.meta.draft = true;
  231. library.insert_page(draft);
  232. let mut section = create_section(is_index);
  233. section.pages = library.pages().keys().collect();
  234. library.insert_section(section.clone());
  235. (section, library)
  236. }
  237. #[test]
  238. fn test_can_create_paginator() {
  239. let (section, library) = create_library(false);
  240. let paginator = Paginator::from_section(&section, &library);
  241. assert_eq!(paginator.pagers.len(), 2);
  242. assert_eq!(paginator.pagers[0].index, 1);
  243. assert_eq!(paginator.pagers[0].pages.len(), 2);
  244. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts/");
  245. assert_eq!(paginator.pagers[0].path, "posts/");
  246. assert_eq!(paginator.pagers[1].index, 2);
  247. assert_eq!(paginator.pagers[1].pages.len(), 1);
  248. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2/");
  249. assert_eq!(paginator.pagers[1].path, "posts/page/2/");
  250. }
  251. #[test]
  252. fn test_can_create_paginator_for_index() {
  253. let (section, library) = create_library(true);
  254. let paginator = Paginator::from_section(&section, &library);
  255. assert_eq!(paginator.pagers.len(), 2);
  256. assert_eq!(paginator.pagers[0].index, 1);
  257. assert_eq!(paginator.pagers[0].pages.len(), 2);
  258. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/");
  259. assert_eq!(paginator.pagers[0].path, "");
  260. assert_eq!(paginator.pagers[1].index, 2);
  261. assert_eq!(paginator.pagers[1].pages.len(), 1);
  262. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2/");
  263. assert_eq!(paginator.pagers[1].path, "page/2/");
  264. }
  265. #[test]
  266. fn test_can_build_paginator_context() {
  267. let (section, library) = create_library(false);
  268. let paginator = Paginator::from_section(&section, &library);
  269. assert_eq!(paginator.pagers.len(), 2);
  270. let context = paginator.build_paginator_context(&paginator.pagers[0]);
  271. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  272. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  273. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  274. assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
  275. assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2/").unwrap());
  276. assert_eq!(context["current_index"], to_value(1).unwrap());
  277. let context = paginator.build_paginator_context(&paginator.pagers[1]);
  278. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  279. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  280. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  281. assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
  282. assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
  283. assert_eq!(context["current_index"], to_value(2).unwrap());
  284. }
  285. #[test]
  286. fn test_can_create_paginator_for_taxonomy() {
  287. let (_, library) = create_library(false);
  288. let taxonomy_def = TaxonomyConfig {
  289. name: "tags".to_string(),
  290. paginate_by: Some(2),
  291. ..TaxonomyConfig::default()
  292. };
  293. let taxonomy_item = TaxonomyItem {
  294. name: "Something".to_string(),
  295. slug: "something".to_string(),
  296. permalink: "https://vincent.is/tags/something/".to_string(),
  297. pages: library.pages().keys().collect(),
  298. };
  299. let taxonomy = Taxonomy { kind: taxonomy_def, items: vec![taxonomy_item.clone()] };
  300. let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library);
  301. assert_eq!(paginator.pagers.len(), 2);
  302. assert_eq!(paginator.pagers[0].index, 1);
  303. assert_eq!(paginator.pagers[0].pages.len(), 2);
  304. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/tags/something/");
  305. assert_eq!(paginator.pagers[0].path, "tags/something");
  306. assert_eq!(paginator.pagers[1].index, 2);
  307. assert_eq!(paginator.pagers[1].pages.len(), 1);
  308. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/page/2/");
  309. assert_eq!(paginator.pagers[1].path, "tags/something/page/2/");
  310. }
  311. }