Commit 7a40b53

HPCesia <me@hpcesia.com>
2025-02-09 11:58:33
perf: time archive
1 parent 0f72910
Changed files (3)
src/components/TimeArchives.astro
@@ -1,53 +0,0 @@
----
-import { getTimeArchives } from '@utils/content-utils';
-import Timeline from './Timeline.astro';
-import PostCard from './widgets/PostCard.astro';
-
-type AllTimeArchives = Awaited<ReturnType<typeof getTimeArchives>>;
-type YearArchives = AllTimeArchives[number];
-type MonthArchives = YearArchives['months'][number];
-
-interface Props {
-  group: AllTimeArchives | YearArchives | MonthArchives;
-}
-
-const { group } = Astro.props;
----
-
-{
-  Array.isArray(group)
-    ? group.map((year) => <Astro.self group={year} />)
-    : 'year' in group && (
-        <Fragment>
-          <div class="ml-2 text-3xl font-bold">
-            <a href={`/archives/${group.year}/`}>{group.year}</a>
-          </div>
-          <Timeline items={group.months} class="ml-4">
-            <fragment slot="title">
-              {(monthGroup: MonthArchives) => (
-                <div class="mt-3 mb-6 ml-6 flex items-center justify-between">
-                  <h2 class="text-2xl font-bold">{monthGroup.month}</h2>
-                </div>
-              )}
-            </fragment>
-            <fragment slot="body">
-              {(monthGroup: MonthArchives) => (
-                <div class="ml-4 flex flex-col gap-4">
-                  {monthGroup.posts.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>
-              )}
-            </fragment>
-          </Timeline>
-        </Fragment>
-      )
-}
src/pages/archives/[...time].astro
@@ -1,34 +1,226 @@
 ---
-import TimeArchives from '@components/TimeArchives.astro';
+import { siteConfig } from '@/config';
+import type { BlogPostData } from '@/types/data';
+import MetaIcon from '@components/widgets/MetaIcon.astro';
 import ProfileCard from '@components/widgets/ProfileCard.astro';
+import I18nKey from '@i18n/I18nKey';
+import { i18n } from '@i18n/translation';
 import GridLayout from '@layouts/GridLayout.astro';
-import { getTimeArchives } from '@utils/content-utils';
-
-type AllTimeArchives = Awaited<ReturnType<typeof getTimeArchives>>;
-type YearArchives = AllTimeArchives[number];
+import { getCategoryUrl, getSortedPosts, getTagUrl } from '@utils/content-utils';
 
 export async function getStaticPaths() {
-  const timeReducedPosts = await getTimeArchives();
-  let paths: {
-    params: { time: string | undefined };
-    props: { group: AllTimeArchives | YearArchives };
-  }[] = [{ params: { time: undefined }, props: { group: timeReducedPosts } }];
-  paths = [
-    ...paths,
-    ...timeReducedPosts.map((group) => ({
-      params: { time: `${group.year}` },
-      props: { group: group },
+  const allBlogPosts = await getSortedPosts();
+  const yearMap = new Map<number, Map<number, { body: string; data: BlogPostData }[]>>();
+  for (const post of allBlogPosts) {
+    const year = post.data.published.getFullYear();
+    const month = post.data.published.getMonth() + 1;
+    let monthMap = yearMap.get(year);
+    if (!monthMap) {
+      monthMap = new Map();
+      yearMap.set(year, monthMap);
+    }
+    let monthPosts = monthMap.get(month);
+    if (!monthPosts) {
+      monthPosts = [];
+      monthMap.set(month, monthPosts);
+    }
+    monthPosts.push(post);
+  }
+  const data = Array.from(yearMap.entries()).map(([year, monthMap]) => ({
+    year,
+    data: Array.from(monthMap.entries()).map(([month, postData]) => ({
+      month,
+      data: postData,
+    })),
+  }));
+  const paths = [
+    {
+      params: {
+        time: undefined,
+      },
+      props: {
+        data,
+      },
+    },
+    ...data.map(({ year }) => ({
+      params: {
+        time: year.toString(),
+      },
+      props: {
+        data: data,
+      },
     })),
+    ...data.flatMap(({ year, data: monthData }) =>
+      monthData.map(({ month }) => ({
+        params: {
+          time: `${year}/${month}`,
+        },
+        props: {
+          data: data,
+        },
+      }))
+    ),
   ];
   return paths;
 }
 
-const { group } = Astro.props;
+const { data } = Astro.props;
+const slug = Astro.params.time;
 ---
 
 <GridLayout>
-  <div class="flex flex-col gap-4">
-    <TimeArchives group={group} />
+  <div class="card card-bordered border-base-300 border-2 px-6 py-4">
+    {
+      (() => {
+        function renderMonth(year: number, month: number) {
+          let monthData = data
+            .find((d) => d.year === year)
+            ?.data.find((d) => d.month === month)?.data;
+          if (!monthData) {
+            return <p>SHOULD NOT RENDER THIS, IS A BUG</p>;
+          }
+          return (
+            <ul class="list">
+              {monthData.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: getTagUrl(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>
+                  <span class="text-base-content/60">
+                    {data.published.toLocaleDateString(siteConfig.lang)}
+                  </span>
+                </li>
+              ))}
+            </ul>
+          );
+        }
+
+        function renderYear(year: number) {
+          const yearData = data.find((d) => d.year === year)?.data;
+          if (!yearData) {
+            return <p>SHOULD NOT RENDER THIS, IS A BUG</p>;
+          }
+          return yearData.map(({ month }) => (
+            <>
+              <div class="divider mx-3 mt-8 text-xl font-bold">
+                <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>
+            </>
+          ));
+        }
+
+        function renderAll() {
+          return data.map(({ year }) => (
+            <>
+              <div class="divider mt-12 text-2xl font-bold" id={`${year}`}>
+                <a
+                  href={`/archives/${year}`}
+                  title={`${year}`}
+                  class="hover:text-primary duration-200"
+                >
+                  {year}
+                </a>
+              </div>
+              <div class="px-4">{renderYear(year)}</div>
+            </>
+          ));
+        }
+
+        let archiveNav;
+        if (slug) {
+          const [year, month] = slug.split('/').map(Number);
+          if (month) {
+            archiveNav = (
+              <div class="breadcrumbs text-xl">
+                <ul>
+                  <li>
+                    <a href="/archives">{i18n(I18nKey.archive)}</a>
+                  </li>
+                  <li>
+                    <a href={`/archives/${year}`}>{year}</a>
+                  </li>
+                  <li>
+                    <a href={`/archives/${year}/${month}`}>{month}</a>
+                  </li>
+                </ul>
+              </div>
+            );
+            return (
+              <>
+                <>{archiveNav}</>
+                <>{renderMonth(year, month)}</>
+              </>
+            );
+          }
+          archiveNav = (
+            <div class="breadcrumbs text-xl">
+              <ul>
+                <li>
+                  <a href="/archives">{i18n(I18nKey.archive)}</a>
+                </li>
+                <li>
+                  <a href={`/archives/${year}`}>{year}</a>
+                </li>
+              </ul>
+            </div>
+          );
+          return (
+            <>
+              <>{archiveNav}</>
+              <>{renderYear(year)}</>
+            </>
+          );
+        } else {
+          archiveNav = <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.archive)}</h1>;
+          return (
+            <>
+              <>{archiveNav}</>
+              <>{renderAll()}</>
+            </>
+          );
+        }
+      })()
+    }
   </div>
   <Fragment slot="aside-fixed">
     <ProfileCard />
src/utils/content-utils.ts
@@ -18,6 +18,14 @@ export async function getSortedPosts(): Promise<{ body: string; data: BlogPostDa
   return sortedBlogPosts;
 }
 
+export async function getPostsCount(): Promise<number> {
+  const allBlogPosts = (await getCollection('posts')) as unknown as {
+    body: string;
+    data: BlogPostData;
+  }[];
+  return allBlogPosts.length;
+}
+
 export async function getCategories(): Promise<string[]> {
   const allBlogPosts = await getSortedPosts();
   const categories = [
@@ -34,37 +42,6 @@ export async function getTags(): Promise<string[]> {
   return tags;
 }
 
-export async function getTimeArchives() {
-  const allBlogPosts = await getSortedPosts();
-  const yearMap = new Map<number, Map<number, { body: string; data: BlogPostData }[]>>();
-  for (const post of allBlogPosts) {
-    const year = post.data.published.getFullYear();
-    const month = post.data.published.getMonth() + 1;
-    let monthMap = yearMap.get(year);
-    if (!monthMap) {
-      monthMap = new Map();
-      yearMap.set(year, monthMap);
-    }
-    let monthPosts = monthMap.get(month);
-    if (!monthPosts) {
-      monthPosts = [];
-      monthMap.set(month, monthPosts);
-    }
-    monthPosts.push(post);
-  }
-  return Array.from(yearMap.entries())
-    .map(([year, monthMap]) => ({
-      year,
-      months: Array.from(monthMap.entries())
-        .map(([month, posts]) => ({
-          month,
-          posts,
-        }))
-        .sort((a, b) => b.month - a.month),
-    }))
-    .sort((a, b) => b.year - a.year);
-}
-
 export function getCategoryUrl(category: string | undefined) {
   return category
     ? `/archives//categories/${category.replaceAll(/[\\/]/g, '-')}/1/`