From 06b37614010cdf7e58742a9d0d37f74fcc6b5b6a Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 8 May 2026 13:47:34 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20windowed=20pagination=20=E2=80=94=20show?= =?UTF-8?q?=201=E2=80=A6current=C2=B12=E2=80=A6last=20instead=20of=20all?= =?UTF-8?q?=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/adapters/template-askama/src/lib.rs | 59 +++++++++++++------ .../templates/activity_feed.html | 10 ++-- .../template-askama/templates/diary.html | 10 ++-- .../template-askama/templates/profile.html | 10 ++-- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/crates/adapters/template-askama/src/lib.rs b/crates/adapters/template-askama/src/lib.rs index e2dfa48..7239d00 100644 --- a/crates/adapters/template-askama/src/lib.rs +++ b/crates/adapters/template-askama/src/lib.rs @@ -9,6 +9,35 @@ use domain::models::{ collections::Paginated, }; +struct PageItem { + number: u32, + is_current: bool, + is_ellipsis: bool, +} + +fn build_page_items(total_pages: u32, current_page: u32) -> Vec { + if total_pages <= 1 { + return vec![]; + } + let mut set = std::collections::BTreeSet::new(); + set.insert(0u32); + set.insert(total_pages - 1); + let start = current_page.saturating_sub(2); + let end = (current_page + 2).min(total_pages - 1); + for p in start..=end { + set.insert(p); + } + let pages: Vec = set.into_iter().collect(); + let mut items = Vec::new(); + for (i, &p) in pages.iter().enumerate() { + if i > 0 && p > pages[i - 1] + 1 { + items.push(PageItem { number: 0, is_current: false, is_ellipsis: true }); + } + items.push(PageItem { number: p, is_current: p == current_page, is_ellipsis: false }); + } + items +} + #[derive(Template)] #[template(path = "diary.html")] struct DiaryTemplate<'a> { @@ -17,8 +46,7 @@ struct DiaryTemplate<'a> { limit: u32, has_more: bool, ctx: &'a HtmlPageContext, - total_pages: u32, - current_page: u32, + page_items: Vec, } #[derive(Template)] @@ -50,8 +78,7 @@ struct ActivityFeedTemplate<'a> { limit: u32, has_more: bool, ctx: &'a HtmlPageContext, - total_pages: u32, - current_page: u32, + page_items: Vec, } #[derive(Template)] @@ -82,8 +109,7 @@ struct ProfileTemplate<'a> { trends: Option<&'a UserTrends>, monthly_rating_rows: Vec>, heatmap: Vec, - total_pages: u32, - current_page: u32, + page_items: Vec, } struct HeatmapCell { @@ -148,9 +174,8 @@ impl HtmlRenderer for AskamaHtmlRenderer { fn render_diary_page(&self, data: &Paginated, ctx: HtmlPageContext) -> Result { let has_more = (data.offset + data.limit) < data.total_count as u32; let (total_pages, current_page) = if data.limit > 0 { - let total_pages = ((data.total_count + data.limit as u64 - 1) / data.limit as u64) as u32; - let current_page = data.offset / data.limit; - (total_pages, current_page) + let tp = ((data.total_count + data.limit as u64 - 1) / data.limit as u64) as u32; + (tp, data.offset / data.limit) } else { (0, 0) }; @@ -160,8 +185,7 @@ impl HtmlRenderer for AskamaHtmlRenderer { limit: data.limit, has_more, ctx: &ctx, - total_pages, - current_page, + page_items: build_page_items(total_pages, current_page), } .render() .map_err(|e| e.to_string()) @@ -195,9 +219,10 @@ impl HtmlRenderer for AskamaHtmlRenderer { } fn render_activity_feed_page(&self, data: ActivityFeedPageData) -> Result { - let total_count = data.entries.total_count; let limit = data.limit; - let total_pages = ((total_count + limit as u64 - 1) / limit as u64) as u32; + let total_pages = if limit > 0 { + ((data.entries.total_count + limit as u64 - 1) / limit as u64) as u32 + } else { 0 }; let current_page = if limit > 0 { data.current_offset / limit } else { 0 }; ActivityFeedTemplate { entries: &data.entries.items, @@ -205,8 +230,7 @@ impl HtmlRenderer for AskamaHtmlRenderer { limit, has_more: data.has_more, ctx: &data.ctx, - total_pages, - current_page, + page_items: build_page_items(total_pages, current_page), } .render() .map_err(|e| e.to_string()) @@ -234,7 +258,7 @@ impl HtmlRenderer for AskamaHtmlRenderer { }).collect()) .unwrap_or_default(); let total_pages = data.entries.as_ref() - .map(|e| ((e.total_count + e.limit as u64 - 1) / e.limit as u64) as u32) + .map(|e| if e.limit > 0 { ((e.total_count + e.limit as u64 - 1) / e.limit as u64) as u32 } else { 0 }) .unwrap_or(0); let current_page = if data.limit > 0 { data.current_offset / data.limit } else { 0 }; ProfileTemplate { @@ -251,8 +275,7 @@ impl HtmlRenderer for AskamaHtmlRenderer { trends: data.trends.as_ref(), monthly_rating_rows, heatmap, - total_pages, - current_page, + page_items: build_page_items(total_pages, current_page), } .render() .map_err(|e| e.to_string()) diff --git a/crates/adapters/template-askama/templates/activity_feed.html b/crates/adapters/template-askama/templates/activity_feed.html index 4673300..6eaf84a 100644 --- a/crates/adapters/template-askama/templates/activity_feed.html +++ b/crates/adapters/template-askama/templates/activity_feed.html @@ -44,11 +44,13 @@ {% if current_offset >= limit %} ← Prev {% endif %} - {% for p in (0..total_pages) %} - {% if p == current_page %} - {{ p + 1 }} + {% for item in page_items %} + {% if item.is_ellipsis %} + + {% elif item.is_current %} + {{ item.number + 1 }} {% else %} - {{ p + 1 }} + {{ item.number + 1 }} {% endif %} {% endfor %} {% if has_more %} diff --git a/crates/adapters/template-askama/templates/diary.html b/crates/adapters/template-askama/templates/diary.html index b25dd8b..16d184e 100644 --- a/crates/adapters/template-askama/templates/diary.html +++ b/crates/adapters/template-askama/templates/diary.html @@ -44,11 +44,13 @@ {% if current_offset > 0 %} ← Prev {% endif %} - {% for p in (0..total_pages) %} - {% if p == current_page %} - {{ p + 1 }} + {% for item in page_items %} + {% if item.is_ellipsis %} + + {% elif item.is_current %} + {{ item.number + 1 }} {% else %} - {{ p + 1 }} + {{ item.number + 1 }} {% endif %} {% endfor %} {% if has_more %} diff --git a/crates/adapters/template-askama/templates/profile.html b/crates/adapters/template-askama/templates/profile.html index 1cb39ea..84f1521 100644 --- a/crates/adapters/template-askama/templates/profile.html +++ b/crates/adapters/template-askama/templates/profile.html @@ -155,11 +155,13 @@ {% if current_offset >= limit %} ← Prev {% endif %} - {% for p in (0..total_pages) %} - {% if p == current_page %} - {{ p + 1 }} + {% for item in page_items %} + {% if item.is_ellipsis %} + + {% elif item.is_current %} + {{ item.number + 1 }} {% else %} - {{ p + 1 }} + {{ item.number + 1 }} {% endif %} {% endfor %} {% if has_more %}