mirror of
https://github.com/KazooTTT/kazoottt-blog.git
synced 2025-06-22 10:11:30 +08:00
feat: add category page
This commit is contained in:
@ -7,18 +7,42 @@
|
|||||||
class='relative mx-auto flex w-full items-center justify-between sm:flex sm:items-center'
|
class='relative mx-auto flex w-full items-center justify-between sm:flex sm:items-center'
|
||||||
aria-label='global'
|
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'>
|
<div class='flex flex-row items-center justify-center gap-x-5 sm:gap-x-7'>
|
||||||
<a
|
<a
|
||||||
href='/blog'
|
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'
|
aria-label='Nav Menu Item'
|
||||||
>Blog
|
>Blog
|
||||||
</a>
|
</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
|
<a
|
||||||
href='/tools'
|
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'
|
aria-label='Nav Menu Item'
|
||||||
>Tools
|
>Tools
|
||||||
</a>
|
</a>
|
||||||
|
@ -12,7 +12,7 @@ const post = defineCollection({
|
|||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
z.object({
|
z.object({
|
||||||
title: z.string().max(60),
|
title: z.string().max(60),
|
||||||
description: z.string().min(50).max(160),
|
description: z.string(),
|
||||||
publishDate: z
|
publishDate: z
|
||||||
.string()
|
.string()
|
||||||
.or(z.date())
|
.or(z.date())
|
||||||
@ -29,7 +29,8 @@ const post = defineCollection({
|
|||||||
.optional(),
|
.optional(),
|
||||||
draft: z.boolean().default(false),
|
draft: z.boolean().default(false),
|
||||||
tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase),
|
tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase),
|
||||||
ogImage: z.string().optional()
|
ogImage: z.string().optional(),
|
||||||
|
category: z.string().optional(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -7,4 +7,5 @@ coverImage:
|
|||||||
src: "./cover.png"
|
src: "./cover.png"
|
||||||
alt: "Astro build wallpaper"
|
alt: "Astro build wallpaper"
|
||||||
tags: ["test", "image"]
|
tags: ["test", "image"]
|
||||||
|
category: c1
|
||||||
---
|
---
|
||||||
|
@ -4,6 +4,7 @@ description: "This post is for testing the draft post functionality"
|
|||||||
publishDate: "10 Sept 2023"
|
publishDate: "10 Sept 2023"
|
||||||
tags: ["test"]
|
tags: ["test"]
|
||||||
draft: true
|
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.
|
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.
|
||||||
|
@ -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"
|
description: "This post is purely for testing if the css is correct for the title on the page"
|
||||||
publishDate: "01 Feb 2023"
|
publishDate: "01 Feb 2023"
|
||||||
tags: ["test"]
|
tags: ["test"]
|
||||||
|
category: c3
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing the title tag
|
## Testing the title tag
|
||||||
|
@ -8,21 +8,23 @@ import Button from '@/components/Button.astro'
|
|||||||
import Pagination from '@/components/Paginator.astro'
|
import Pagination from '@/components/Paginator.astro'
|
||||||
import PostPreview from '@/components/blog/PostPreview.astro'
|
import PostPreview from '@/components/blog/PostPreview.astro'
|
||||||
import PageLayout from '@/layouts/BaseLayout.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 }) => {
|
export const getStaticPaths = (async ({ paginate }) => {
|
||||||
const allPosts = await getAllPosts()
|
const allPosts = await getAllPosts()
|
||||||
const allPostsByDate = sortMDByDate(allPosts)
|
const allPostsByDate = sortMDByDate(allPosts)
|
||||||
const uniqueTags = getUniqueTags(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
|
}) satisfies GetStaticPaths
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
page: Page<CollectionEntry<'post'>>
|
page: Page<CollectionEntry<'post'>>
|
||||||
uniqueTags: string[]
|
uniqueTags: string[]
|
||||||
|
uniqueCategories: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page, uniqueTags } = Astro.props
|
const { page, uniqueTags, uniqueCategories } = Astro.props
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
description: 'Posts',
|
description: 'Posts',
|
||||||
@ -77,41 +79,89 @@ const paginationProps = {
|
|||||||
</ul>
|
</ul>
|
||||||
<Pagination {...paginationProps} />
|
<Pagination {...paginationProps} />
|
||||||
</section>
|
</section>
|
||||||
{!!uniqueTags.length && (
|
<div>
|
||||||
<aside>
|
{!!uniqueCategories.length && (
|
||||||
<h2 class='mb-4 flex items-center text-lg font-semibold'>
|
<aside>
|
||||||
<svg
|
<h2 class='mb-4 flex items-center text-lg font-semibold'>
|
||||||
aria-hidden='true'
|
<svg
|
||||||
class='h-6 w-6'
|
aria-hidden='true'
|
||||||
fill='none'
|
class='h-6 w-6'
|
||||||
stroke='currentColor'
|
fill='none'
|
||||||
stroke-linecap='round'
|
stroke='currentColor'
|
||||||
stroke-linejoin='round'
|
stroke-linecap='round'
|
||||||
stroke-width='1.5'
|
stroke-linejoin='round'
|
||||||
viewBox='0 0 24 24'
|
stroke-width='1.5'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
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='M0 0h24v24H0z' fill='none' stroke='none' />
|
||||||
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
|
<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='M6 9h-.01' />
|
<path d='M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116' />
|
||||||
</svg>
|
<path d='M6 9h-.01' />
|
||||||
Tags
|
</svg>
|
||||||
</h2>
|
Categories
|
||||||
<ul class='text-bgColor flex flex-wrap gap-2'>
|
</h2>
|
||||||
{uniqueTags.map((tag) => (
|
<ul class='text-bgColor flex flex-wrap gap-2'>
|
||||||
<li>
|
{uniqueCategories.slice(0, 6).map((category) => (
|
||||||
<Button title={tag} href={`/tags/${tag}/`} style='pill' />
|
<li>
|
||||||
</li>
|
<Button title={category} href={`/categories/${category}/`} style='pill' />
|
||||||
))}
|
</li>
|
||||||
</ul>
|
))}
|
||||||
<span class='mt-4 block sm:text-end'>
|
</ul>
|
||||||
<a aria-label='View all blog categories' class='' href='/tags/' data-astro-prefetch>
|
<span class='mt-4 block sm:text-end'>
|
||||||
View all →
|
<a
|
||||||
</a>
|
aria-label='View all blog categories'
|
||||||
</span>
|
class=''
|
||||||
</aside>
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
96
src/pages/categories/[category]/[...page].astro
Normal file
96
src/pages/categories/[category]/[...page].astro
Normal 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>
|
72
src/pages/categories/index.astro
Normal file
72
src/pages/categories/index.astro
Normal 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}`}
|
||||||
|
>
|
||||||
|
#{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>
|
@ -3,18 +3,18 @@ import type { AstroExpressiveCodeOptions } from 'astro-expressive-code'
|
|||||||
|
|
||||||
export const siteConfig: SiteConfig = {
|
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)
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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.prototype.toLocaleDateString() parameters, found in src/utils/date.ts.
|
||||||
date: {
|
date: {
|
||||||
locale: 'en-GB',
|
locale: 'zh-CN',
|
||||||
options: {
|
options: {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@ -31,7 +31,7 @@ export const menuLinks: Array<{ title: string; path: string }> = [
|
|||||||
{
|
{
|
||||||
title: 'Blog',
|
title: 'Blog',
|
||||||
path: '/blog/'
|
path: '/blog/'
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// https://expressive-code.com/reference/configuration/
|
// https://expressive-code.com/reference/configuration/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { cn } from './tailwind'
|
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 { getFormattedDate } from './date'
|
||||||
export { generateToc } from './generateToc'
|
export { generateToc } from './generateToc'
|
||||||
export type { TocItem } from './generateToc'
|
export type { TocItem } from './generateToc'
|
||||||
|
@ -37,3 +37,26 @@ export function getUniqueTagsWithCount(
|
|||||||
)
|
)
|
||||||
].sort((a, b) => b[1] - a[1])
|
].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])
|
||||||
|
}
|
Reference in New Issue
Block a user