Day18 - Astro Series: Content Collection Category

Astro 系列文第十八日:实现集合分类功能

一个漂亮的渐变背景上面有一句标题:「实现集合分类功能」

前言

前面学习了内容集合与页签的制作,今天的主题练习透过整理集合抓取到的资料更进一步制作分类功能页面。

定义问题

随着内容集合发布的文章越来越多,里面可能会有许多相同性质的文章你会想要集合起来并替他们分类,并且自动根据类别自动创建页面方便查阅。希望达成以下需求:

  • 替每个文章添加类别资料
  • 创建一个展示当前集合中所有类别的页面
  • 创建个别的类别页面并在其中显示所有该类别的文章

替每个文章添加类别资料

在集合的设定档中定义好 category 资料,如无输入时则预设给予 unsorted 字串。 (该 post 集合记得要回传)

const post = defineCollection({
schema: z.object({
isDraft: z.boolean().default(true),
category: z.string().default('unsorted'),
}),
});

集合定义好之后就可以开始在 content 当中撰写 Markdown 或 MDX 资料了。

创建一个展示当前集合中所有类别的页面

post/categories 中创建 index.astro 档案,并索取 post 集合可用的文章内的 category 资料阵列,摊平装入 Set 中(由于可能有多篇文章具备相同的类别,因此要消除重复的类别),最后再根据字母做分类,这样便得到 categories 也就是当前该集合所有存在且不重复的分类名称。

const publishedPosts = await getCollection('post', (post) => (import.meta.env.PROD ? !post.data.isDraft : true));
const categories = [...new Set(publishedPosts.map((post) => post.data.category).flat())].sort((a, b) =>
a.localeCompare(b, 'en', { sensitivity: 'base' }),
);

接着只需要简单的拿这笔资料在模板当中呈现即可。

{
categories.map((category) => (
<ul>
<li class="text-3xl">
<Card>
<a href={`/post/categories/${category}`}>{category}</a>
</Card>
</li>
</ul>
));
}

创建个别的类别页面并在其中显示所有该类别的文章

这里需要使用到先前学到的动态路由与 getStaticPaths 方法。首先先创建一个动态路由档案: pages/post/categories/[slug].astro,并抓取当前该集合所有存在且不重复的分类名称:

export async function getStaticPaths() {
/* 1. 抓取当前该集合所有存在且不重复的分类名称,资料大致会长得像这样:
[
'A-分类名称', 'B-分类名称', 'C-分类名称',
]
*/
const publishedPosts = await getCollection('post', (post) => (import.meta.env.PROD ? !post.data.isDraft : true));
const categories = [...new Set(publishedPosts.map((post) => post.data.category).flat())];
/* 2. 最后制作 getStaticPaths 期望接收的资料
[
{ params: { slug: 'A-分类名称' }, props: { publishedPosts: [Array] } },
{ params: { slug: 'B-分类名称' }, props: { publishedPosts: [Array] } },
{ params: { slug: 'C-分类名称' }, props: { publishedPosts: [Array] } },
]
*/
return categories.flatMap((category) => [{ params: { slug: category }, props: { publishedPosts } }]);
}

依照以上的操作顺利透过 getStaticPaths 根据分类来创造分类页面,除了创造页面之外也替每个页面传入了 publishedPosts 的 Props,后续再过滤出跟这个 slug 相关的所有文章并依照时间分类。

const { slug } = Astro.params;
const { publishedPosts } = Astro.props;
const avaliableCurrentCategoryPosts = publishedPosts
.filter((post) => post.data.category === slug)
.sort((a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime());

总结

在这个分类功能实作当中利用了动态路由与内容集合来产生文章分类页面,可以在 GitHub 上观赏我自己部落格的分类页面作法🔗

延伸阅读