Skip to content

Commit f664caf

Browse files
committed
Support pagination.
1 parent 0638349 commit f664caf

File tree

4 files changed

+130
-33
lines changed

4 files changed

+130
-33
lines changed

src/main.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,13 @@ async fn handle_websocket(
292292
Ok(())
293293
}
294294

295-
fn get_default_index(slug: &str) -> Resource {
295+
fn get_default_index(slug: &str, page_number: Option<usize>) -> Resource {
296296
Resource {
297297
kind: ResourceKind::Page,
298298
slug: slug.to_string(),
299299
date: Utc::now().naive_utc(),
300300
event_id: None,
301+
page_number,
301302
}
302303
}
303304

@@ -436,15 +437,37 @@ async fn handle_request(request: Request<State>) -> tide::Result<Response> {
436437
if let Some(r) = resources.get(&resource_path) {
437438
page = Some(Page::from_resource(r, &site)?);
438439
} else {
439-
let mut slug = request.url().path_segments().unwrap().last().unwrap();
440+
let last_two: Vec<&str> = request
441+
.url()
442+
.path_segments()
443+
.unwrap()
444+
.rev()
445+
.take(2)
446+
.collect();
447+
let (last, second_last) = match &last_two[..] {
448+
[last, second_last] => (Some(*last), Some(*second_last)),
449+
[last] => (Some(*last), None),
450+
[] => (None, None),
451+
_ => unreachable!(),
452+
};
453+
let page_number = if let Some(last) = last {
454+
last.parse::<usize>().ok()
455+
} else {
456+
None
457+
};
458+
let mut slug = if page_number.is_some() {
459+
second_last.unwrap_or(&"")
460+
} else {
461+
last.unwrap_or(&"")
462+
};
440463
if slug == "" {
441464
slug = "index";
442465
}
443-
let default_index = get_default_index(&slug);
466+
let default_index = get_default_index(&slug, page_number);
444467
let r = resources
445468
.get(&format!("/{}", &slug))
446469
.unwrap_or(&default_index);
447-
if resource_path == "/" {
470+
if slug == "index" {
448471
match &site.config.homepage_filter {
449472
Some(HomepageFilter::Posts) | None => {
450473
posts_section = Some(Section::from_resource(r, &site)?);
@@ -460,13 +483,13 @@ async fn handle_request(request: Request<State>) -> tide::Result<Response> {
460483
}
461484
}
462485
}
463-
if resource_path == "/posts" {
486+
if slug == "posts" {
464487
posts_section = Some(Section::from_resource(r, &site)?);
465-
} else if resource_path == "/notes" {
488+
} else if slug == "notes" {
466489
notes_section = Some(Section::from_resource(r, &site)?);
467-
} else if resource_path == "/pictures" {
490+
} else if slug == "pictures" {
468491
pictures_section = Some(Section::from_resource(r, &site)?);
469-
} else if resource_path == "/listings" {
492+
} else if slug == "listings" {
470493
listings_section = Some(Section::from_resource(r, &site)?);
471494
}
472495
}
@@ -1341,7 +1364,10 @@ fn validate_themes(
13411364
}
13421365
match render_and_build_response(
13431366
&empty_site,
1344-
Section::<PostSectionFilter>::from_resource(&get_default_index("index"), &empty_site)?,
1367+
Section::<PostSectionFilter>::from_resource(
1368+
&get_default_index("index", None),
1369+
&empty_site,
1370+
)?,
13451371
) {
13461372
Err(e) => {
13471373
let mut error_str = format!("{}", e);

src/resource.rs

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use url::Url;
77

88
use crate::site::{ServusMetadata, Site};
99

10-
#[derive(Clone, Copy, PartialEq, Serialize)]
10+
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
1111
pub enum ResourceKind {
1212
Post,
1313
Page,
@@ -99,7 +99,9 @@ impl Renderable for Page {
9999

100100
Ok(Self {
101101
title,
102-
permalink: site.config.make_permalink(&site.domain, &resource_url),
102+
permalink: site
103+
.config
104+
.make_permalink(&site.domain, &resource_url, None),
103105
url: resource_url.clone(),
104106
slug: resource.slug.to_owned(),
105107
path: Some(resource_url),
@@ -187,6 +189,7 @@ pub struct Section<T> {
187189
pages: Vec<Page>,
188190
content: String,
189191
description: Option<String>,
192+
paginator: Paginator,
190193
_phantom: PhantomData<T>,
191194
}
192195

@@ -198,27 +201,77 @@ where
198201
let Some(resource_url) = resource.get_resource_url() else {
199202
bail!("Cannot render a resource without URL");
200203
};
204+
205+
let current_index = resource.page_number.unwrap_or(1);
206+
let paginate_by = site
207+
.config
208+
.sections
209+
.iter()
210+
.find(|s| s.name == resource.slug)
211+
.map_or(0, |s| s.paginate_by.unwrap_or(0));
212+
201213
let Ok(resources) = site.resources.read() else {
202214
bail!("Cannot access resources");
203215
};
204-
let mut resources_list = resources.values().collect::<Vec<&Resource>>();
205-
resources_list.sort_by(|a, b| b.date.cmp(&a.date));
206-
let pages_list = resources_list
216+
let mut resources = resources.values().collect::<Vec<&Resource>>();
217+
resources.sort_by(|a, b| b.date.cmp(&a.date));
218+
219+
let mut all_pages = Vec::new();
220+
let mut current_pager_pages = Vec::new();
221+
let pager_start = (current_index - 1) * paginate_by;
222+
let pager_end = pager_start + paginate_by;
223+
for (i, page) in resources
207224
.into_iter()
208225
.filter(|r| T::filter(r.kind))
209226
.map(|r| Page::from_resource(r, site))
210227
.filter_map(Result::ok)
211-
.collect::<Vec<Page>>();
228+
.enumerate()
229+
{
230+
if i >= pager_start && i < pager_end || paginate_by == 0 {
231+
current_pager_pages.push(page.clone());
232+
}
233+
all_pages.push(page);
234+
}
235+
236+
let number_pagers = match paginate_by {
237+
0 => 0,
238+
_ => all_pages.len().div_ceil(paginate_by),
239+
};
212240

213241
Ok(Self {
214242
title: None,
215-
permalink: site.config.make_permalink(&site.domain, &resource_url),
243+
permalink: site
244+
.config
245+
.make_permalink(&site.domain, &resource_url, Some(current_index)),
216246
url: resource_url,
217247
slug: resource.slug.to_owned(),
218248
path: None, // TODO
219249
description: None, // TODO
220250
content: String::new(),
221-
pages: pages_list,
251+
pages: all_pages,
252+
paginator: Paginator {
253+
current_index,
254+
number_pagers,
255+
previous: if current_index > 1 {
256+
Some(site.config.make_permalink(
257+
&site.domain,
258+
&resource.slug,
259+
Some(current_index - 1),
260+
))
261+
} else {
262+
None
263+
},
264+
next: if current_index < number_pagers {
265+
Some(site.config.make_permalink(
266+
&site.domain,
267+
&resource.slug,
268+
Some(current_index + 1),
269+
))
270+
} else {
271+
None
272+
},
273+
pages: current_pager_pages,
274+
},
222275
_phantom: PhantomData,
223276
})
224277
}
@@ -238,19 +291,8 @@ where
238291

239292
extra_context.insert("config", &site.config);
240293

241-
// NB: some themes expect to iterate over section.pages, others look for paginator.pages.
242-
// We are currently passing both in all cases, so all themes will find the pages.
243294
extra_context.insert("section", &self);
244-
245-
// TODO: paginator.pages should be paginated, but it is not.
246-
extra_context.insert(
247-
"paginator",
248-
&Paginator {
249-
current_index: 1,
250-
number_pagers: 1,
251-
pages: self.pages.clone(),
252-
},
253-
);
295+
extra_context.insert("paginator", &self.paginator);
254296

255297
// https://www.getzola.org/documentation/templates/pages-sections/
256298
let template = match self.slug.as_str() {
@@ -273,15 +315,18 @@ where
273315
struct Paginator {
274316
current_index: usize,
275317
number_pagers: usize,
318+
previous: Option<String>,
319+
next: Option<String>,
276320
pages: Vec<Page>,
277321
}
278322

279-
#[derive(Clone, Serialize)]
323+
#[derive(Clone, Debug, Serialize)]
280324
pub struct Resource {
281325
pub kind: ResourceKind,
282326
pub slug: String,
283327
pub date: NaiveDateTime, // this is nice to have here for sorting
284328
pub event_id: Option<String>,
329+
pub page_number: Option<usize>,
285330
}
286331

287332
impl Resource {

src/site.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ pub enum HomepageFilter {
6767
Listings,
6868
}
6969

70+
#[derive(Clone, Debug, Deserialize, Serialize)]
71+
pub struct SectionConfig {
72+
pub name: String,
73+
pub paginate_by: Option<usize>,
74+
}
75+
7076
#[derive(Clone, Debug, Deserialize, Serialize)]
7177
pub struct SiteConfig {
7278
pub base_url: String,
@@ -78,6 +84,11 @@ pub struct SiteConfig {
7884
pub description: Option<String>,
7985
pub homepage_filter: Option<HomepageFilter>,
8086

87+
// a way to store section config (such as per-section paginate_by)
88+
// which in Zola would normally go to the section's front matter
89+
#[serde(default)]
90+
pub sections: Vec<SectionConfig>,
91+
8192
// required by some themes
8293
pub author: Option<String>,
8394
#[serde(default = "default_feed_filename")]
@@ -104,6 +115,7 @@ impl SiteConfig {
104115
title: None,
105116
description: None,
106117
homepage_filter: None,
118+
sections: vec![],
107119
author: None,
108120
feed_filename: default_feed_filename(),
109121
feed_filenames: default_feed_filenames(),
@@ -142,7 +154,12 @@ impl SiteConfig {
142154
// https://github.com/getzola/zola/blob/master/components/config/src/config/mod.rs
143155

144156
/// Makes a url, taking into account that the base url might have a trailing slash
145-
pub fn make_permalink(&self, site_domain: &str, path: &str) -> String {
157+
pub fn make_permalink(
158+
&self,
159+
site_domain: &str,
160+
path: &str,
161+
page_number: Option<usize>,
162+
) -> String {
146163
let trailing_bit = if path.ends_with('/')
147164
|| path.ends_with("atom.xml")
148165
|| path.ends_with(".css")
@@ -154,7 +171,7 @@ impl SiteConfig {
154171
};
155172

156173
// Index section with a base url that has a trailing slash
157-
let permalink = if self.base_url.ends_with('/') && path == "/" {
174+
let mut permalink = if self.base_url.ends_with('/') && path == "/" {
158175
self.base_url.to_string()
159176
} else if path == "/" {
160177
// index section with a base url that doesn't have a trailing slash
@@ -167,6 +184,10 @@ impl SiteConfig {
167184
format!("{}/{}{}", self.base_url, path, trailing_bit)
168185
};
169186

187+
if let Some(page_number) = page_number {
188+
permalink = format!("{}{}", permalink, page_number);
189+
}
190+
170191
if self.is_local_server() {
171192
// rewrite links when running locally
172193
// to allow the server know what site they are referring to
@@ -330,6 +351,7 @@ impl Site {
330351
}
331352
.to_string(),
332353
event_id: Some(event.id.clone()),
354+
page_number: None,
333355
};
334356

335357
if let Some(url) = resource.get_resource_url() {
@@ -413,6 +435,7 @@ impl Site {
413435
}
414436
.to_string(),
415437
event_id: Some(event.id.to_owned()),
438+
page_number: None,
416439
};
417440

418441
if let Some(url) = resource.get_resource_url() {

src/template.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ impl TeraFn for GetUrl {
8080

8181
let path = segments.join("/");
8282

83-
let mut permalink = self.site.config.make_permalink(&self.site.domain, &path);
83+
let mut permalink = self
84+
.site
85+
.config
86+
.make_permalink(&self.site.domain, &path, None);
8487
if !trailing_slash && permalink.ends_with('/') {
8588
permalink.pop(); // Removes the slash
8689
}

0 commit comments

Comments
 (0)