Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fcd8703

Browse files
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']

‎src/components/blog/BlogPagination.vue

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)

AltStyle によって変換されたページ (->オリジナル) /