Skip to content

Commit

Permalink
feat: add pagination in tag posts (#201)
Browse files Browse the repository at this point in the history
* feat: add pagination in tag posts

Implement pagination in tag posts. Refactor and extract pagination logic and pagination component.

Closes: #152

* fix: update breadcrumbs for updated tag pagination
  • Loading branch information
satnaing authored Dec 29, 2023
1 parent b05b8fb commit 581826a
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 140 deletions.
12 changes: 12 additions & 0 deletions src/components/Breadcrumbs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ const breadcrumbList = currentUrlPath.split("/").slice(1);
// replace Posts with Posts (page number)
breadcrumbList[0] === "posts" &&
breadcrumbList.splice(0, 2, `Posts (page ${breadcrumbList[1] || 1})`);
// if breadcrumb is Home > Tags > [tag] > [page] <etc>
// replace [tag] > [page] with [tag] (page number)
breadcrumbList[0] === "tags" &&
!isNaN(Number(breadcrumbList[2])) &&
breadcrumbList.splice(
1,
3,
`${breadcrumbList[1]} ${
Number(breadcrumbList[2]) === 1 ? "" : "(page " + breadcrumbList[2] + ")"
}`
);
---

<nav class="breadcrumb" aria-label="breadcrumb">
Expand Down
57 changes: 57 additions & 0 deletions src/components/Pagination.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
import LinkButton from "./LinkButton.astro";
export interface Props {
currentPage: number;
totalPages: number;
prevUrl: string;
nextUrl: string;
}
const { currentPage, totalPages, prevUrl, nextUrl } = Astro.props;
const prev = currentPage > 1 ? "" : "disabled";
const next = currentPage < totalPages ? "" : "disabled";
---

{
totalPages > 1 && (
<nav class="pagination-wrapper" aria-label="Pagination">
<LinkButton
disabled={prev === "disabled"}
href={prevUrl}
className={`mr-4 select-none ${prev}`}
ariaLabel="Previous"
>
<svg xmlns="http://www.w3.org/2000/svg" class={`${prev}-svg`}>
<path d="M12.707 17.293 8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z" />
</svg>
Prev
</LinkButton>
{currentPage} / {totalPages}
<LinkButton
disabled={next === "disabled"}
href={nextUrl}
className={`ml-4 select-none ${next}`}
ariaLabel="Next"
>
Next
<svg xmlns="http://www.w3.org/2000/svg" class={`${next}-svg`}>
<path d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z" />
</svg>
</LinkButton>
</nav>
)
}

<style>
.pagination-wrapper {
@apply mb-8 mt-auto flex justify-center;
}
.disabled {
@apply pointer-events-none select-none opacity-50 hover:text-skin-base group-hover:fill-skin-base;
}
.disabled-svg {
@apply group-hover:!fill-skin-base;
}
</style>
64 changes: 14 additions & 50 deletions src/layouts/Posts.astro
Original file line number Diff line number Diff line change
@@ -1,76 +1,40 @@
---
import { SITE } from "@config";
import type { CollectionEntry } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Pagination from "@components/Pagination.astro";
import Card from "@components/Card";
import LinkButton from "@components/LinkButton.astro";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@config";
export interface Props {
pageNum: number;
currentPage: number;
totalPages: number;
posts: CollectionEntry<"blog">[];
paginatedPosts: CollectionEntry<"blog">[];
}
const { pageNum, totalPages, posts } = Astro.props;
const prev = pageNum > 1 ? "" : "disabled";
const next = pageNum < totalPages ? "" : "disabled";
const { currentPage, totalPages, paginatedPosts } = Astro.props;
---

<Layout title={`Posts | ${SITE.title}`}>
<Header activeNav="posts" />
<Main pageTitle="Posts" pageDesc="All the articles I've posted.">
<ul>
{
posts.map(({ data, slug }) => (
paginatedPosts.map(({ data, slug }) => (
<Card href={`/posts/${slug}`} frontmatter={data} />
))
}
</ul>
</Main>

{
totalPages > 1 && (
<nav class="pagination-wrapper" aria-label="Pagination">
<LinkButton
disabled={prev === "disabled"}
href={`/posts${pageNum - 1 !== 1 ? "/" + (pageNum - 1) : ""}`}
className={`mr-4 select-none ${prev}`}
ariaLabel="Previous"
>
<svg xmlns="http://www.w3.org/2000/svg" class={`${prev}-svg`}>
<path d="M12.707 17.293 8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z" />
</svg>
Prev
</LinkButton>
<LinkButton
disabled={next === "disabled"}
href={`/posts/${pageNum + 1}`}
className={`ml-4 select-none ${next}`}
ariaLabel="Next"
>
Next
<svg xmlns="http://www.w3.org/2000/svg" class={`${next}-svg`}>
<path d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z" />
</svg>
</LinkButton>
</nav>
)
}
<Pagination
{currentPage}
{totalPages}
prevUrl={`/posts${currentPage - 1 !== 1 ? "/" + (currentPage - 1) : ""}`}
nextUrl={`/posts/${currentPage + 1}`}
/>

<Footer noMarginTop={totalPages > 1} />
</Layout>

<style>
.pagination-wrapper {
@apply mb-8 mt-auto flex justify-center;
}
.disabled {
@apply pointer-events-none select-none opacity-50 hover:text-skin-base group-hover:fill-skin-base;
}
.disabled-svg {
@apply group-hover:!fill-skin-base;
}
</style>
49 changes: 49 additions & 0 deletions src/layouts/TagPosts.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
import { type CollectionEntry } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Card from "@components/Card";
import Pagination from "@components/Pagination.astro";
import { SITE } from "@config";
export interface Props {
currentPage: number;
totalPages: number;
paginatedPosts: CollectionEntry<"blog">[];
tag: string;
tagName: string;
}
const { currentPage, totalPages, paginatedPosts, tag, tagName } = Astro.props;
---

<Layout title={`Tag: ${tagName} | ${SITE.title}`}>
<Header activeNav="tags" />
<Main
pageTitle={[`Tag:`, `${tagName}`]}
titleTransition={tag}
pageDesc={`All the articles with the tag "${tagName}".`}
>
<h1 slot="title" transition:name={tag}>{`Tag:${tag}`}</h1>
<ul>
{
paginatedPosts.map(({ data, slug }) => (
<Card href={`/posts/${slug}`} frontmatter={data} />
))
}
</ul>
</Main>

<Pagination
{currentPage}
{totalPages}
prevUrl={`/tags/${tag}${
currentPage - 1 !== 1 ? "/" + (currentPage - 1) : ""
}`}
nextUrl={`/tags/${tag}/${currentPage + 1}`}
/>

<Footer noMarginTop={totalPages > 1} />
</Layout>
28 changes: 6 additions & 22 deletions src/pages/posts/[slug]/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Posts from "@layouts/Posts.astro";
import PostDetails from "@layouts/PostDetails.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import { SITE } from "@config";
import getPagination from "@utils/getPagination";
export interface Props {
post: CollectionEntry<"blog">;
Expand Down Expand Up @@ -32,26 +32,10 @@ const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const currentPage =
slug && !isNaN(Number(slug)) && totalPages.includes(Number(slug))
? Number(slug)
: 0;
const lastPost = currentPage * SITE.postPerPage;
const startPost = lastPost - SITE.postPerPage;
const paginatedPosts = sortedPosts.slice(startPost, lastPost);
const pagination = getPagination({
posts: sortedPosts,
page: slug,
});
---

{
post ? (
<PostDetails post={post} />
) : (
<Posts
posts={paginatedPosts}
pageNum={currentPage}
totalPages={totalPages.length}
/>
)
}
{post ? <PostDetails post={post} /> : <Posts {...pagination} />}
16 changes: 8 additions & 8 deletions src/pages/posts/index.astro
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
---
import { SITE } from "@config";
import { getCollection } from "astro:content";
import Posts from "@layouts/Posts.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import { getCollection } from "astro:content";
import getPagination from "@utils/getPagination";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const paginatedPosts = sortedPosts.slice(0, SITE.postPerPage);
const pagination = getPagination({
posts: sortedPosts,
page: 1,
isIndex: true,
});
---

<Posts posts={paginatedPosts} pageNum={1} totalPages={totalPages.length} />
<Posts {...pagination} />
58 changes: 0 additions & 58 deletions src/pages/tags/[tag].astro

This file was deleted.

44 changes: 44 additions & 0 deletions src/pages/tags/[tag]/[page].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import TagPosts from "@layouts/TagPosts.astro";
import getUniqueTags from "@utils/getUniqueTags";
import getPostsByTag from "@utils/getPostsByTag";
import getPageNumbers from "@utils/getPageNumbers";
import getPagination from "@utils/getPagination";
export interface Props {
post: CollectionEntry<"blog">;
tag: string;
tagName: string;
}
export async function getStaticPaths() {
const posts = await getCollection("blog");
const tags = getUniqueTags(posts);
return tags.flatMap(({ tag, tagName }) => {
const tagPosts = getPostsByTag(posts, tag);
const totalPages = getPageNumbers(tagPosts.length);
return totalPages.map(page => ({
params: { tag, page },
props: { tag, tagName },
}));
});
}
const { page } = Astro.params;
const { tag, tagName } = Astro.props;
const posts = await getCollection("blog", ({ data }) => !data.draft);
const postsByTag = getPostsByTag(posts, tag);
const pagination = getPagination({
posts: postsByTag,
page,
});
---

<TagPosts {...pagination} {tag} {tagName} />
Loading

0 comments on commit 581826a

Please sign in to comment.