Skip to content

Commit c6bf655

Browse files
authored
Merge pull request #3 from pkgdiff-dev/fix/blog-pagination-window
fix(blog): show a window of page links instead of every page
2 parents d2b3527 + db6d218 commit c6bf655

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

‎src/frontend/src/app/features/blog/blog-list/blog-list.html‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,32 @@ <h2 class="text-xl font-bold text-slate-900 dark:text-white mb-2">No articles ye
8484

8585
@if (totalPages() > 1) {
8686
<nav class="mt-16 flex flex-wrap justify-center gap-2" aria-label="Blog pages">
87-
@for (page of pages(); track page) {
88-
<a [routerLink]="['/blog']" [queryParams]="page > 1 ? { page: page } : {}"
89-
[attr.aria-current]="currentPage() === page ? 'page' : null"
90-
[class]="currentPage() === page
87+
@if (currentPage() > 1) {
88+
<a [routerLink]="['/blog']" [queryParams]="currentPage() > 2 ? { page: currentPage() - 1 } : {}"
89+
rel="prev" aria-label="Previous page"
90+
class="bg-white dark:bg-transparent text-slate-700 dark:text-slate-300 border-gray-200 dark:border-[#1f1f1f] hover:bg-slate-50 dark:hover:bg-slate-900 border px-2 md:px-3 py-2 rounded transition-colors flex items-center">
91+
<lucide-icon [img]="PrevIcon" class="w-4 h-4"></lucide-icon>
92+
</a>
93+
}
94+
@for (link of pages(); track link.kind === 'page' ? link.page : link.key) {
95+
@if (link.kind === 'page') {
96+
<a [routerLink]="['/blog']" [queryParams]="link.page > 1 ? { page: link.page } : {}"
97+
[attr.aria-current]="currentPage() === link.page ? 'page' : null"
98+
[class]="currentPage() === link.page
9199
? 'bg-slate-900 text-white dark:bg-white dark:text-slate-900 border-slate-900 dark:border-white'
92100
: 'bg-white dark:bg-transparent text-slate-700 dark:text-slate-300 border-gray-200 dark:border-[#1f1f1f] hover:bg-slate-50 dark:hover:bg-slate-900'"
93101
class="font-mono text-xs font-bold border px-3 md:px-4 py-2 rounded transition-colors uppercase tracking-widest">
94-
{{ page }}
102+
{{ link.page }}
103+
</a>
104+
} @else {
105+
<span class="font-mono text-xs font-bold px-1 py-2 text-slate-400 dark:text-slate-600" aria-hidden="true">…</span>
106+
}
107+
}
108+
@if (currentPage() < totalPages()) {
109+
<a [routerLink]="['/blog']" [queryParams]="{ page: currentPage() + 1 }"
110+
rel="next" aria-label="Next page"
111+
class="bg-white dark:bg-transparent text-slate-700 dark:text-slate-300 border-gray-200 dark:border-[#1f1f1f] hover:bg-slate-50 dark:hover:bg-slate-900 border px-2 md:px-3 py-2 rounded transition-colors flex items-center">
112+
<lucide-icon [img]="NextIcon" class="w-4 h-4"></lucide-icon>
95113
</a>
96114
}
97115
</nav>

‎src/frontend/src/app/features/blog/blog-list/blog-list.ts‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
33
import { DatePipe } from '@angular/common';
44
import { ActivatedRoute, RouterLink } from '@angular/router';
55
import { Subject, catchError, distinctUntilChanged, map, of, startWith, switchMap, tap } from 'rxjs';
6-
import { LucideAngularModule, BookOpen, AlertCircle } from 'lucide-angular';
6+
import { LucideAngularModule, BookOpen, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-angular';
77
import { ApiService } from '../../../core/services/api';
88
import { SeoService } from '../../../core/services/seo.service';
99
import { BlogPostSnippet } from '../../../core/models/blog.models';
@@ -13,7 +13,24 @@ import { stripMarkdown } from '../../../core/utils/markdown.utils';
1313
type PageState = 'loading' | 'loaded' | 'error';
1414
type Loaded = { page: PaginatedResult<BlogPostSnippet> } | { failure: unknown };
1515

16+
type PageLink = { kind: 'page'; page: number } | { kind: 'gap'; key: string };
17+
1618
const PAGE_SIZE = 10;
19+
const SIBLINGS = 1;
20+
21+
function pageWindow(current: number, total: number): PageLink[] {
22+
let start = Math.max(2, current - SIBLINGS);
23+
let end = Math.min(total - 1, current + SIBLINGS);
24+
if (start === 3) start = 2;
25+
if (end === total - 2) end = total - 1;
26+
27+
const links: PageLink[] = [{ kind: 'page', page: 1 }];
28+
if (start > 2) links.push({ kind: 'gap', key: 'gap-start' });
29+
for (let page = start; page <= end; page++) links.push({ kind: 'page', page });
30+
if (end < total - 1) links.push({ kind: 'gap', key: 'gap-end' });
31+
if (total > 1) links.push({ kind: 'page', page: total });
32+
return links;
33+
}
1734

1835
@Component({
1936
selector: 'app-blog-list',
@@ -29,14 +46,16 @@ export class BlogListComponent {
2946

3047
readonly BookOpenIcon = BookOpen;
3148
readonly AlertIcon = AlertCircle;
49+
readonly PrevIcon = ChevronLeft;
50+
readonly NextIcon = ChevronRight;
3251
readonly skeletonRows = [1, 2, 3];
3352
readonly stripMarkdown = stripMarkdown;
3453

3554
posts = signal<BlogPostSnippet[]>([]);
3655
state = signal<PageState>('loading');
3756
currentPage = signal(1);
3857
totalPages = signal(0);
39-
pages = computed(() => Array.from({ length: this.totalPages() }, (_, index) => index + 1));
58+
pages = computed(() => pageWindow(this.currentPage(), this.totalPages()));
4059

4160
private reload$ = new Subject<void>();
4261

0 commit comments

Comments
 (0)