Commit 02cee10
Changed files (8)
src
components
i18n
pages
archives
src/components/Timeline.astro
@@ -1,55 +0,0 @@
----
-// Usage:
-// <TimeLine items={items}>
-// <fragement slot="title">
-// {(item) => (titleHTML)}
-// </fragement>
-// <fragement slot="body">
-// {(item) => (bodyHTML)}
-// </fragement>
-// </TimeLine>
-//
-// The result will same to:
-// <div class="timeline-classes">
-// {
-// items.map((item) => (
-// <div class="timeline-node-wrapper-classes">
-// <div class="timeline-node-classes"></div>
-// {titleHTML}
-// {bodyHTML}
-// </div>
-// }
-// </div>
-
-interface Props {
- items: unknown[];
- class?: string;
-}
-
-const { items, class: className } = Astro.props;
-
-const renderedItems = await Promise.all(
- items.map(async (item) => {
- return {
- title: Astro.slots.has('title') ? await Astro.slots.render('title', [item]) : undefined,
- body: Astro.slots.has('body') ? await Astro.slots.render('body', [item]) : undefined,
- };
- })
-);
----
-
-<div class:list={['relative flex flex-col empty:min-h-12', className]}>
- <div
- class="timeline absolute top-0 left-4 h-full w-0.5 bg-linear-to-b from-blue-500 to-purple-500 opacity-60"
- >
- </div>
- {
- renderedItems.map((item) => (
- <div class="relative mb-12 pl-12">
- <div class="absolute top-5 left-2 h-4 w-4 rounded-full bg-blue-500 shadow-lg transition-transform duration-300 hover:scale-125" />
- <Fragment set:html={item.title} />
- <Fragment set:html={item.body} />
- </div>
- ))
- }
-</div>
src/i18n/langs/en.ts
@@ -25,6 +25,10 @@ export const en: Translation = {
[Key.minutesCount]: 'minutes',
[Key.postCount]: 'post',
[Key.postsCount]: 'posts',
+ [Key.tagCount]: 'tag',
+ [Key.tagsCount]: 'tags',
+ [Key.categoryCount]: 'category',
+ [Key.categoriesCount]: 'categories',
[Key.lightMode]: 'Light',
[Key.darkMode]: 'Dark',
src/i18n/langs/zh_CN.ts
@@ -25,6 +25,10 @@ export const zh_CN: Translation = {
[Key.minutesCount]: '分钟',
[Key.postCount]: '篇文章',
[Key.postsCount]: '篇文章',
+ [Key.tagCount]: '个标签',
+ [Key.tagsCount]: '个标签',
+ [Key.categoryCount]: '个分类',
+ [Key.categoriesCount]: '个分类',
[Key.lightMode]: '亮色',
[Key.darkMode]: '暗色',
src/i18n/langs/zh_TW.ts
@@ -25,6 +25,10 @@ export const zh_TW: Translation = {
[Key.minutesCount]: '分鐘',
[Key.postCount]: '篇文章',
[Key.postsCount]: '篇文章',
+ [Key.tagCount]: '個標籤',
+ [Key.tagsCount]: '個標籤',
+ [Key.categoryCount]: '個分類',
+ [Key.categoriesCount]: '個分類',
[Key.lightMode]: '亮色',
[Key.darkMode]: '暗色',
src/i18n/I18nKey.ts
@@ -22,6 +22,10 @@ enum I18nKey {
minutesCount = 'minutesCount',
postCount = 'postCount',
postsCount = 'postsCount',
+ tagCount = 'tagCount',
+ tagsCount = 'tagsCount',
+ categoryCount = 'categoryCount',
+ categoriesCount = 'categoriesCount',
lightMode = 'lightMode',
darkMode = 'darkMode',
src/pages/archives/categories/index.astro
@@ -1,12 +1,13 @@
---
import type { BlogPostData } from '@/types/data';
import { getCategories, getCategoryUrl, getSortedPosts } from '@/utils/content-utils';
-import Timeline from '@components/Timeline.astro';
import Button from '@components/widgets/Button.astro';
-import PostCard from '@components/widgets/PostCard.astro';
+import MetaIcon from '@components/widgets/MetaIcon.astro';
+import ProfileCard from '@components/widgets/ProfileCard.astro';
+import TOC from '@components/widgets/TOC.astro';
import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
-import MainLayout from '@layouts/MainLayout.astro';
+import GridLayout from '@layouts/GridLayout.astro';
import { Icon } from 'astro-icon/components';
const categories = await getCategories();
@@ -22,42 +23,106 @@ if (uncategorizedPosts.length > 0)
categoryPosts.set(i18n(I18nKey.uncategorized) as string, uncategorizedPosts);
---
-<MainLayout title={i18n(I18nKey.categories)}>
- <div class="mx-auto flex max-w-(--breakpoint-xl) flex-col items-center">
- <h1 class="my-8 text-3xl font-bold">{i18n(I18nKey.categories)}</h1>
- <Timeline items={Array.from(categoryPosts.keys())}>
- <fragment slot="title">
+<GridLayout title={i18n(I18nKey.categories)}>
+ <div
+ class="card card-border border-base-300 mx-auto flex flex-col items-center border-2 px-6 py-4"
+ >
+ <div class="tooltip tooltip-right mx-auto w-fit">
+ <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.categories)}</h1>
+ <div class="tooltip-content">
{
- (category: string) => (
- <div class="mb-6 flex items-center justify-between">
- <h2 class="text-2xl font-bold">{category}</h2>
- <Button href={getCategoryUrl(category)} title={category} class="pl-3">
- {i18n(I18nKey.more)}
- <Icon name="material-symbols:chevron-right-rounded" class="text-2xl" />
- </Button>
- </div>
- )
+ [
+ `${allPosts.length} ${i18n(allPosts.length > 1 ? I18nKey.postsCount : I18nKey.postCount)}`,
+ ', ',
+ `${categories.length} ${i18n(categories.length > 1 ? I18nKey.categoriesCount : I18nKey.categoryCount)}`,
+ ]
}
- </fragment>
- <fragment slot="body">
- {
- (category: string) => (
- <div class="ml-4 flex flex-col gap-4">
- {categoryPosts.get(category)?.map((post) => (
- <PostCard
- title={post.data.title}
- url={`/posts/${post.data.slug}/`}
- published={post.data.published}
- tags={post.data.tags || []}
- category={post.data.category}
- cover={post.data.cover}
- description={post.data.description}
- />
- ))}
+ </div>
+ </div>
+ <ul
+ class="timeline timeline-snap-icon timeline-vertical max-md:timeline-compact w-full p-4"
+ >
+ {
+ Array.from(categoryPosts.entries()).map(([category, posts], index) => (
+ <li>
+ {index > 0 && <hr />}
+ <div class="timeline-middle">
+ <Icon name="material-symbols:add-circle-rounded" class="text-xl" />
</div>
- )
- }
- </fragment>
- </Timeline>
+ <div class:list={[`timeline-${index % 2 === 0 ? 'start' : 'end'}`, 'w-full']}>
+ <div
+ class:list={[
+ index % 2 === 0 && 'md:flex-row-reverse',
+ 'mx-4 flex flex-row items-center justify-between',
+ ]}
+ >
+ <h2 class="scroll-mt-20 text-2xl font-bold" id={`heading-${category}`}>
+ {category}
+ </h2>
+ <Button href={getCategoryUrl(category)} title={category} class="pl-3">
+ {i18n(I18nKey.more)}
+ <Icon name="material-symbols:chevron-right-rounded" class="text-2xl" />
+ </Button>
+ </div>
+ <ul class="list">
+ {posts.map(({ data }) => (
+ <li class="list-row">
+ <div class="list-col-grow">
+ <a
+ href={`/posts/${data.slug}`}
+ title={data.title}
+ class="text-lg font-bold"
+ >
+ {data.title}
+ </a>
+ <div class="text-base-content/60 mt-2 flex flex-wrap items-start gap-x-4 gap-y-2 text-sm">
+ {[
+ {
+ icon: 'material-symbols:category-outline-rounded',
+ text: data.category || i18n(I18nKey.uncategorized),
+ link: getCategoryUrl(data.category),
+ },
+ ...(data.tags?.map((tag) => {
+ return {
+ icon: 'material-symbols:tag-rounded',
+ text: tag,
+ link: getCategoryUrl(tag),
+ };
+ }) || []),
+ ].map((meta) => (
+ <div class="flex items-center gap-0">
+ <MetaIcon name={meta.icon} />
+ {meta.link ? (
+ <a href={meta.link} class="meta-text" title={meta.text}>
+ {meta.text}
+ </a>
+ ) : (
+ <span class="meta-text">{meta.text}</span>
+ )}
+ </div>
+ ))}
+ </div>
+ </div>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <hr />
+ </li>
+ ))
+ }
+ </ul>
</div>
-</MainLayout>
+ <Fragment slot="aside-fixed">
+ <ProfileCard />
+ </Fragment>
+ <Fragment slot="aside-sticky">
+ <TOC
+ headings={categories.map((category) => ({
+ text: category,
+ slug: `heading-${category}`,
+ depth: 2,
+ }))}
+ />
+ </Fragment>
+</GridLayout>
src/pages/archives/[...time].astro
@@ -1,6 +1,7 @@
---
import { siteConfig } from '@/config';
import type { BlogPostData } from '@/types/data';
+import Button from '@components/widgets/Button.astro';
import MetaIcon from '@components/widgets/MetaIcon.astro';
import ProfileCard from '@components/widgets/ProfileCard.astro';
import TOC from '@components/widgets/TOC.astro';
@@ -8,6 +9,7 @@ import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
import GridLayout from '@layouts/GridLayout.astro';
import { getCategoryUrl, getPostsCount, getSortedPosts, getTagUrl } from '@utils/content-utils';
+import { Icon } from 'astro-icon/components';
export async function getStaticPaths() {
const allBlogPosts = await getSortedPosts();
@@ -99,9 +101,11 @@ const postCount = await getPostsCount();
</div>
</div>
<div class="card card-bordered border-base-300 border-2 px-6 py-4">
- <h1 class="mb-2 text-center text-3xl font-bold">{i18n(I18nKey.archive)}</h1>
- <div class="text-base-content/80 text-center">
- {`${postCount} ${i18n(postCount > 1 ? I18nKey.postsCount : I18nKey.postCount)}`}
+ <div class="tooltip tooltip-right mx-auto w-fit">
+ <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.archive)}</h1>
+ <div class="tooltip-content">
+ {`${postCount} ${i18n(postCount > 1 ? I18nKey.postsCount : I18nKey.postCount)}`}
+ </div>
</div>
{
(() => {
@@ -166,23 +170,39 @@ const postCount = await getPostsCount();
if (!yearData) {
return <p>SHOULD NOT RENDER THIS, IS A BUG</p>;
}
- return yearData.map(({ month }) => (
- <>
- <div
- class="divider mx-3 mt-8 scroll-mt-20 text-xl font-bold"
- id={`${year}/${month}`}
- >
- <a
- href={`/archives/${year}/${month}`}
- title={`${year}/${month}`}
- class="hover:text-primary duration-200"
- >
- {month}
- </a>
- </div>
- <div class="mx-2">{renderMonth(year, month)}</div>
- </>
- ));
+ return (
+ <ul class="timeline timeline-snap-icon timeline-vertical max-md:timeline-compact w-full">
+ {yearData.map(({ month }, index) => (
+ <li>
+ {index > 0 && <hr />}
+ <div class="timeline-middle">
+ <Icon name="material-symbols:add-circle-rounded" class="text-xl" />
+ </div>
+ <div class:list={[`timeline-${index % 2 === 0 ? 'start' : 'end'}`, 'w-full']}>
+ <div
+ class:list={[
+ index % 2 === 0 && 'md:flex-row-reverse',
+ 'mx-4 flex scroll-mt-20 flex-row items-center justify-between',
+ ]}
+ id={`${year}-${month}`}
+ >
+ <div class="text-2xl font-bold">{month}</div>
+ <Button
+ href={`/archives/${year}/${month}`}
+ title={`${year}/${month}`}
+ class="pl-3"
+ >
+ {i18n(I18nKey.more)}
+ <Icon name="material-symbols:chevron-right-rounded" class="text-2xl" />
+ </Button>
+ </div>
+ <div class="mx-2">{renderMonth(year, month)}</div>
+ </div>
+ <hr />
+ </li>
+ ))}
+ </ul>
+ );
}
function renderAll() {
@@ -230,7 +250,7 @@ const postCount = await getPostsCount();
...data.map(({ month }) => ({
depth: 3,
text: month.toString(),
- slug: `${year}/${month}`,
+ slug: `${year}-${month}`,
})),
])}
/>