Skip to content

Commit fcd8703

Browse files
committed
Add BlogPagination component and implement pagination logic in blog index
1 parent 2c388d7 commit fcd8703

File tree

4 files changed

+161
-11
lines changed

4 files changed

+161
-11
lines changed

GEMINI.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Gemini Customization File
2+
3+
This file provides instructions for Gemini to effectively assist with development in this project.
4+
5+
## Project Overview
6+
7+
This project is a Python cheatsheet website built with Vue.js and Vite. The content is written in Markdown and located in the `docs/` directory.
8+
9+
## Technologies
10+
11+
* **Framework:** Vue.js 3
12+
* **Build Tool:** Vite
13+
* **Package Manager:** pnpm
14+
* **Language:** TypeScript
15+
* **Styling:** Tailwind CSS
16+
* **Linting:** ESLint
17+
* **Formatting:** Prettier
18+
* **Testing:** Vitest
19+
20+
## Coding Style and Conventions
21+
22+
* Follow the existing coding style.
23+
* Use TypeScript for all new code.
24+
* Use pnpm for all package management.
25+
* Components are located in `src/components`.
26+
* Pages are located in `src/pages`.
27+
* Content is in Markdown files in the `docs/` directory.
28+
29+
## Available Scripts
30+
31+
The following scripts are available in `package.json`:
32+
33+
* `pnpm dev`: Starts the development server.
34+
* `pnpm build`: Builds the project for production.
35+
* `pnpm preview`: Previews the production build.
36+
* `pnpm test`: Runs the tests.
37+
* `pnpm lint`: Lints the code with ESLint.
38+
* `pnpm typecheck`: Runs a type check with `vue-tsc`.
39+
* `pnpm fetch-contributors`: Fetches the contributors from GitHub.

src/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ declare module 'vue' {
2828
BaseWarning: typeof import('./components/ui/warning/BaseWarning.vue')['default']
2929
BaseWarningContent: typeof import('./components/ui/warning/BaseWarningContent.vue')['default']
3030
BaseWarningTitle: typeof import('./components/ui/warning/BaseWarningTitle.vue')['default']
31+
BlogPagination: typeof import('./components/blog/BlogPagination.vue')['default']
3132
BlogTitleHeader: typeof import('./components/BlogTitleHeader.vue')['default']
3233
BugIcon: typeof import('./components/icons/BugIcon.vue')['default']
3334
CarbonAds: typeof import('./components/CarbonAds.vue')['default']
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script setup lang="ts">
2+
defineProps({
3+
currentPage: {
4+
type: Number,
5+
required: true,
6+
},
7+
totalPages: {
8+
type: Number,
9+
required: true,
10+
},
11+
})
12+
13+
const emit = defineEmits(['prev-page', 'next-page'])
14+
15+
const prev = () => {
16+
emit('prev-page')
17+
}
18+
19+
const next = () => {
20+
emit('next-page')
21+
}
22+
</script>
23+
24+
<template>
25+
<div class="mt-12 flex items-center justify-between gap-x-8 max-w-lg mx-auto">
26+
<button
27+
:disabled="currentPage === 1"
28+
class="rounded-lg border border-slate-300/70 p-4 text-left transition duration-300 hover:border-sky-500 hover:bg-sky-400/5 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:hover:border-sky-400"
29+
@click="prev"
30+
>
31+
<span class="text-sm text-slate-500 dark:text-slate-400">Previous</span>
32+
<span class="mt-1 block font-medium text-sky-500 dark:text-sky-400">
33+
Newer posts
34+
</span>
35+
</button>
36+
37+
<span class="font-medium text-slate-700 dark:text-slate-300">
38+
{{ currentPage }} / {{ totalPages }}
39+
</span>
40+
41+
<button
42+
:disabled="currentPage === totalPages"
43+
class="rounded-lg border border-slate-300/70 p-4 text-right transition duration-300 hover:border-sky-500 hover:bg-sky-400/5 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:hover:border-sky-400"
44+
@click="next"
45+
>
46+
<span class="text-sm text-slate-500 dark:text-slate-400">Next</span>
47+
<span class="mt-1 block font-medium text-sky-500 dark:text-sky-400">
48+
Older posts
49+
</span>
50+
</button>
51+
</div>
52+
</template>

src/pages/blog/index.vue

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<script setup>
2+
import { watch } from 'vue'
3+
import { useRoute, useRouter } from 'vue-router'
4+
25
useHead({
36
title: 'Blog - Python Cheatsheet',
47
meta: [
@@ -11,6 +14,16 @@ useHead({
1114
})
1215
1316
const router = useRouter()
17+
const route = useRoute()
18+
const currentPage = ref(parseInt(route.query.page) || 1)
19+
const postsPerPage = 7
20+
21+
watch(
22+
() => route.query.page,
23+
(newPage) => {
24+
currentPage.value = parseInt(newPage) || 1
25+
},
26+
)
1427
1528
const articles = computed(() => {
1629
const routes = router.options.routes
@@ -25,27 +38,62 @@ const articles = computed(() => {
2538
})
2639
})
2740
28-
const latestArticle = computed(() => articles.value[0])
29-
const otherArticles = computed(() => articles.value.slice(1))
41+
const totalPages = computed(() => {
42+
return Math.ceil(articles.value.length / postsPerPage)
43+
})
44+
45+
const postsToShow = computed(() => {
46+
const start = (currentPage.value - 1) * postsPerPage
47+
const end = start + postsPerPage
48+
return articles.value.slice(start, end)
49+
})
50+
51+
const featuredArticle = computed(() => {
52+
return currentPage.value === 1 ? postsToShow.value[0] : null
53+
})
54+
55+
const gridArticles = computed(() => {
56+
if (currentPage.value === 1) {
57+
return postsToShow.value.slice(1)
58+
}
59+
return postsToShow.value
60+
})
3061
3162
const getTags = (article) => {
3263
const tags = article.children[0]?.meta?.tags
3364
if (!tags) return []
3465
return tags.split(',').map((tag) => tag.trim())
3566
}
67+
68+
function updatePage(newPage) {
69+
currentPage.value = newPage
70+
router.push({ query: { page: newPage } })
71+
}
72+
73+
function nextPage() {
74+
if (currentPage.value < totalPages.value) {
75+
updatePage(currentPage.value + 1)
76+
}
77+
}
78+
79+
function prevPage() {
80+
if (currentPage.value > 1) {
81+
updatePage(currentPage.value - 1)
82+
}
83+
}
3684
</script>
3785
3886
<template>
39-
<div v-if="latestArticle" class="mb-12">
87+
<div v-if="featuredArticle" class="mb-12">
4088
<router-link
41-
:to="latestArticle.path"
89+
:to="featuredArticle.path"
4290
class="group block overflow-hidden rounded-lg border border-slate-200 bg-white shadow-md transition-all duration-300 hover:shadow-xl dark:border-slate-700 dark:bg-slate-800"
4391
>
4492
<div class="md:flex">
4593
<div class="relative md:w-1/2">
4694
<img
47-
v-if="latestArticle.children[0]?.meta?.socialImage"
48-
:src="latestArticle.children[0]?.meta?.socialImage"
95+
v-if="featuredArticle.children[0]?.meta?.socialImage"
96+
:src="featuredArticle.children[0]?.meta?.socialImage"
4997
alt=""
5098
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
5199
/>
@@ -72,22 +120,25 @@ const getTags = (article) => {
72120
<div class="flex flex-col p-6 md:w-1/2">
73121
<div class="flex-1">
74122
<div class="mb-2 flex flex-wrap gap-2">
75-
<Tag v-for="tag in getTags(latestArticle).slice(0, 2)" :key="tag">
123+
<Tag
124+
v-for="tag in getTags(featuredArticle).slice(0, 2)"
125+
:key="tag"
126+
>
76127
{{ tag }}
77128
</Tag>
78129
</div>
79130
<h2
80131
class="text-2xl font-semibold text-slate-800 dark:text-slate-100"
81132
>
82-
{{ latestArticle.children[0]?.meta?.title }}
133+
{{ featuredArticle.children[0]?.meta?.title }}
83134
</h2>
84135
<p class="mt-2 text-slate-600 dark:text-slate-400">
85-
{{ latestArticle.children[0]?.meta?.description }}
136+
{{ featuredArticle.children[0]?.meta?.description }}
86137
</p>
87138
</div>
88139
<div class="mt-4 flex items-center justify-between">
89140
<time class="text-sm text-slate-500 dark:text-slate-400">
90-
{{ latestArticle.children[0]?.meta?.date }}
141+
{{ featuredArticle.children[0]?.meta?.date }}
91142
</time>
92143
<div class="flex items-center text-sm font-medium text-sky-500">
93144
Read article
@@ -113,7 +164,7 @@ const getTags = (article) => {
113164
114165
<div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
115166
<router-link
116-
v-for="article in otherArticles"
167+
v-for="article in gridArticles"
117168
:key="article.path"
118169
:to="article.path"
119170
class="group flex flex-col overflow-hidden rounded-lg border border-slate-200 bg-white shadow-md transition-all duration-300 hover:shadow-xl dark:border-slate-700 dark:bg-slate-800"
@@ -183,6 +234,13 @@ const getTags = (article) => {
183234
</div>
184235
</router-link>
185236
</div>
237+
238+
<BlogPagination
239+
:current-page="currentPage"
240+
:total-pages="totalPages"
241+
@prev-page="prevPage"
242+
@next-page="nextPage"
243+
/>
186244
</template>
187245
188246
<route lang="yaml">

0 commit comments

Comments
 (0)