@xingwangzhe/stalux 1.25.10 → 1.25.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/package.json +25 -14
  2. package/src/assets/cc-icon.svg +6 -3
  3. package/src/components/stalux/analytics/umami.astro +5 -2
  4. package/src/components/stalux/archives/archivesList.astro +29 -40
  5. package/src/components/stalux/archives/toArchive.astro +1 -1
  6. package/src/components/stalux/categories/categoriesCard.astro +7 -3
  7. package/src/components/stalux/categories/categoriesList.astro +4 -6
  8. package/src/components/stalux/common/author.astro +4 -11
  9. package/src/components/stalux/common/date.astro +2 -53
  10. package/src/components/stalux/common/search.astro +1 -1
  11. package/src/components/stalux/common/textType.astro +5 -8
  12. package/src/components/stalux/common/verCount.astro +1 -2
  13. package/src/components/stalux/footer/FooterBadges.astro +75 -97
  14. package/src/components/stalux/footer/FooterBeian.astro +37 -39
  15. package/src/components/stalux/footer/FooterCopyright.astro +12 -12
  16. package/src/components/stalux/footer/FooterStats.astro +21 -27
  17. package/src/components/stalux/footer/FooterThemeInfo.astro +23 -25
  18. package/src/components/stalux/layout/footer.astro +50 -46
  19. package/src/components/stalux/layout/head.astro +46 -287
  20. package/src/components/stalux/layout/listPage.astro +3 -3
  21. package/src/components/stalux/layout/navs.astro +14 -128
  22. package/src/components/stalux/links/linkCard.astro +4 -9
  23. package/src/components/stalux/links/linkList.astro +3 -5
  24. package/src/components/stalux/links/socialMedia.astro +21 -42
  25. package/src/components/stalux/posts/PhotoSwipeStyle.astro +2 -0
  26. package/src/components/stalux/posts/PostTaxonomy.astro +3 -5
  27. package/src/components/stalux/posts/WalineComment.astro +378 -478
  28. package/src/components/stalux/posts/postAuthor.astro +3 -5
  29. package/src/components/stalux/posts/postCC.astro +14 -16
  30. package/src/components/stalux/posts/postCard.astro +14 -25
  31. package/src/components/stalux/posts/postContent.astro +14 -152
  32. package/src/components/stalux/posts/postLeft.astro +13 -15
  33. package/src/components/stalux/posts/postList.astro +5 -7
  34. package/src/components/stalux/posts/postNavigation.astro +11 -6
  35. package/src/components/stalux/posts/postRight.astro +5 -4
  36. package/src/components/stalux/posts/postToc.astro +22 -23
  37. package/src/components/stalux/posts/randomPost.astro +42 -7
  38. package/src/components/stalux/tags/tagHeader.astro +6 -2
  39. package/src/components/stalux/tags/tagsCloud.astro +12 -68
  40. package/src/components/stalux/words/wordCard.astro +3 -7
  41. package/src/components/stalux/words/wordList.astro +4 -36
  42. package/src/expressive-code.ts +1 -0
  43. package/src/index.ts +33 -168
  44. package/src/internal/font-slices.ts +5 -3
  45. package/src/internal/injected-routes.ts +15 -0
  46. package/src/internal/override.ts +3 -2
  47. package/src/internal/satteri-config.ts +59 -0
  48. package/src/internal/version.ts +3 -0
  49. package/src/layouts/PostLayout.astro +20 -24
  50. package/src/layouts/Stalux.astro +74 -75
  51. package/src/pages/404.astro +51 -44
  52. package/src/pages/about.astro +10 -11
  53. package/src/pages/api/post.abbrlink.json.ts +1 -1
  54. package/src/pages/api/posts.json.ts +1 -1
  55. package/src/pages/archives.astro +9 -5
  56. package/src/pages/atom.xml.ts +5 -6
  57. package/src/pages/categories/[category].astro +5 -4
  58. package/src/pages/categories/[category].md.ts +1 -1
  59. package/src/pages/categories/index.astro +6 -7
  60. package/src/pages/index.astro +42 -31
  61. package/src/pages/links.astro +4 -3
  62. package/src/pages/llms-full.txt.ts +1 -1
  63. package/src/pages/llms.txt.ts +2 -3
  64. package/src/pages/posts/[post].astro +33 -17
  65. package/src/pages/posts/[post].md.ts +4 -4
  66. package/src/pages/rss.xml.ts +5 -6
  67. package/src/pages/tags/[tag].astro +5 -4
  68. package/src/pages/tags/[tag].md.ts +1 -1
  69. package/src/pages/tags/index.astro +6 -7
  70. package/src/pages/words.astro +5 -5
  71. package/src/plugins/feature-flags.ts +37 -16
  72. package/src/plugins/satteri-temml.ts +11 -9
  73. package/src/schemas/collections.ts +7 -15
  74. package/src/schemas/config.ts +3 -3
  75. package/src/scripts/background.ts +2 -0
  76. package/src/scripts/clarity.ts +8 -2
  77. package/src/scripts/core-runtime.ts +7 -0
  78. package/src/scripts/countdown.ts +6 -15
  79. package/src/scripts/date-time.ts +40 -0
  80. package/src/scripts/footer-badges.ts +18 -0
  81. package/src/scripts/google-analytics.ts +3 -2
  82. package/src/scripts/navigation.ts +110 -0
  83. package/src/scripts/page-runtime.ts +84 -0
  84. package/src/scripts/photoswipe.ts +99 -0
  85. package/src/scripts/random-posts.ts +47 -22
  86. package/src/scripts/runtime-counter.ts +15 -24
  87. package/src/scripts/tags-cloud.ts +59 -0
  88. package/src/scripts/toc-scrollspy.ts +8 -0
  89. package/src/scripts/vercount.ts +1 -0
  90. package/src/scripts/waline.ts +38 -0
  91. package/src/scripts/webmcp.ts +11 -11
  92. package/src/scripts/word-list.ts +39 -0
  93. package/src/styles/base/init.css +1 -6
  94. package/src/styles/base/math.css +3 -3
  95. package/src/styles/components/layout/navs.module.css +0 -1
  96. package/src/styles/components/links/socialLinks.module.css +46 -0
  97. package/src/styles/components/markdown.css +1 -6
  98. package/src/styles/components/tags/tagHeader.module.css +1 -1
  99. package/src/styles/components/tags/tagsCloud.module.css +48 -1
  100. package/src/utils/ai-discovery.ts +23 -26
  101. package/src/utils/badge-generator.ts +8 -11
  102. package/src/utils/cc.ts +13 -4
  103. package/src/utils/collections-stats.ts +15 -14
  104. package/src/utils/config-utils.ts +36 -54
  105. package/src/utils/content-index.ts +139 -0
  106. package/src/utils/dayjs.ts +4 -2
  107. package/src/utils/feed.ts +2 -2
  108. package/src/utils/public-routes.ts +54 -0
  109. package/src/utils/route-catalog.ts +36 -0
  110. package/src/utils/semantic-time.ts +1 -1
  111. package/src/utils/seo-document.ts +272 -0
  112. package/src/utils/taxonomy.ts +6 -30
  113. package/src/utils/toc-scrollspy.ts +36 -14
  114. package/src/utils/view-transitions.ts +3 -36
  115. package/src/utils/waline.ts +47 -24
  116. package/src/utils/word-count-utils.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.25.10",
3
+ "version": "1.25.12",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -44,6 +44,7 @@
44
44
  "!src/content.config.ts"
45
45
  ],
46
46
  "type": "module",
47
+ "packageManager": "bun@1.4.0",
47
48
  "exports": {
48
49
  ".": "./src/index.ts",
49
50
  "./config": "./src/config.ts",
@@ -63,15 +64,24 @@
63
64
  "./package.json": "./package.json"
64
65
  },
65
66
  "scripts": {
66
- "dev": "astro dev ",
67
- "build": "astro build ",
68
- "preview": "astro preview ",
67
+ "dev": "astro dev",
68
+ "build": "astro build && bun run verify:build",
69
+ "preview": "astro preview",
69
70
  "astro": "astro",
70
- "init": "node scripts/cli.mjs init",
71
- "fmt": "oxfmt",
72
- "fmt:check": "oxfmt --check",
73
- "lint": "oxlint",
74
- "lint:fix": "oxlint --fix",
71
+ "init": "bun scripts/cli.mjs init",
72
+ "check": "biome check .",
73
+ "check:fix": "biome check --write .",
74
+ "format": "biome format --write .",
75
+ "format:check": "biome format .",
76
+ "lint": "biome lint .",
77
+ "lint:fix": "biome lint --write .",
78
+ "astro:check": "astro check --minimumFailingSeverity warning",
79
+ "test": "vitest",
80
+ "test:run": "vitest run",
81
+ "test:coverage": "vitest run --coverage",
82
+ "knip": "knip",
83
+ "verify:build": "bun scripts/verify-build.mjs",
84
+ "validate": "bun run check && bun run astro:check && bun run knip && bun run test:coverage && bun run build",
75
85
  "prepare": "git config core.hooksPath scripts/git-hooks"
76
86
  },
77
87
  "dependencies": {
@@ -86,7 +96,6 @@
86
96
  "@xingwangzhe/satteri-mermaid": "0.7.7",
87
97
  "@xingwangzhe/satteri-photoswipe": "^0.2.4",
88
98
  "@xingwangzhe/tags-cloud": "^1.2.7",
89
- "animejs": "^4.5.0",
90
99
  "astro-expressive-code": "^0.44.1",
91
100
  "astro-seo": "^1.1.0",
92
101
  "astro-typewriter": "^1.2.0",
@@ -99,12 +108,14 @@
99
108
  "temml": "^0.13.4"
100
109
  },
101
110
  "devDependencies": {
111
+ "@astrojs/check": "0.9.10",
112
+ "@biomejs/biome": "2.5.10",
113
+ "@vitest/coverage-v8": "4.1.11",
102
114
  "astro": "^7.2.4",
103
115
  "knip": "^6.32.2",
104
- "oxfmt": "^0.64.0",
105
- "oxlint": "^1.79.0",
106
- "typescript": "^7.0.2",
107
- "vite": "8.2.2"
116
+ "typescript": "npm:@typescript/typescript6@6.0.2",
117
+ "vite": "8.2.2",
118
+ "vitest": "4.1.11"
108
119
  },
109
120
  "peerDependencies": {
110
121
  "astro": "7.2.4"
@@ -1,4 +1,7 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
2
- <path fill="#cccccc"
3
- d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.408 5.89c-.83 0-1.318.64-1.318 1.753v.742c0 1.108.479 1.727 1.318 1.727c.69 0 1.138-.435 1.187-1.05h1.147v.114c-.058 1.147-1.029 1.938-2.343 1.938c-1.612 0-2.518-1.028-2.518-2.729v-.747c0-1.7.914-2.75 2.518-2.75c1.319 0 2.29.812 2.343 1.999v.11H6.595c-.049-.638-.506-1.108-1.187-1.108Zm5.404 0c-.831 0-1.319.64-1.319 1.753v.742c0 1.108.48 1.727 1.319 1.727c.69 0 1.138-.435 1.186-1.05h1.147v.114c-.057 1.147-1.028 1.938-2.342 1.938c-1.613 0-2.518-1.028-2.518-2.729v-.747c0-1.7.914-2.75 2.518-2.75c1.318 0 2.29.812 2.342 1.999v.11h-1.147c-.048-.638-.505-1.108-1.186-1.108Z" />
4
- </svg>
2
+ <title>Creative Commons</title>
3
+ <path
4
+ fill="#cccccc"
5
+ d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.408 5.89c-.83 0-1.318.64-1.318 1.753v.742c0 1.108.479 1.727 1.318 1.727c.69 0 1.138-.435 1.187-1.05h1.147v.114c-.058 1.147-1.029 1.938-2.343 1.938c-1.612 0-2.518-1.028-2.518-2.729v-.747c0-1.7.914-2.75 2.518-2.75c1.319 0 2.29.812 2.343 1.999v.11H6.595c-.049-.638-.506-1.108-1.187-1.108Zm5.404 0c-.831 0-1.319.64-1.319 1.753v.742c0 1.108.48 1.727 1.319 1.727c.69 0 1.138-.435 1.186-1.05h1.147v.114c-.057 1.147-1.028 1.938-2.342 1.938c-1.613 0-2.518-1.028-2.518-2.729v-.747c0-1.7.914-2.75 2.518-2.75c1.318 0 2.29.812 2.342 1.999v.11h-1.147c-.048-.638-.505-1.108-1.186-1.108Z"
6
+ />
7
+ </svg>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import { getCollection } from "astro:content";
3
- import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
3
+ import { getHeadData } from "@utils/config-utils";
4
+
4
5
  const configEntries = await getCollection("config");
5
6
  const headConfig = getHeadData(configEntries);
6
7
  const umamiConfig = headConfig?.umami;
@@ -9,8 +10,10 @@ const umamiURL = umamiConfig?.url;
9
10
  ---
10
11
 
11
12
  <script
13
+ is:inline
12
14
  defer
13
15
  src={umamiURL}
14
16
  data-website-id={umamiID}
15
17
  type="text/javascript"
16
- data-astro-rerun></script>
18
+ transition:persist="stalux-umami"
19
+ ></script>
@@ -1,9 +1,9 @@
1
1
  ---
2
- import { getCollection, type CollectionEntry } from "astro:content";
2
+ import { type CollectionEntry, getCollection } from "astro:content";
3
+ import styles from "@styles/components/archives/archivesList.module.css";
3
4
  import { getSiteData } from "@utils/config-utils";
5
+ import { format, parseDate, toTimestamp } from "@utils/dayjs";
4
6
  import { createTranslator } from "@utils/i18n";
5
- import styles from "@styles/components/archives/archivesList.module.css";
6
- import { toTimestamp, parseDate, format } from "@utils/dayjs";
7
7
 
8
8
  interface Props {
9
9
  posts?: CollectionEntry<"posts">[];
@@ -15,26 +15,18 @@ const posts = props.posts ?? (await getCollection("posts", ({ data }) => !data.d
15
15
  const configEntries = await getCollection("config");
16
16
  const siteConfig = getSiteData(configEntries);
17
17
  const { t } = createTranslator(siteConfig.lang || "zh-CN");
18
- const archivesByYearMonth: Record<
19
- number,
20
- Record<number, CollectionEntry<"posts">[]>
21
- > = {};
22
- const yearCounts: Record<number, number> = {};
18
+ const archivesByYearMonth = new Map<number, Map<number, CollectionEntry<"posts">[]>>();
23
19
 
24
20
  posts.forEach((post) => {
25
21
  if (!post.data.date) return;
26
22
  const date = parseDate(post.data.date);
27
23
  const year = date.getUTCFullYear();
28
24
  const month = date.getUTCMonth() + 1;
29
- if (!archivesByYearMonth[year]) {
30
- archivesByYearMonth[year] = {};
31
- yearCounts[year] = 0;
32
- }
33
- if (!archivesByYearMonth[year][month]) {
34
- archivesByYearMonth[year][month] = [];
35
- }
36
- archivesByYearMonth[year][month].push(post);
37
- yearCounts[year] += 1;
25
+ const months = archivesByYearMonth.get(year) ?? new Map();
26
+ const monthPosts = months.get(month) ?? [];
27
+ monthPosts.push(post);
28
+ months.set(month, monthPosts);
29
+ archivesByYearMonth.set(year, months);
38
30
  });
39
31
 
40
32
  // 每个 post 只解析一次时间,供排序比较器复用,避免比较器里反复 new Date()
@@ -43,46 +35,45 @@ const tsCache = new Map<CollectionEntry<"posts">, number>(
43
35
  );
44
36
 
45
37
  // 对每个月内的文章按 date 字段降序排序(用缓存时间戳做纯数字比较)
46
- for (const [, months] of Object.entries(archivesByYearMonth)) {
47
- for (const month of Object.keys(months).map(Number)) {
48
- months[month].sort((a, b) => (tsCache.get(b) ?? 0) - (tsCache.get(a) ?? 0));
38
+ for (const months of archivesByYearMonth.values()) {
39
+ for (const monthPosts of months.values()) {
40
+ monthPosts.sort((a, b) => (tsCache.get(b) ?? 0) - (tsCache.get(a) ?? 0));
49
41
  }
50
42
  }
51
43
 
52
- // 按年份降序排序 -
53
- const sortedYears = Object.keys(archivesByYearMonth)
54
- .map(Number)
55
- .sort((a: number, b: number) => b - a);
44
+ const archiveYears = [...archivesByYearMonth.entries()]
45
+ .sort(([left], [right]) => right - left)
46
+ .map(([year, months]) => ({
47
+ year,
48
+ count: [...months.values()].reduce((total, monthPosts) => total + monthPosts.length, 0),
49
+ months: [...months.entries()]
50
+ .sort(([left], [right]) => right - left)
51
+ .map(([month, monthPosts]) => ({ month, posts: monthPosts })),
52
+ }));
56
53
  ---
57
54
 
58
55
  <section class={styles.archivesContainer}>
59
56
  <div class={styles.pageHeader}>
60
57
  <div class={styles.timelineContainer}>
61
- {
62
- sortedYears.length > 0 ? (
63
- sortedYears.map((year) => (
58
+ {archiveYears.length > 0 ? (
59
+ archiveYears.map((archiveYear) => (
64
60
  <div
65
61
  class={styles.yearSection}
66
- data-year={String(year)}
62
+ data-year={String(archiveYear.year)}
67
63
  >
68
64
  <div class={styles.yearHeader} data-year-header style="opacity:0">
69
- <h2 class={styles.yearTitle}>{year}</h2>
65
+ <h2 class={styles.yearTitle}>{archiveYear.year}</h2>
70
66
  <div class={styles.yearCount}>
71
- {t("archives.postCount", { n: yearCounts[year] ?? 0 })}
67
+ {t("archives.postCount", { n: archiveYear.count })}
72
68
  </div>
73
69
  </div>
74
- {Object.keys(archivesByYearMonth[year])
75
- .map(Number)
76
- .sort((a, b) => b - a)
77
- .map((month) => (
70
+ {archiveYear.months.map(({ month, posts: monthPosts }) => (
78
71
  <div class={styles.monthSection} data-month-section style="opacity:0">
79
72
  <h3 class={styles.monthTitle}>
80
73
  {t("archives.month", { n: month })}
81
74
  </h3>
82
75
  <ul class={styles.postList}>
83
- {archivesByYearMonth[year][
84
- month
85
- ].map((post) => (
76
+ {monthPosts.map((post) => (
86
77
  <li class={styles.postItem} data-post-item style="opacity:0">
87
78
  <time
88
79
  class={styles.postDate}
@@ -106,11 +97,9 @@ const sortedYears = Object.keys(archivesByYearMonth)
106
97
  ))
107
98
  ) : (
108
99
  <div class={styles.noPosts}>{t("archives.noPosts")}</div>
109
- )
110
- }
100
+ )}
111
101
  </div>
112
102
 
113
103
  <slot name="main" />
114
104
  </div>
115
-
116
105
  </section>
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  import { getCollection } from "astro:content";
3
+ import styles from "@styles/components/archives/toArchive.module.css";
3
4
  import { getSiteData } from "@utils/config-utils";
4
5
  import { createTranslator } from "@utils/i18n";
5
- import styles from "@styles/components/archives/toArchive.module.css";
6
6
 
7
7
  const configEntries = await getCollection("config");
8
8
  const siteConfig = getSiteData(configEntries);
@@ -1,6 +1,6 @@
1
1
  ---
2
- import styles from "@styles/components/categories/categoriesCard.module.css";
3
2
  import { getCollection } from "astro:content";
3
+ import styles from "@styles/components/categories/categoriesCard.module.css";
4
4
  import { createTranslator } from "@utils/i18n";
5
5
 
6
6
  interface Props {
@@ -12,18 +12,22 @@ interface Props {
12
12
 
13
13
  const props = Astro.props as Props;
14
14
  const configEntries = await getCollection("config");
15
- const siteConfig = (configEntries.find(e => e.id === "site")?.data ?? {}) as Record<string, unknown>;
15
+ const siteConfig = (configEntries.find((e) => e.id === "site")?.data ?? {}) as Record<
16
+ string,
17
+ unknown
18
+ >;
16
19
  const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
17
20
  ---
18
21
 
19
22
  <article class={styles.categoryCard}>
20
23
  <a
21
24
  href={`/categories/${props.category.name}/`}
22
- class={styles.categoryheader + " a-none"}
25
+ class={`${styles.categoryheader} a-none`}
23
26
  data-astro-prefetch
24
27
  >
25
28
  <div class={styles.categoryIcon}>
26
29
  <svg
30
+ aria-hidden="true"
27
31
  xmlns="http://www.w3.org/2000/svg"
28
32
  viewBox="0 0 24 24"
29
33
  width="24"
@@ -1,17 +1,15 @@
1
1
  ---
2
- import { getCategoryCountList } from "@utils/collections-stats";
3
- import styles from "@styles/components/categories/categoriesList.module.css";
4
2
  import CategoriesCard from "@components/stalux/categories/categoriesCard.astro";
3
+ import styles from "@styles/components/categories/categoriesList.module.css";
4
+ import { getCategoryCountList } from "@utils/collections-stats";
5
5
 
6
6
  const categoryList = await getCategoryCountList();
7
7
  ---
8
8
 
9
9
  <ul class={`${styles.categoriesList} stagger-children`}>
10
- {
11
- categoryList.map((category) => (
10
+ {categoryList.map((category) => (
12
11
  <li class={styles.item} style="opacity:0">
13
12
  <CategoriesCard category={category} />
14
13
  </li>
15
- ))
16
- }
14
+ ))}
17
15
  </ul>
@@ -1,9 +1,9 @@
1
1
  ---
2
- // @ts-expect-error
3
2
  import { Image } from "astro:assets";
4
3
  import { getCollection } from "astro:content";
5
4
  import styles from "@styles/components/common/author.module.css";
6
- import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
5
+ import { getAuthorData } from "@utils/config-utils";
6
+
7
7
  const configEntries = await getCollection("config");
8
8
  const authorConfig = getAuthorData(configEntries);
9
9
  interface Props {
@@ -18,16 +18,9 @@ const navigationHref = isHomePage ? "/about" : "/";
18
18
  ---
19
19
 
20
20
  <article class={styles.author} transition:persist-props>
21
- <figure
22
- class={`${styles.authorFigure} ${props.iscolum ? styles.figureColumn : ""}`}
23
- >
21
+ <figure class={`${styles.authorFigure} ${props.iscolum ? styles.figureColumn : ""}`}>
24
22
  <a href={navigationHref} class="a-none" data-astro-prefetch>
25
- <Image
26
- src={authorConfig.avatar}
27
- alt={authorConfig.name}
28
- width={100}
29
- height={100}
30
- />
23
+ <Image src={authorConfig.avatar} alt={authorConfig.name} width={100} height={100} />
31
24
  </a>
32
25
  <figcaption>{authorConfig.name}</figcaption>
33
26
  {props.showBio && <p>{authorConfig.bio}</p>}
@@ -3,57 +3,6 @@ import styles from "@styles/components/common/date.module.css";
3
3
  ---
4
4
 
5
5
  <div id={styles["date-time"]} class="date-time" transition:persist-props>
6
- <time id={styles["date"]} data-ref="date" datetime=""></time>
7
- <time id={styles["time"]} data-ref="time" datetime=""></time>
6
+ <time id={styles.date} data-ref="date" datetime=""></time>
7
+ <time id={styles.time} data-ref="time" datetime=""></time>
8
8
  </div>
9
-
10
- <script>
11
- const pad2 = (n: number) => String(n).padStart(2, "0");
12
-
13
- /** 机器可读:yyyy-MM-dd / HH:mm:ss(本地时区) */
14
- function toMachineDate(d: Date): string {
15
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
16
- }
17
- function toMachineTime(d: Date): string {
18
- return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
19
- }
20
-
21
- /** 展示用日期:中文 2026年07月16日 / 英文 Jul 16, 2026 */
22
- function toDisplayDate(d: Date, lang: string): string {
23
- if (lang.startsWith("zh")) {
24
- return `${d.getFullYear()}年${pad2(d.getMonth() + 1)}月${pad2(d.getDate())}日`;
25
- }
26
- return new Intl.DateTimeFormat("en-US", {
27
- year: "numeric",
28
- month: "short",
29
- day: "2-digit",
30
- }).format(d);
31
- }
32
-
33
- function updateDateTime() {
34
- const now = new Date();
35
- const dateEl = document.querySelector('[data-ref="date"]');
36
- const timeEl = document.querySelector('[data-ref="time"]');
37
- const lang = document.documentElement.lang || "zh-CN";
38
-
39
- if (dateEl) {
40
- dateEl.textContent = toDisplayDate(now, lang);
41
- dateEl.setAttribute("datetime", toMachineDate(now));
42
- }
43
- if (timeEl) {
44
- const t = toMachineTime(now);
45
- timeEl.textContent = t;
46
- timeEl.setAttribute("datetime", t);
47
- }
48
- }
49
-
50
- // 全局只保留一个 1Hz 定时器:用单例守卫避免视图切换时累加定时器。
51
- let timerId: ReturnType<typeof setInterval> | null = null;
52
-
53
- document.addEventListener("astro:page-load", () => {
54
- updateDateTime();
55
- if (timerId === null) {
56
- timerId = setInterval(updateDateTime, 1000);
57
- }
58
- });
59
- </script>
@@ -5,5 +5,5 @@
5
5
  ---
6
6
 
7
7
  <div data-pf-theme="dark">
8
- <pagefind-modal id="search"></pagefind-modal>
8
+ <pagefind-modal id="search"></pagefind-modal>
9
9
  </div>
@@ -1,16 +1,13 @@
1
1
  ---
2
- // @ts-expect-error
3
- import Typewriter from "astro-typewriter";
4
2
  import { getCollection } from "astro:content";
5
3
  import styles from "@styles/components/common/textType.module.css";
6
- import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
4
+ import { getTypetextsData } from "@utils/config-utils";
5
+ import Typewriter from "astro-typewriter";
6
+
7
7
  const configEntries = await getCollection("config");
8
- const siteConfig = getSiteData(configEntries);
9
8
  const typetextsConfig = getTypetextsData(configEntries);
10
9
  ---
11
10
 
12
- {
13
- typetextsConfig?.items && (
11
+ {typetextsConfig?.items && (
14
12
  <Typewriter class={styles.typeWriter} messages={typetextsConfig?.items} />
15
- )
16
- }
13
+ )}
@@ -1,5 +1,4 @@
1
1
  ---
2
-
3
2
  ---
4
3
 
5
- <script src="../../../scripts/vercount.ts"></script>
4
+ <Fragment />
@@ -1,9 +1,9 @@
1
1
  ---
2
- import type { BadgeItem } from "@utils/badge-generator";
3
- import { generateBadge, svgToDataUrl, isBadgeGroup } from "@utils/badge-generator";
4
- import { createTranslator } from "@utils/i18n";
5
2
  import { getCollection } from "astro:content";
3
+ import type { BadgeItem, BadgeOptions } from "@utils/badge-generator";
4
+ import { generateBadge, isBadgeGroup, svgToDataUrl } from "@utils/badge-generator";
6
5
  import { getSiteData } from "@utils/config-utils";
6
+ import { createTranslator } from "@utils/i18n";
7
7
 
8
8
  interface Props {
9
9
  badges?: BadgeItem[];
@@ -14,7 +14,7 @@ const configEntries = await getCollection("config");
14
14
  const siteConfig = getSiteData(configEntries);
15
15
  const { t } = createTranslator(siteConfig.lang || "zh-CN");
16
16
 
17
- function badgeImg(opts: Record<string, any>) {
17
+ function badgeImg(opts: BadgeOptions) {
18
18
  const src = svgToDataUrl(generateBadge(opts));
19
19
  const alt = opts.alt || `${opts.label}: ${opts.message}`;
20
20
  if (opts.href) {
@@ -24,8 +24,7 @@ function badgeImg(opts: Record<string, any>) {
24
24
  }
25
25
  ---
26
26
 
27
- {
28
- badges && badges.length > 0 && (
27
+ {badges && badges.length > 0 && (
29
28
  <div class="badges-wrapper">
30
29
  {badges.map((item: BadgeItem, idx: number) => {
31
30
  if (isBadgeGroup(item)) {
@@ -39,7 +38,7 @@ function badgeImg(opts: Record<string, any>) {
39
38
  <span class="badge-group-hint" data-hint="collapsed">{t("footer.expand")}</span>
40
39
  </summary>
41
40
  <div class="badge-group-items badges">
42
- {item.items.map((b: any) => (
41
+ {item.items.map((b) => (
43
42
  <Fragment set:html={badgeImg(b)} />
44
43
  ))}
45
44
  </div>
@@ -49,108 +48,87 @@ function badgeImg(opts: Record<string, any>) {
49
48
  return <Fragment set:html={badgeImg(item)} />;
50
49
  })}
51
50
  </div>
52
- )
51
+ )}
52
+
53
+ <style>
54
+ .badges-wrapper {
55
+ display: flex;
56
+ justify-content: center;
57
+ align-items: center;
58
+ flex-wrap: wrap;
59
+ gap: 0.5rem;
53
60
  }
54
61
 
55
- <script>
56
- function initBadgeGroups() {
57
- document.querySelectorAll<HTMLDetailsElement>(".badge-group").forEach((el) => {
58
- const key = `badge-${el.id}`;
59
- const saved = localStorage.getItem(key);
60
- if (saved !== null) {
61
- el.open = saved === "open";
62
- } else {
63
- // 首次访问使用默认折叠状态
64
- el.open = el.getAttribute("data-default-open") === "true";
65
- }
66
- el.addEventListener("toggle", () => {
67
- localStorage.setItem(key, el.open ? "open" : "closed");
68
- });
69
- });
70
- }
71
- document.addEventListener("astro:page-load", initBadgeGroups);
72
- initBadgeGroups();
73
- </script>
62
+ .badge-group {
63
+ width: 100%;
64
+ border: none;
65
+ }
74
66
 
75
- <style>
76
- .badges-wrapper {
77
- display: flex;
78
- justify-content: center;
79
- align-items: center;
80
- flex-wrap: wrap;
81
- gap: 0.5rem;
82
- }
67
+ .badge-group-summary {
68
+ cursor: pointer;
69
+ display: flex;
70
+ justify-content: center;
71
+ align-items: center;
72
+ gap: 0.4rem;
73
+ padding: 0.2rem 0;
74
+ color: var(--white-70p);
75
+ font-size: 0.85rem;
76
+ user-select: none;
77
+ list-style: none;
78
+ transition: color 0.2s;
79
+ }
80
+ .badge-group-summary:hover {
81
+ color: var(--white-70p);
82
+ }
83
+ .badge-group-summary::-webkit-details-marker {
84
+ display: none;
85
+ }
83
86
 
84
- .badge-group {
85
- width: 100%;
86
- border: none;
87
- }
87
+ .badge-group-arrow {
88
+ font-size: 0.65rem;
89
+ transition: transform 0.2s;
90
+ display: inline-block;
91
+ }
92
+ .badge-group[open] .badge-group-arrow {
93
+ transform: rotate(90deg);
94
+ }
88
95
 
89
- .badge-group-summary {
90
- cursor: pointer;
91
- display: flex;
92
- justify-content: center;
93
- align-items: center;
94
- gap: 0.4rem;
95
- padding: 0.2rem 0;
96
- color: var(--white-70p);
97
- font-size: 0.85rem;
98
- user-select: none;
99
- list-style: none;
100
- transition: color 0.2s;
101
- }
102
- .badge-group-summary:hover {
103
- color: var(--white-70p);
104
- }
105
- .badge-group-summary::-webkit-details-marker {
106
- display: none;
107
- }
96
+ .badge-group-title {
97
+ color: var(--white-80p);
98
+ }
108
99
 
109
- .badge-group-arrow {
110
- font-size: 0.65rem;
111
- transition: transform 0.2s;
112
- display: inline-block;
113
- }
114
- .badge-group[open] .badge-group-arrow {
115
- transform: rotate(90deg);
116
- }
100
+ .badge-group-hint {
101
+ font-size: 0.7rem;
102
+ color: var(--white-70p);
103
+ }
104
+ .badge-group[open] .badge-group-hint[data-hint="collapsed"],
105
+ .badge-group:not([open]) .badge-group-hint[data-hint="expanded"] {
106
+ display: none;
107
+ }
117
108
 
118
- .badge-group-title {
119
- color: var(--white-80p);
120
- }
109
+ .badge-group-items {
110
+ padding-top: 0.35rem;
111
+ }
121
112
 
122
- .badge-group-hint {
123
- font-size: 0.7rem;
124
- color: var(--white-70p);
125
- }
126
- .badge-group[open] .badge-group-hint[data-hint="collapsed"],
127
- .badge-group:not([open]) .badge-group-hint[data-hint="expanded"] {
128
- display: none;
129
- }
113
+ .badges {
114
+ display: flex;
115
+ justify-content: center;
116
+ align-items: center;
117
+ flex-wrap: wrap;
118
+ gap: 0.5rem;
119
+ }
130
120
 
131
- .badge-group-items {
132
- padding-top: 0.35rem;
133
- }
121
+ .badge {
122
+ height: 20px;
123
+ vertical-align: middle;
124
+ }
134
125
 
126
+ @media (max-width: 768px) {
135
127
  .badges {
136
- display: flex;
137
- justify-content: center;
138
- align-items: center;
139
- flex-wrap: wrap;
140
- gap: 0.5rem;
128
+ gap: 0.3rem;
141
129
  }
142
-
143
130
  .badge {
144
- height: 20px;
145
- vertical-align: middle;
146
- }
147
-
148
- @media (max-width: 768px) {
149
- .badges {
150
- gap: 0.3rem;
151
- }
152
- .badge {
153
- height: 18px;
154
- }
131
+ height: 18px;
155
132
  }
133
+ }
156
134
  </style>