| ---
|
| import { Icon } from "astro-icon/components";
|
| import WidgetLayout from "@/components/common/WidgetLayout.astro";
|
| import { siteConfig } from "@/config";
|
| import I18nKey from "@/i18n/i18nKey";
|
| import { i18n } from "@/i18n/translation";
|
| import {
|
| getCategoryList,
|
| getSortedPosts,
|
| getTagList,
|
| } from "@/utils/content-utils";
|
|
|
| interface Props {
|
| class?: string;
|
| style?: string;
|
| }
|
|
|
| const { class: className, style } = Astro.props;
|
|
|
|
|
| const siteStartDate = siteConfig.siteStartDate || "2025-01-01";
|
|
|
|
|
| const posts = await getSortedPosts();
|
| const categories = await getCategoryList();
|
| const tags = await getTagList();
|
|
|
|
|
| let totalWords = 0;
|
| for (const post of posts) {
|
| if (post.body) {
|
|
|
| let text = post.body
|
| .replace(/```[\s\S]*?```/g, "")
|
| .replace(/`[^`]*`/g, "")
|
| .replace(/\s+/g, " ")
|
| .trim();
|
|
|
|
|
| const chineseChars = text.match(/[\u4e00-\u9fa5]/g) || [];
|
| const englishChars = text.match(/[a-zA-Z]/g) || [];
|
|
|
| totalWords += chineseChars.length + englishChars.length;
|
| }
|
| }
|
|
|
|
|
| function formatNumber(num: number): string {
|
| return num.toLocaleString();
|
| }
|
|
|
|
|
| const latestPost = posts.reduce((latest, post) => {
|
| if (!latest) return post;
|
| return post.data.published > latest.data.published ? post : latest;
|
| }, posts[0]);
|
|
|
| const lastPostDate = latestPost
|
| ? latestPost.data.published.toISOString()
|
| : null;
|
|
|
| const todayText = i18n(I18nKey.today);
|
|
|
| const stats = [
|
| {
|
| icon: "material-symbols:article-outline",
|
| label: i18n(I18nKey.siteStatsPostCount),
|
| value: posts.length,
|
| },
|
| {
|
| icon: "material-symbols:folder-outline",
|
| label: i18n(I18nKey.siteStatsCategoryCount),
|
| value: categories.length,
|
| },
|
| {
|
| icon: "material-symbols:label-outline",
|
| label: i18n(I18nKey.siteStatsTagCount),
|
| value: tags.length,
|
| },
|
| {
|
| icon: "material-symbols:text-ad-outline-rounded",
|
| label: i18n(I18nKey.siteStatsTotalWords),
|
| value: totalWords,
|
| formatted: true,
|
| },
|
| {
|
| icon: "material-symbols:calendar-clock-outline",
|
| label: i18n(I18nKey.siteStatsRunningDays),
|
| value: 0,
|
| suffix: i18n(I18nKey.siteStatsDays).replace("{days}", ""),
|
| dynamic: true,
|
| id: "running-days",
|
| },
|
| {
|
| icon: "material-symbols:ecg-heart-outline",
|
| label: i18n(I18nKey.siteStatsLastUpdate),
|
| value: 0,
|
| suffix: i18n(I18nKey.siteStatsDaysAgo).replace("{days}", ""),
|
| dynamic: true,
|
| id: "last-update",
|
| },
|
| ];
|
| ---
|
|
|
| <WidgetLayout name={i18n(I18nKey.siteStats)} id="site-stats" class={className} style={style}>
|
| <div class="flex flex-col gap-2">
|
| {stats.map((stat) => (
|
| <div class="flex items-center justify-between px-3 py-1.5">
|
| <div class="flex items-center gap-2.5">
|
| <div class="text-(--primary) text-xl">
|
| <Icon name={stat.icon} />
|
| </div>
|
| <span class="text-neutral-700 dark:text-neutral-300 font-medium text-sm">
|
| {stat.label}
|
| </span>
|
| </div>
|
| <div class="flex items-center">
|
| <span
|
| class="text-base font-bold text-neutral-900 dark:text-neutral-100"
|
| data-stat-id={stat.id}
|
| >
|
| {stat.formatted ? formatNumber(stat.value) : stat.value}
|
| </span>
|
| {stat.suffix && (
|
| <span class="text-sm text-neutral-500 dark:text-neutral-400 ml-1">
|
| {stat.suffix}
|
| </span>
|
| )}
|
| </div>
|
| </div>
|
| ))}
|
| </div>
|
| </WidgetLayout>
|
|
|
| <script is:inline define:vars={{ siteStartDate, lastPostDate, todayText }}>
|
| function updateDynamicStats() {
|
| const today = new Date();
|
|
|
|
|
| const startDate = new Date(siteStartDate);
|
| const diffTime = Math.abs(today.getTime() - startDate.getTime());
|
| const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
| const runningDaysElements = document.querySelectorAll('[data-stat-id="running-days"]');
|
| runningDaysElements.forEach((element) => {
|
| element.textContent = diffDays.toString();
|
| });
|
|
|
|
|
| if (lastPostDate) {
|
| const lastPost = new Date(lastPostDate);
|
| const timeSinceLastPost = Math.abs(today.getTime() - lastPost.getTime());
|
| const daysSinceLastUpdate = Math.floor(timeSinceLastPost / (1000 * 60 * 60 * 24));
|
|
|
| const lastUpdateElements = document.querySelectorAll('[data-stat-id="last-update"]');
|
| lastUpdateElements.forEach((element) => {
|
| if (daysSinceLastUpdate === 0) {
|
| element.textContent = todayText;
|
| if (element.nextElementSibling) {
|
| element.nextElementSibling.style.display = 'none';
|
| }
|
| } else {
|
| element.textContent = daysSinceLastUpdate.toString();
|
| if (element.nextElementSibling) {
|
| element.nextElementSibling.style.display = '';
|
| }
|
| }
|
| });
|
| }
|
| }
|
|
|
|
|
| updateDynamicStats();
|
|
|
|
|
| document.addEventListener("swup:contentReplaced", () => {
|
| setTimeout(updateDynamicStats, 100);
|
| });
|
| </script>
|
|
|