feat: add category page

This commit is contained in:
KazooTTT
2024-07-21 22:29:07 +08:00
parent 3f502739d0
commit 50813b0c43
11 changed files with 320 additions and 51 deletions

View File

@ -7,18 +7,42 @@
class='relative mx-auto flex w-full items-center justify-between sm:flex sm:items-center'
aria-label='global'
>
<a class='flex-none text-xl font-semibold' href='/' aria-label='Brand'>resume</a>
<a class='flex-none text-xl font-semibold' href='/' aria-label='Brand'>KazooTTT Blog</a>
<div class='flex flex-row items-center justify-center gap-x-5 sm:gap-x-7'>
<a
href='/blog'
class='flex-none text-[1.05rem] font-medium hover:text-foreground/75'
class={`
flex-none text-[1.05rem] font-medium hover:text-foreground/75
${Astro.url.pathname.startsWith('/blog') ? 'text-green-400' : ''}
`}
aria-label='Nav Menu Item'
>Blog
</a>
<a
href='/categories'
class={`
flex-none text-[1.05rem] font-medium hover:text-foreground/75
${Astro.url.pathname.startsWith('/categories') ? 'text-green-400' : ''}
`}
aria-label='Nav Menu Item'
>Categories
</a>
<a
href='/tags'
class={`
flex-none text-[1.05rem] font-medium hover:text-foreground/75
${Astro.url.pathname.startsWith('/tags') ? 'text-green-400' : ''}
`}
aria-label='Nav Menu Item'
>Tags
</a>
<a
href='/tools'
class='flex-none text-[1.05rem] font-medium hover:text-foreground/75'
class={`
flex-none text-[1.05rem] font-medium hover:text-foreground/75
${Astro.url.pathname.startsWith('/tools') ? 'text-green-400' : ''}
`}
aria-label='Nav Menu Item'
>Tools
</a>

View File

@ -12,7 +12,7 @@ const post = defineCollection({
schema: ({ image }) =>
z.object({
title: z.string().max(60),
description: z.string().min(50).max(160),
description: z.string(),
publishDate: z
.string()
.or(z.date())
@ -29,7 +29,8 @@ const post = defineCollection({
.optional(),
draft: z.boolean().default(false),
tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase),
ogImage: z.string().optional()
ogImage: z.string().optional(),
category: z.string().optional(),
})
})

View File

@ -7,4 +7,5 @@ coverImage:
src: "./cover.png"
alt: "Astro build wallpaper"
tags: ["test", "image"]
category: c1
---

View File

@ -4,6 +4,7 @@ description: "This post is for testing the draft post functionality"
publishDate: "10 Sept 2023"
tags: ["test"]
draft: true
category: c2
---
If this is working correctly, this post should only be accessible in a dev environment, as well as any tags that are unique to this post.

View File

@ -3,6 +3,7 @@ title: "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Id"
description: "This post is purely for testing if the css is correct for the title on the page"
publishDate: "01 Feb 2023"
tags: ["test"]
category: c3
---
## Testing the title tag

View File

@ -8,21 +8,23 @@ import Button from '@/components/Button.astro'
import Pagination from '@/components/Paginator.astro'
import PostPreview from '@/components/blog/PostPreview.astro'
import PageLayout from '@/layouts/BaseLayout.astro'
import { getAllPosts, getUniqueTags, sortMDByDate } from '@/utils'
import { getAllPosts, getUniqueCategories, getUniqueTags, sortMDByDate } from '@/utils'
export const getStaticPaths = (async ({ paginate }) => {
const allPosts = await getAllPosts()
const allPostsByDate = sortMDByDate(allPosts)
const uniqueTags = getUniqueTags(allPosts)
return paginate(allPostsByDate, { pageSize: 10, props: { uniqueTags } })
const uniqueCategories = getUniqueCategories(allPosts)
return paginate(allPostsByDate, { pageSize: 10, props: { uniqueTags, uniqueCategories } })
}) satisfies GetStaticPaths
interface Props {
page: Page<CollectionEntry<'post'>>
uniqueTags: string[]
uniqueCategories: string[]
}
const { page, uniqueTags } = Astro.props
const { page, uniqueTags, uniqueCategories } = Astro.props
const meta = {
description: 'Posts',
@ -77,41 +79,89 @@ const paginationProps = {
</ul>
<Pagination {...paginationProps} />
</section>
{!!uniqueTags.length && (
<aside>
<h2 class='mb-4 flex items-center text-lg font-semibold'>
<svg
aria-hidden='true'
class='h-6 w-6'
fill='none'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='1.5'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M0 0h24v24H0z' fill='none' stroke='none' />
<path d='M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z' />
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
<path d='M6 9h-.01' />
</svg>
Tags
</h2>
<ul class='text-bgColor flex flex-wrap gap-2'>
{uniqueTags.map((tag) => (
<li>
<Button title={tag} href={`/tags/${tag}/`} style='pill' />
</li>
))}
</ul>
<span class='mt-4 block sm:text-end'>
<a aria-label='View all blog categories' class='' href='/tags/' data-astro-prefetch>
View all →
</a>
</span>
</aside>
)}
<div>
{!!uniqueCategories.length && (
<aside>
<h2 class='mb-4 flex items-center text-lg font-semibold'>
<svg
aria-hidden='true'
class='h-6 w-6'
fill='none'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='1.5'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M0 0h24v24H0z' fill='none' stroke='none' />
<path d='M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z' />
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
<path d='M6 9h-.01' />
</svg>
Categories
</h2>
<ul class='text-bgColor flex flex-wrap gap-2'>
{uniqueCategories.slice(0, 6).map((category) => (
<li>
<Button title={category} href={`/categories/${category}/`} style='pill' />
</li>
))}
</ul>
<span class='mt-4 block sm:text-end'>
<a
aria-label='View all blog categories'
class=''
href='/categories/'
data-astro-prefetch
>
View all →
</a>
</span>
</aside>
)}
{!!uniqueTags.length && (
<aside>
<h2 class='mb-4 flex items-center text-lg font-semibold'>
<svg
aria-hidden='true'
class='h-6 w-6'
fill='none'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='1.5'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M0 0h24v24H0z' fill='none' stroke='none' />
<path d='M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z' />
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
<path d='M6 9h-.01' />
</svg>
Tags
</h2>
<ul class='text-bgColor flex flex-wrap gap-2'>
{uniqueTags.slice(0, 6).map((tag) => (
<li>
<Button title={tag} href={`/tags/${tag}/`} style='pill' />
</li>
))}
</ul>
<span class='mt-4 block sm:text-end'>
<a
aria-label='View all blog categories'
class=''
href='/tags/'
data-astro-prefetch
>
View all →
</a>
</span>
</aside>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,96 @@
---
export const prerender = true
import type { GetStaticPaths, Page } from 'astro'
import type { CollectionEntry } from 'astro:content'
import Pagination from '@/components/Paginator.astro'
import PostPreview from '@/components/blog/PostPreview.astro'
import PageLayout from '@/layouts/BaseLayout.astro'
import Button from '@/components/Button.astro'
import { getAllPosts, getUniqueTags, sortMDByDate } from '@/utils'
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
const allPosts = await getAllPosts()
const allPostsByDate = sortMDByDate(allPosts)
const uniqueTags = getUniqueTags(allPostsByDate)
return uniqueTags.flatMap((tag) => {
const filterPosts = allPostsByDate.filter((post) => post.data.tags.includes(tag))
return paginate(filterPosts, {
pageSize: 10,
params: { tag }
})
})
}
interface Props {
page: Page<CollectionEntry<'post'>>
}
const { page } = Astro.props
const { tag } = Astro.params
const meta = {
description: `View all posts with the tag - ${tag}`,
title: `Tag: ${tag}`
}
const paginationProps = {
...(page.url.prev && {
prevUrl: {
text: `← Previous Tags`,
url: page.url.prev
}
}),
...(page.url.next && {
nextUrl: {
text: `Next Tags →`,
url: page.url.next
}
})
}
---
<PageLayout meta={meta}>
<div class='w-full'>
<Button title='Back' href='/blog' style='button'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='14'
height='14'
viewBox='0 0 24 24'
slot='icon-before'
>
<path
fill='currentColor'
d='m6.921 12.5l5.792 5.792L12 19l-7-7l7-7l.713.708L6.921 11.5H19v1z'
>
</path>
</svg>
</Button>
<h1 class='mb-6 mt-5 flex items-end gap-x-2 text-2xl font-bold'>
Tags:
<span class='text-xl'>#{tag}</span>
</h1>
<section aria-label='Blog post list'>
<ul class='flex flex-col gap-y-3 text-start'>
{page.data.map((p) => <PostPreview as='h2' post={p} withDesc />)}
</ul>
<Pagination {...paginationProps} />
</section>
<a
href='https://github.com/srleom/astro-theme-resume.git'
class='mt-16 inline-flex flex-row items-center gap-x-3 rounded-3xl border border-input px-4 py-2 text-sm shadow-sm transition-all hover:shadow-md'
>
<span class='relative flex items-center justify-center'>
<span
class='absolute inline-flex h-2 w-2 animate-ping rounded-full border border-green-400 bg-green-400 opacity-75'
></span>
<span class='relative inline-flex h-2 w-2 rounded-full bg-green-400'></span>
</span>
<p class='font-medium'>Get free template</p>
</a>
</div>
</PageLayout>

View File

@ -0,0 +1,72 @@
---
import Button from '@/components/Button.astro'
import PageLayout from '@/layouts/BaseLayout.astro'
import { getAllPosts, getUniqueCategoriesWithCount } from '@/utils'
const allPosts = await getAllPosts()
const allCategories = getUniqueCategoriesWithCount(allPosts)
const meta = {
description: "A list of all the topics I've written about in my posts",
title: 'All Categories'
}
---
<PageLayout meta={meta}>
<div class='w-full'>
<Button title='Back' href='/blog' style='button'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='14'
height='14'
viewBox='0 0 24 24'
slot='icon-before'
>
<path
fill='currentColor'
d='m6.921 12.5l5.792 5.792L12 19l-7-7l7-7l.713.708L6.921 11.5H19v1z'
>
</path>
</svg>
</Button>
<h1 class='mb-6 mt-5 text-2xl font-bold'>Categories</h1>
{allCategories.length === 0 && <p>No posts yet.</p>}
{
allCategories.length > 0 && (
<ul class='flex flex-col gap-y-3'>
{allCategories.map(([tag, val]) => (
<li class='flex items-center gap-x-2 '>
<a
class='inline-block underline underline-offset-4 hover:text-foreground/75'
data-astro-prefetch
href={`/tags/${tag}/`}
title={`View posts with the tag: ${tag}`}
>
&#35;{tag}
</a>
<span class='inline-block'>
- {val} post{val > 1 && 's'}
</span>
</li>
))}
</ul>
)
}
<a
href='https://github.com/srleom/astro-theme-resume.git'
class='mt-16 inline-flex flex-row items-center gap-x-3 rounded-3xl border border-input px-4 py-2 text-sm shadow-sm transition-all hover:shadow-md'
>
<span class='relative flex items-center justify-center'>
<span
class='absolute inline-flex h-2 w-2 animate-ping rounded-full border border-green-400 bg-green-400 opacity-75'
></span>
<span class='relative inline-flex h-2 w-2 rounded-full bg-green-400'></span>
</span>
<p class='font-medium'>Get free template</p>
</a>
</div>
</PageLayout>

View File

@ -3,18 +3,18 @@ import type { AstroExpressiveCodeOptions } from 'astro-expressive-code'
export const siteConfig: SiteConfig = {
// Used as both a meta property (src/components/BaseHead.astro L:31 + L:49) & the generated satori png (src/pages/og-image/[slug].png.ts)
author: 'SRLEOM',
author: 'KazooTTT',
// Meta property used to construct the meta title property, found in src/components/BaseHead.astro L:11
title: 'astro-theme-resume',
title: 'KazooTTT Blog',
// Meta property used as the default description meta property
description: 'The official Astro Resume Theme',
description: '声控烤箱 我喜欢的烤箱是声控的 前端小透明',
// HTML lang property, found in src/layouts/Base.astro L:18
lang: 'en-GB',
lang: 'zh-CN',
// Meta property, found in src/components/BaseHead.astro L:42
ogLocale: 'en_GB',
ogLocale: 'zh-CN',
// Date.prototype.toLocaleDateString() parameters, found in src/utils/date.ts.
date: {
locale: 'en-GB',
locale: 'zh-CN',
options: {
day: 'numeric',
month: 'short',
@ -31,7 +31,7 @@ export const menuLinks: Array<{ title: string; path: string }> = [
{
title: 'Blog',
path: '/blog/'
}
},
]
// https://expressive-code.com/reference/configuration/

View File

@ -1,5 +1,5 @@
export { cn } from './tailwind'
export { getAllPosts, sortMDByDate, getUniqueTags, getUniqueTagsWithCount } from './post'
export { getAllPosts, sortMDByDate, getUniqueTags, getUniqueTagsWithCount, getAllCategories, getUniqueCategories,getUniqueCategoriesWithCount } from './post'
export { getFormattedDate } from './date'
export { generateToc } from './generateToc'
export type { TocItem } from './generateToc'

View File

@ -37,3 +37,26 @@ export function getUniqueTagsWithCount(
)
].sort((a, b) => b[1] - a[1])
}
/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
export function getAllCategories(posts: Array<CollectionEntry<'post'>>): string[] {
return posts.map(post => post.data.category ?? "未分类")
}
/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
export function getUniqueCategories(posts: Array<CollectionEntry<'post'>>) {
return [...new Set(getAllCategories(posts))]
}
/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
export function getUniqueCategoriesWithCount(
posts: Array<CollectionEntry<'post'>>
): Array<[string, number]> {
return [
...getAllCategories(posts).reduce(
(acc, t) => acc.set(t, (acc.get(t) || 0) + 1),
new Map<string, number>()
)
].sort((a, b) => b[1] - a[1])
}