Commit 541cdcb

HPCesia <me@hpcesia.com>
2025-08-13 07:07:56
feat: enhance meta icons
1 parent 0f64aa0
Changed files (7)
packages
i18n
src
packages/i18n/src/en/web/index.ts
@@ -52,6 +52,8 @@ const en_web = {
     category: 'Category',
     unTagged: 'No Tags',
     unCategorized: 'Uncategorized',
+    wordsCount: 'Words Count',
+    readingTime: 'Reading Time',
   },
   info: {
     toc: 'Table of Content',
packages/i18n/src/zh-CN/web/index.ts
@@ -52,6 +52,8 @@ const zh_CN_web = {
     category: '分类',
     unTagged: '无标签',
     unCategorized: '未分类',
+    wordsCount: '字数统计',
+    readingTime: '阅读时间',
   },
   info: {
     toc: '目录',
packages/i18n/src/zh-TW/web/index.ts
@@ -52,6 +52,8 @@ const zh_TW_web = {
     category: '分類',
     unTagged: '無標籤',
     unCategorized: '未分類',
+    wordsCount: '字數統計',
+    readingTime: '閱讀時間',
   },
   info: {
     toc: '目錄',
packages/i18n/src/i18n-types.ts
@@ -344,6 +344,14 @@ export type NamespaceWebTranslation = {
 		 * U​n​c​a​t​e​g​o​r​i​z​e​d
 		 */
 		unCategorized: string
+		/**
+		 * W​o​r​d​s​ ​C​o​u​n​t
+		 */
+		wordsCount: string
+		/**
+		 * R​e​a​d​i​n​g​ ​T​i​m​e
+		 */
+		readingTime: string
 	}
 	info: {
 		/**
@@ -743,6 +751,14 @@ export type TranslationFunctions = {
 			 * Uncategorized
 			 */
 			unCategorized: () => LocalizedString
+			/**
+			 * Words Count
+			 */
+			wordsCount: () => LocalizedString
+			/**
+			 * Reading Time
+			 */
+			readingTime: () => LocalizedString
 		}
 		info: {
 			/**
src/components/misc/PostInfo.astro
@@ -28,38 +28,54 @@ const {
   ...rest
 } = Astro.props;
 
-const metas: ({ icon: string; text: string; link?: string; time?: Date } | undefined)[] = [
+interface MetaInfo {
+  text: string;
+  link?: string;
+  time?: Date;
+}
+
+const metas: (
+  | ({ icon: string; title?: string } & (MetaInfo | { group: MetaInfo[] }))
+  | undefined
+)[] = [
   publishedAt && {
     icon: 'material-symbols:calendar-clock-outline-rounded',
+    title: t.meta.publishedAt(),
     text: publishedAt.toLocaleDateString(lang || siteConfig.lang.replace('_', '-')),
     time: publishedAt,
   },
   articleConfig.wordCount && typeof wordCount === 'number'
     ? {
         icon: 'material-symbols:docs-rounded',
+        title: t.meta.wordsCount(),
         text: t.status.wordsCount(wordCount),
       }
     : undefined,
   articleConfig.readingTime && typeof readingTime === 'number'
     ? {
         icon: 'material-symbols:nest-clock-farsight-analog-rounded',
+        title: t.meta.readingTime(),
         text: t.status.readTime(readingTime),
       }
     : undefined,
   category
     ? {
         icon: 'material-symbols:category-outline-rounded',
+        title: t.meta.category(),
         text: category,
         link: getCategoryUrl(category),
       }
     : undefined,
-  ...(tags?.map((tag) => {
-    return {
-      icon: 'material-symbols:tag-rounded',
-      text: tag,
-      link: getTagUrl(tag),
-    };
-  }) || []),
+  tags && tags.length > 0
+    ? {
+        icon: 'material-symbols:tag-rounded',
+        title: t.meta.tags(),
+        group: tags.map((tag) => ({
+          text: tag,
+          link: getTagUrl(tag),
+        })),
+      }
+    : undefined,
 ];
 ---
 
@@ -71,21 +87,31 @@ const metas: ({ icon: string; text: string; link?: string; time?: Date } | undef
         {metas.map((meta) => {
           return (
             meta && (
-              <div class="text-base-content/60 flex items-center text-sm">
-                <MetaIcon name={meta.icon} />
+              <div class="flex flex-wrap items-center gap-1">
+                <MetaIcon name={meta.icon} title={meta.title} aria-label={meta.title} />
                 {(() => {
-                  const text = meta.time ? (
-                    <time datetime={meta.time?.toISOString()}>{meta.text}</time>
-                  ) : (
-                    <span>{meta.text}</span>
-                  );
-                  return meta.link ? (
-                    <a href={meta.link} title={meta.text}>
-                      {text}
-                    </a>
-                  ) : (
-                    text
-                  );
+                  const process = (info: MetaInfo) => {
+                    const text = info.time ? (
+                      <time datetime={info.time?.toISOString()}>{info.text}</time>
+                    ) : (
+                      <span>{info.text}</span>
+                    );
+                    return info.link ? (
+                      <a href={info.link} title={info.text} class="link-hover">
+                        {text}
+                      </a>
+                    ) : (
+                      text
+                    );
+                  };
+                  if ('group' in meta) {
+                    return meta.group
+                      .map(process)
+                      .flatMap((item, index, arr) =>
+                        index === arr.length - 1 ? [item] : [item, '/']
+                      );
+                  }
+                  return process(meta);
                 })()}
               </div>
             )
src/components/widgets/MetaIcon.astro
@@ -14,7 +14,6 @@ const { class: className, ...rest } = Astro.props;
     width: 1.5rem;
     height: 1.5rem;
     align-items: center;
-    margin-right: 0.5rem;
     display: flex;
     justify-content: center;
     color: color-mix(in oklab, var(--color-secondary) 80%, transparent);
src/components/widgets/PostCard.astro
@@ -1,6 +1,7 @@
 ---
 import { siteConfig } from '@/config';
 import { getCategoryUrl, getTagUrl } from '@utils/content-utils';
+import { t } from '@utils/i18n';
 import type { ImageMetadata } from 'astro';
 import MetaIcon from './MetaIcon.astro';
 import PostCardCover from './PostCardCover.astro';
@@ -11,42 +12,51 @@ interface Props {
   title: string;
   url: string;
   published: Date;
-  updated?: Date;
   tags: string[];
   category?: string;
   cover?: string | ImageMetadata;
   description: string;
 }
 
-const { title, url, published, updated, tags, category, cover } = Astro.props;
+const { title, url, published, tags, category, cover } = Astro.props;
 const className = Astro.props.class;
 
 const hasCover = cover !== '' && cover !== undefined && cover !== null;
 
-const metas: ({ icon: string; text: string; link?: string; time?: Date } | undefined)[] = [
+interface MetaInfo {
+  text: string;
+  link?: string;
+  time?: Date;
+}
+
+const metas: (
+  | ({ icon: string; title?: string } & (MetaInfo | { group: MetaInfo[] }))
+  | undefined
+)[] = [
   {
     icon: 'material-symbols:calendar-clock-outline-rounded',
+    title: t.meta.publishedAt(),
     text: published.toLocaleDateString(siteConfig.lang.replace('_', '-')),
     time: published,
   },
-  updated && {
-    icon: 'material-symbols:edit-calendar-outline-rounded',
-    text: updated.toLocaleDateString(siteConfig.lang.replace('_', '-')),
-  },
   category
     ? {
         icon: 'material-symbols:category-outline-rounded',
+        title: t.meta.category(),
         text: category,
         link: getCategoryUrl(category),
       }
     : undefined,
-  ...tags.map((tag) => {
-    return {
-      icon: 'material-symbols:tag-rounded',
-      text: tag,
-      link: getTagUrl(tag),
-    };
-  }),
+  tags.length > 0
+    ? {
+        icon: 'material-symbols:tag-rounded',
+        title: t.meta.tags(),
+        group: tags.map((tag) => ({
+          text: tag,
+          link: getTagUrl(tag),
+        })),
+      }
+    : undefined,
 ];
 ---
 
@@ -58,26 +68,36 @@ const metas: ({ icon: string; text: string; link?: string; time?: Date } | undef
 >
   <div class="card-body">
     <a href={url} class="card-title">{title}</a>
-    <div class="text-base-content/60 mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-sm">
+    <div class="text-base-content/60 mb-3 flex flex-wrap items-center gap-3 text-sm">
       {
         metas.map((meta) => {
           return (
             meta && (
-              <div class="flex items-center gap-1">
-                <MetaIcon name={meta.icon} />
+              <div class="flex flex-wrap items-center gap-1">
+                <MetaIcon name={meta.icon} title={meta.title} aria-label={meta.title} />
                 {(() => {
-                  const text = meta.time ? (
-                    <time datetime={meta.time?.toISOString()}>{meta.text}</time>
-                  ) : (
-                    <span>{meta.text}</span>
-                  );
-                  return meta.link ? (
-                    <a href={meta.link} title={meta.text}>
-                      {text}
-                    </a>
-                  ) : (
-                    text
-                  );
+                  const process = (info: MetaInfo) => {
+                    const text = info.time ? (
+                      <time datetime={info.time?.toISOString()}>{info.text}</time>
+                    ) : (
+                      <span>{info.text}</span>
+                    );
+                    return info.link ? (
+                      <a href={info.link} title={info.text} class="link-hover">
+                        {text}
+                      </a>
+                    ) : (
+                      text
+                    );
+                  };
+                  if ('group' in meta) {
+                    return meta.group
+                      .map(process)
+                      .flatMap((item, index, arr) =>
+                        index === arr.length - 1 ? [item] : [item, '/']
+                      );
+                  }
+                  return process(meta);
                 })()}
               </div>
             )