master
1---
2import type { BlogPostData } from '@/types/data';
3import {
4 getCategories,
5 getCategoryUrl,
6 getSortedPosts,
7 getTagUrl,
8} from '@/utils/content-utils';
9import ProfileCard from '@components/aside/ProfileCard.astro';
10import TOC from '@components/aside/TOC.astro';
11import Button from '@components/widgets/Button.astro';
12import MetaList from '@components/widgets/MetaList.astro';
13import MainLayout from '@layouts/MainLayout.astro';
14import { t } from '@utils/i18n';
15import { Icon } from 'astro-icon/components';
16
17const categoriesMap = await getCategories();
18const allPosts = await getSortedPosts();
19
20const categoryPosts = new Map<string, { body: string; data: BlogPostData }[]>();
21for (const [category] of categoriesMap) {
22 const posts = allPosts.filter((post) => post.data.category === category).slice(0, 3);
23 categoryPosts.set(category, posts);
24}
25const uncategorizedPosts = allPosts.filter((post) => !post.data.category).slice(0, 3);
26if (uncategorizedPosts.length > 0)
27 categoryPosts.set(t.meta.unCategorized(), uncategorizedPosts);
28---
29
30<MainLayout title={t.navigation.archive.categories()}>
31 <div
32 class="card bg-base-200 border-base-300 swup-transition-fade mx-auto flex flex-col items-center border px-6 py-4"
33 >
34 <div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
35 <h1 class="text-center text-3xl font-bold">{t.navigation.archive.categories()}</h1>
36 <div class="tooltip-content flex flex-col">
37 <div>
38 {t.status.postsCount(allPosts.length) + ', '}
39 </div>
40 <div>
41 {t.status.categoriesCount(categoriesMap.size)}
42 </div>
43 </div>
44 </div>
45 <ul
46 class="timeline timeline-snap-icon timeline-vertical max-md:timeline-compact w-full p-4"
47 >
48 {
49 Array.from(categoryPosts.entries()).map(([category, posts], index) => (
50 <li>
51 {index > 0 && <hr />}
52 <div class="timeline-middle">
53 <Icon
54 name="material-symbols:add-circle-rounded"
55 height="1.25rem"
56 width="1.25rem"
57 />
58 </div>
59 <div class:list={[`timeline-${index % 2 === 0 ? 'start' : 'end'}`, 'w-full']}>
60 <div
61 class:list={[
62 index % 2 === 0 && 'md:flex-row-reverse',
63 'mx-4 flex flex-row items-center justify-between',
64 ]}
65 >
66 <h2 class="scroll-mt-20 text-2xl font-bold" id={`heading-${category}`}>
67 {category}
68 <span class="text-base-content/60 ml-2 text-base">
69 ({categoriesMap.get(category) || 0})
70 </span>
71 </h2>
72 <Button href={getCategoryUrl(category)} title={category} class="pl-3">
73 {t.button.more()}
74 <Icon
75 name="material-symbols:chevron-right-rounded"
76 height="1.5rem"
77 width="1.5rem"
78 />
79 </Button>
80 </div>
81 <ul class="list">
82 {posts.map(({ data }) => (
83 <li class="list-row">
84 <div class="list-col-grow">
85 <a
86 href={`/posts/${data.slug}`}
87 title={data.title}
88 class="text-lg font-bold"
89 >
90 {data.title}
91 </a>
92 <MetaList
93 class="text-base-content/60 mt-2 items-start text-sm"
94 metas={[
95 {
96 icon: 'material-symbols:category-outline-rounded',
97 text: data.category || t.meta.unCategorized(),
98 link: getCategoryUrl(data.category),
99 },
100 {
101 icon: 'material-symbols:tag-rounded',
102 title: t.meta.tags(),
103 group: data.tags.map((tag) => ({
104 icon: 'material-symbols:tag-rounded',
105 text: tag,
106 link: getTagUrl(tag),
107 })),
108 },
109 ]}
110 />
111 </div>
112 </li>
113 ))}
114 </ul>
115 </div>
116 <hr />
117 </li>
118 ))
119 }
120 </ul>
121 </div>
122 <Fragment slot="aside-fixed">
123 <ProfileCard />
124 </Fragment>
125 <Fragment slot="aside-sticky">
126 <TOC
127 headings={Array.from(categoriesMap.keys()).map((category) => ({
128 text: category,
129 slug: `heading-${category}`,
130 depth: 2,
131 }))}
132 />
133 </Fragment>
134</MainLayout>