@xingwangzhe/stalux 1.2.0

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 (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/package.json +108 -0
  4. package/scripts/cli.mjs +103 -0
  5. package/src/assets/BiCcCircle.svg +1 -0
  6. package/src/assets/astro.svg +1 -0
  7. package/src/assets/background/README.md +3 -0
  8. package/src/assets/background/pattern-1.min.svg +1 -0
  9. package/src/assets/background/pattern-10.min.svg +1 -0
  10. package/src/assets/background/pattern-11.min.svg +1 -0
  11. package/src/assets/background/pattern-12.min.svg +1 -0
  12. package/src/assets/background/pattern-13.min.svg +1 -0
  13. package/src/assets/background/pattern-14.min.svg +1 -0
  14. package/src/assets/background/pattern-15.min.svg +1 -0
  15. package/src/assets/background/pattern-16.min.svg +1 -0
  16. package/src/assets/background/pattern-17.min.svg +1 -0
  17. package/src/assets/background/pattern-18.min.svg +1 -0
  18. package/src/assets/background/pattern-19.min.svg +1 -0
  19. package/src/assets/background/pattern-2.min.svg +1 -0
  20. package/src/assets/background/pattern-20.min.svg +1 -0
  21. package/src/assets/background/pattern-21.min.svg +1 -0
  22. package/src/assets/background/pattern-22.min.svg +1 -0
  23. package/src/assets/background/pattern-23.min.svg +1 -0
  24. package/src/assets/background/pattern-24.min.svg +1 -0
  25. package/src/assets/background/pattern-25.min.svg +1 -0
  26. package/src/assets/background/pattern-26.min.svg +1 -0
  27. package/src/assets/background/pattern-27.min.svg +1 -0
  28. package/src/assets/background/pattern-28.min.svg +1 -0
  29. package/src/assets/background/pattern-29.min.svg +1 -0
  30. package/src/assets/background/pattern-3.min.svg +1 -0
  31. package/src/assets/background/pattern-30.min.svg +1 -0
  32. package/src/assets/background/pattern-31.min.svg +1 -0
  33. package/src/assets/background/pattern-32.min.svg +1 -0
  34. package/src/assets/background/pattern-33.min.svg +1 -0
  35. package/src/assets/background/pattern-34.min.svg +1 -0
  36. package/src/assets/background/pattern-35.min.svg +1 -0
  37. package/src/assets/background/pattern-36.min.svg +1 -0
  38. package/src/assets/background/pattern-37.min.svg +1 -0
  39. package/src/assets/background/pattern-38.min.svg +1 -0
  40. package/src/assets/background/pattern-39.min.svg +1 -0
  41. package/src/assets/background/pattern-4.min.svg +1 -0
  42. package/src/assets/background/pattern-40.min.svg +1 -0
  43. package/src/assets/background/pattern-41.min.svg +1 -0
  44. package/src/assets/background/pattern-42.min.svg +1 -0
  45. package/src/assets/background/pattern-5.min.svg +1 -0
  46. package/src/assets/background/pattern-6.min.svg +1 -0
  47. package/src/assets/background/pattern-7.min.svg +1 -0
  48. package/src/assets/background/pattern-8.min.svg +1 -0
  49. package/src/assets/background/pattern-9.min.svg +1 -0
  50. package/src/assets/background.svg +1 -0
  51. package/src/assets/cc-icon.svg +4 -0
  52. package/src/assets/fonts/GoogleSansCode-Italic.woff2 +0 -0
  53. package/src/assets/fonts/GoogleSansCode.woff2 +0 -0
  54. package/src/assets/fonts/LXGWWenKai-Regular.ttf +0 -0
  55. package/src/assets/fonts/LXGWWenKai-Regular.woff2 +0 -0
  56. package/src/components/stalux/analytics/clarity.astro +25 -0
  57. package/src/components/stalux/analytics/google.astro +31 -0
  58. package/src/components/stalux/analytics/umami.astro +16 -0
  59. package/src/components/stalux/archives/archivesList.astro +116 -0
  60. package/src/components/stalux/archives/toArchive.astro +14 -0
  61. package/src/components/stalux/categories/categoriesCard.astro +49 -0
  62. package/src/components/stalux/categories/categoriesList.astro +17 -0
  63. package/src/components/stalux/common/author.astro +35 -0
  64. package/src/components/stalux/common/date.astro +59 -0
  65. package/src/components/stalux/common/search.astro +14 -0
  66. package/src/components/stalux/common/textType.astro +16 -0
  67. package/src/components/stalux/common/verCount.astro +10 -0
  68. package/src/components/stalux/footer/FooterBadges.astro +150 -0
  69. package/src/components/stalux/footer/FooterBeian.astro +91 -0
  70. package/src/components/stalux/footer/FooterCopyright.astro +40 -0
  71. package/src/components/stalux/footer/FooterStats.astro +108 -0
  72. package/src/components/stalux/footer/FooterThemeInfo.astro +67 -0
  73. package/src/components/stalux/layout/footer.astro +87 -0
  74. package/src/components/stalux/layout/head.astro +194 -0
  75. package/src/components/stalux/layout/listPage.astro +26 -0
  76. package/src/components/stalux/layout/navs.astro +174 -0
  77. package/src/components/stalux/links/linkCard.astro +50 -0
  78. package/src/components/stalux/links/linkList.astro +20 -0
  79. package/src/components/stalux/links/socialMedia.astro +102 -0
  80. package/src/components/stalux/posts/PhotoSwipeStyle.astro +3 -0
  81. package/src/components/stalux/posts/PostTaxonomy.astro +65 -0
  82. package/src/components/stalux/posts/WalineComment.astro +609 -0
  83. package/src/components/stalux/posts/postAuthor.astro +23 -0
  84. package/src/components/stalux/posts/postCC.astro +52 -0
  85. package/src/components/stalux/posts/postCard.astro +83 -0
  86. package/src/components/stalux/posts/postContent.astro +174 -0
  87. package/src/components/stalux/posts/postLeft.astro +41 -0
  88. package/src/components/stalux/posts/postList.astro +25 -0
  89. package/src/components/stalux/posts/postNavigation.astro +137 -0
  90. package/src/components/stalux/posts/postRight.astro +33 -0
  91. package/src/components/stalux/posts/postToc.astro +88 -0
  92. package/src/components/stalux/posts/randomPost.astro +134 -0
  93. package/src/components/stalux/tags/tagHeader.astro +50 -0
  94. package/src/components/stalux/tags/tagsCloud.astro +100 -0
  95. package/src/components/stalux/words/wordCard.astro +62 -0
  96. package/src/components/stalux/words/wordList.astro +70 -0
  97. package/src/config.ts +58 -0
  98. package/src/i18n/en.json +193 -0
  99. package/src/i18n/zh-CN.json +193 -0
  100. package/src/index.ts +215 -0
  101. package/src/internal/components-plugin.ts +54 -0
  102. package/src/internal/dev-toolbar.ts +33 -0
  103. package/src/internal/override.ts +99 -0
  104. package/src/internal/pagefind.ts +40 -0
  105. package/src/layouts/PostLayout.astro +90 -0
  106. package/src/layouts/Stalux.astro +91 -0
  107. package/src/pages/404.astro +103 -0
  108. package/src/pages/about.astro +59 -0
  109. package/src/pages/about.md.ts +15 -0
  110. package/src/pages/api/post.abbrlink.json.ts +20 -0
  111. package/src/pages/archives.astro +23 -0
  112. package/src/pages/archives.md.ts +24 -0
  113. package/src/pages/atom.xml.ts +29 -0
  114. package/src/pages/categories/[category].astro +35 -0
  115. package/src/pages/categories/[category].md.ts +34 -0
  116. package/src/pages/categories/index.astro +23 -0
  117. package/src/pages/categories/index.md.ts +27 -0
  118. package/src/pages/index.astro +30 -0
  119. package/src/pages/index.md.ts +18 -0
  120. package/src/pages/links.astro +23 -0
  121. package/src/pages/links.md.ts +18 -0
  122. package/src/pages/llms-full.txt.ts +25 -0
  123. package/src/pages/llms.txt.ts +26 -0
  124. package/src/pages/posts/[post].astro +69 -0
  125. package/src/pages/posts/[post].md.ts +109 -0
  126. package/src/pages/rss.xml.ts +29 -0
  127. package/src/pages/tags/[tag].astro +37 -0
  128. package/src/pages/tags/[tag].md.ts +34 -0
  129. package/src/pages/tags/index.astro +23 -0
  130. package/src/pages/tags/index.md.ts +27 -0
  131. package/src/pages/words.astro +24 -0
  132. package/src/pages/words.md.ts +14 -0
  133. package/src/plugins/feature-flags.ts +68 -0
  134. package/src/plugins/satteri-temml.ts +81 -0
  135. package/src/schemas/collections.ts +140 -0
  136. package/src/schemas/config.ts +226 -0
  137. package/src/scripts/background.ts +59 -0
  138. package/src/styles/base/init.css +286 -0
  139. package/src/styles/base/math.css +411 -0
  140. package/src/styles/components/archives/archivesList.module.css +378 -0
  141. package/src/styles/components/archives/toArchive.module.css +3 -0
  142. package/src/styles/components/categories/categoriesCard.module.css +87 -0
  143. package/src/styles/components/categories/categoriesList.module.css +27 -0
  144. package/src/styles/components/common/author.module.css +73 -0
  145. package/src/styles/components/common/date.module.css +15 -0
  146. package/src/styles/components/common/textType.module.css +10 -0
  147. package/src/styles/components/footer/footer.module.css +44 -0
  148. package/src/styles/components/layout/navs.module.css +250 -0
  149. package/src/styles/components/links/linkCard.module.css +99 -0
  150. package/src/styles/components/links/linkList.module.css +67 -0
  151. package/src/styles/components/links/socialLinks.module.css +22 -0
  152. package/src/styles/components/markdown.css +528 -0
  153. package/src/styles/components/posts/postAuthor.module.css +32 -0
  154. package/src/styles/components/posts/postCC.module.css +138 -0
  155. package/src/styles/components/posts/postCard.module.css +156 -0
  156. package/src/styles/components/posts/postCategories.module.css +50 -0
  157. package/src/styles/components/posts/postContent.module.css +230 -0
  158. package/src/styles/components/posts/postLayout.module.css +142 -0
  159. package/src/styles/components/posts/postLeft.module.css +38 -0
  160. package/src/styles/components/posts/postList.module.css +24 -0
  161. package/src/styles/components/posts/postNavigation.module.css +187 -0
  162. package/src/styles/components/posts/postRight.module.css +60 -0
  163. package/src/styles/components/posts/postSidebar.module.css +57 -0
  164. package/src/styles/components/posts/postTags.module.css +51 -0
  165. package/src/styles/components/posts/postTaxonomy.module.css +87 -0
  166. package/src/styles/components/posts/postToc.module.css +74 -0
  167. package/src/styles/components/posts/randomPost.module.css +131 -0
  168. package/src/styles/components/tags/tagHeader.module.css +44 -0
  169. package/src/styles/components/tags/tagsCloud.module.css +124 -0
  170. package/src/styles/components/words/wordCard.module.css +130 -0
  171. package/src/styles/components/words/wordList.module.css +39 -0
  172. package/src/styles/pages/archives.module.css +18 -0
  173. package/src/styles/pages/categories.module.css +13 -0
  174. package/src/styles/pages/home.module.css +51 -0
  175. package/src/styles/pages/listPage.module.css +13 -0
  176. package/src/styles/pages/tags.module.css +12 -0
  177. package/src/styles/pages/words.module.css +13 -0
  178. package/src/styles/shared/animations.css +87 -0
  179. package/src/styles/shared/stagger.css +20 -0
  180. package/src/utils/ai-discovery.ts +547 -0
  181. package/src/utils/badge-generator.ts +68 -0
  182. package/src/utils/cc.ts +46 -0
  183. package/src/utils/collections-stats.ts +48 -0
  184. package/src/utils/config-utils.ts +107 -0
  185. package/src/utils/count-words.ts +20 -0
  186. package/src/utils/dayjs.ts +106 -0
  187. package/src/utils/feed.ts +70 -0
  188. package/src/utils/i18n.ts +64 -0
  189. package/src/utils/lucide-icons.ts +52 -0
  190. package/src/utils/semantic-time.ts +11 -0
  191. package/src/utils/taxonomy.ts +44 -0
  192. package/src/utils/toc-scrollspy.ts +95 -0
  193. package/src/utils/waline.ts +27 -0
  194. package/src/utils/word-count-utils.ts +83 -0
  195. package/template/astro.config.mjs +51 -0
  196. package/template/package.json +27 -0
  197. package/template/public/robots.txt +3 -0
  198. package/template/src/content.config.ts +11 -0
  199. package/template/stalux/about/index.md +14 -0
  200. package/template/stalux/config/ai-discovery.yml +2 -0
  201. package/template/stalux/config/author.yml +4 -0
  202. package/template/stalux/config/comment.yml +5 -0
  203. package/template/stalux/config/footer.yml +13 -0
  204. package/template/stalux/config/head.yml +7 -0
  205. package/template/stalux/config/links.yml +4 -0
  206. package/template/stalux/config/media-links.yml +8 -0
  207. package/template/stalux/config/navs.yml +23 -0
  208. package/template/stalux/config/promote.yml +2 -0
  209. package/template/stalux/config/site.yml +7 -0
  210. package/template/stalux/config/typetexts.yml +5 -0
  211. package/template/stalux/posts/hello-stalux.md +47 -0
  212. package/template/stalux/words/_template.md +12 -0
  213. package/template/stalux/words/einstein-imagination.md +8 -0
  214. package/template/tsconfig.json +9 -0
@@ -0,0 +1,116 @@
1
+ ---
2
+ import { getCollection, type CollectionEntry } from "astro:content";
3
+ import { getSiteData } from "@utils/config-utils";
4
+ import { createTranslator } from "@utils/i18n";
5
+ import styles from "@styles/components/archives/archivesList.module.css";
6
+ import { toTimestamp, parseDate, format } from "@utils/dayjs";
7
+
8
+ interface Props {
9
+ posts?: CollectionEntry<"posts">[];
10
+ }
11
+
12
+ const props = Astro.props as Props;
13
+ const posts = props.posts ?? (await getCollection("posts", ({ data }) => !data.draft));
14
+
15
+ const configEntries = await getCollection("config");
16
+ const siteConfig = getSiteData(configEntries);
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> = {};
23
+
24
+ posts.forEach((post) => {
25
+ if (!post.data.date) return;
26
+ const date = parseDate(post.data.date);
27
+ const year = date.getUTCFullYear();
28
+ 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;
38
+ });
39
+
40
+ // 每个 post 只解析一次时间,供排序比较器复用,避免比较器里反复 new Date()
41
+ const tsCache = new Map<CollectionEntry<"posts">, number>(
42
+ posts.map((p) => [p, toTimestamp(p.data.date || 0)]),
43
+ );
44
+
45
+ // 对每个月内的文章按 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));
49
+ }
50
+ }
51
+
52
+ // 按年份降序排序 -
53
+ const sortedYears = Object.keys(archivesByYearMonth)
54
+ .map(Number)
55
+ .sort((a: number, b: number) => b - a);
56
+ ---
57
+
58
+ <section class={styles.archivesContainer}>
59
+ <div class={styles.pageHeader}>
60
+ <div class={styles.timelineContainer}>
61
+ {
62
+ sortedYears.length > 0 ? (
63
+ sortedYears.map((year) => (
64
+ <div
65
+ class={styles.yearSection}
66
+ data-year={String(year)}
67
+ >
68
+ <div class={styles.yearHeader} data-year-header style="opacity:0">
69
+ <h2 class={styles.yearTitle}>{year}</h2>
70
+ <div class={styles.yearCount}>
71
+ {t("archives.postCount", { n: yearCounts[year] ?? 0 })}
72
+ </div>
73
+ </div>
74
+ {Object.keys(archivesByYearMonth[year])
75
+ .map(Number)
76
+ .sort((a, b) => b - a)
77
+ .map((month) => (
78
+ <div class={styles.monthSection} data-month-section style="opacity:0">
79
+ <h3 class={styles.monthTitle}>
80
+ {t("archives.month", { n: month })}
81
+ </h3>
82
+ <ul class={styles.postList}>
83
+ {archivesByYearMonth[year][
84
+ month
85
+ ].map((post) => (
86
+ <li class={styles.postItem} data-post-item style="opacity:0">
87
+ <time
88
+ class={styles.postDate}
89
+ datetime={post.data.date}
90
+ >
91
+ {format(parseDate(post.data.date), 'yyyy-MM-dd')}
92
+ </time>
93
+ <a
94
+ href={`/posts/${post.data.abbrlink}/`}
95
+ class={styles.postLink}
96
+ data-astro-prefetch="viewport"
97
+ >
98
+ {post.data.title}
99
+ </a>
100
+ </li>
101
+ ))}
102
+ </ul>
103
+ </div>
104
+ ))}
105
+ </div>
106
+ ))
107
+ ) : (
108
+ <div class={styles.noPosts}>{t("archives.noPosts")}</div>
109
+ )
110
+ }
111
+ </div>
112
+
113
+ <slot name="main" />
114
+ </div>
115
+
116
+ </section>
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { getCollection } from "astro:content";
3
+ import { getSiteData } from "@utils/config-utils";
4
+ import { createTranslator } from "@utils/i18n";
5
+ import styles from "@styles/components/archives/toArchive.module.css";
6
+
7
+ const configEntries = await getCollection("config");
8
+ const siteConfig = getSiteData(configEntries);
9
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
10
+ ---
11
+
12
+ <div id={styles.archs}>
13
+ <a href="/archives" data-astro-prefetch>{t("archives.viewAll")}</a>
14
+ </div>
@@ -0,0 +1,49 @@
1
+ ---
2
+ import styles from "@styles/components/categories/categoriesCard.module.css";
3
+ import { getCollection } from "astro:content";
4
+ import { createTranslator } from "@utils/i18n";
5
+
6
+ interface Props {
7
+ category: {
8
+ name: string;
9
+ count: number;
10
+ };
11
+ }
12
+
13
+ const props = Astro.props as Props;
14
+ const configEntries = await getCollection("config");
15
+ const siteConfig = (configEntries.find(e => e.id === "site")?.data ?? {}) as Record<string, unknown>;
16
+ const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
17
+ ---
18
+
19
+ <article class={styles.categoryCard}>
20
+ <a
21
+ href={`/categories/${props.category.name}/`}
22
+ class={styles.categoryheader + " a-none"}
23
+ data-astro-prefetch
24
+ >
25
+ <div class={styles.categoryIcon}>
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ viewBox="0 0 24 24"
29
+ width="24"
30
+ height="24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ stroke-width="2"
34
+ stroke-linecap="round"
35
+ stroke-linejoin="round"
36
+ >
37
+ <path
38
+ d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
39
+ ></path>
40
+ </svg>
41
+ </div>
42
+ <div class={styles.categoryContent}>
43
+ <h2 class={styles.categoryName}>{props.category.name}</h2>
44
+ <div class={styles.categoryCount}>
45
+ {t("categories.postCount", { n: props.category.count })}
46
+ </div>
47
+ </div>
48
+ </a>
49
+ </article>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import { getCategoryCountList } from "@utils/collections-stats";
3
+ import styles from "@styles/components/categories/categoriesList.module.css";
4
+ import CategoriesCard from "@components/stalux/categories/categoriesCard.astro";
5
+
6
+ const categoryList = await getCategoryCountList();
7
+ ---
8
+
9
+ <ul class={`${styles.categoriesList} stagger-children`}>
10
+ {
11
+ categoryList.map((category) => (
12
+ <li class={styles.item} style="opacity:0">
13
+ <CategoriesCard category={category} />
14
+ </li>
15
+ ))
16
+ }
17
+ </ul>
@@ -0,0 +1,35 @@
1
+ ---
2
+ // @ts-expect-error
3
+ import { Image } from "astro:assets";
4
+ import { getCollection } from "astro:content";
5
+ 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";
7
+ const configEntries = await getCollection("config");
8
+ const authorConfig = getAuthorData(configEntries);
9
+ interface Props {
10
+ showBio?: boolean | false;
11
+ iscolum?: boolean | false;
12
+ }
13
+ const props = Astro.props as Props;
14
+
15
+ // 判断当前页面是否是首页,如果是则跳转到about,否则跳转到首页
16
+ const isHomePage = Astro.url.pathname === "/";
17
+ const navigationHref = isHomePage ? "/about" : "/";
18
+ ---
19
+
20
+ <article class={styles.author} transition:persist-props>
21
+ <figure
22
+ class={`${styles.authorFigure} ${props.iscolum ? styles.figureColumn : ""}`}
23
+ >
24
+ <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
+ />
31
+ </a>
32
+ <figcaption>{authorConfig.name}</figcaption>
33
+ {props.showBio && <p>{authorConfig.bio}</p>}
34
+ </figure>
35
+ </article>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import styles from "@styles/components/common/date.module.css";
3
+ ---
4
+
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>
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>
@@ -0,0 +1,14 @@
1
+ ---
2
+ // Pagefind component-ui search modal.
3
+ // Docs: https://pagefind.app/llms-component-ui.txt
4
+ // data-pf-theme must be on a DIRECT parent wrapper of the component.
5
+ ---
6
+
7
+ <div data-pf-theme="dark">
8
+ <pagefind-modal id="search"></pagefind-modal>
9
+ </div>
10
+
11
+ <script>
12
+ import "@pagefind/component-ui";
13
+ import "@pagefind/component-ui/css";
14
+ </script>
@@ -0,0 +1,16 @@
1
+ ---
2
+ // @ts-expect-error
3
+ import Typewriter from "astro-typewriter";
4
+ import { getCollection } from "astro:content";
5
+ 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";
7
+ const configEntries = await getCollection("config");
8
+ const siteConfig = getSiteData(configEntries);
9
+ const typetextsConfig = getTypetextsData(configEntries);
10
+ ---
11
+
12
+ {
13
+ typetextsConfig?.items && (
14
+ <Typewriter class={styles.typeWriter} messages={typetextsConfig?.items} />
15
+ )
16
+ }
@@ -0,0 +1,10 @@
1
+ ---
2
+
3
+ ---
4
+
5
+ <script
6
+ is:inline
7
+ defer
8
+ data-astro-rerun
9
+ type="text/javascript"
10
+ src="https://events.vercount.one/js"></script>
@@ -0,0 +1,150 @@
1
+ ---
2
+ import type { BadgeItem } from "@utils/badge-generator";
3
+ import { generateBadge, svgToDataUrl, isBadgeGroup } from "@utils/badge-generator";
4
+
5
+ interface Props {
6
+ badges?: BadgeItem[];
7
+ }
8
+
9
+ const { badges } = Astro.props;
10
+
11
+ function badgeImg(opts: Record<string, any>) {
12
+ const src = svgToDataUrl(generateBadge(opts));
13
+ const alt = opts.alt || `${opts.label}: ${opts.message}`;
14
+ if (opts.href) {
15
+ return `<a href="${opts.href}" target="_blank" rel="noopener noreferrer"><img src="${src}" alt="${alt}" class="badge" /></a>`;
16
+ }
17
+ return `<img src="${src}" alt="${alt}" class="badge" />`;
18
+ }
19
+ ---
20
+
21
+ {
22
+ badges && badges.length > 0 && (
23
+ <div class="badges-wrapper">
24
+ {badges.map((item: BadgeItem, idx: number) => {
25
+ if (isBadgeGroup(item)) {
26
+ const groupId = `badge-group-${idx}`;
27
+ return (
28
+ <details class="badge-group" id={groupId}>
29
+ <summary class="badge-group-summary">
30
+ <span class="badge-group-arrow">&#9654;</span>
31
+ <span class="badge-group-title">{item.title}</span>
32
+ <span class="badge-group-hint" data-hint="expanded">收起</span>
33
+ <span class="badge-group-hint" data-hint="collapsed">展开</span>
34
+ </summary>
35
+ <div class="badge-group-items badges">
36
+ {item.items.map((b: any) => (
37
+ <Fragment set:html={badgeImg(b)} />
38
+ ))}
39
+ </div>
40
+ </details>
41
+ );
42
+ }
43
+ return <Fragment set:html={badgeImg(item)} />;
44
+ })}
45
+ </div>
46
+ )
47
+ }
48
+
49
+ <script>
50
+ function initBadgeGroups() {
51
+ document.querySelectorAll<HTMLDetailsElement>(".badge-group").forEach((el) => {
52
+ const key = `badge-${el.id}`;
53
+ const saved = localStorage.getItem(key);
54
+ if (saved !== null) {
55
+ el.open = saved === "open";
56
+ } else {
57
+ // 首次访问使用默认折叠状态
58
+ el.open = el.getAttribute("data-default-open") === "true";
59
+ }
60
+ el.addEventListener("toggle", () => {
61
+ localStorage.setItem(key, el.open ? "open" : "closed");
62
+ });
63
+ });
64
+ }
65
+ document.addEventListener("astro:page-load", initBadgeGroups);
66
+ initBadgeGroups();
67
+ </script>
68
+
69
+ <style>
70
+ .badges-wrapper {
71
+ display: flex;
72
+ justify-content: center;
73
+ align-items: center;
74
+ flex-wrap: wrap;
75
+ gap: 0.5rem;
76
+ }
77
+
78
+ .badge-group {
79
+ width: 100%;
80
+ border: none;
81
+ }
82
+
83
+ .badge-group-summary {
84
+ cursor: pointer;
85
+ display: flex;
86
+ justify-content: center;
87
+ align-items: center;
88
+ gap: 0.4rem;
89
+ padding: 0.2rem 0;
90
+ color: var(--white-40p);
91
+ font-size: 0.85rem;
92
+ user-select: none;
93
+ list-style: none;
94
+ transition: color 0.2s;
95
+ }
96
+ .badge-group-summary:hover {
97
+ color: var(--white-70p);
98
+ }
99
+ .badge-group-summary::-webkit-details-marker {
100
+ display: none;
101
+ }
102
+
103
+ .badge-group-arrow {
104
+ font-size: 0.65rem;
105
+ transition: transform 0.2s;
106
+ display: inline-block;
107
+ }
108
+ .badge-group[open] .badge-group-arrow {
109
+ transform: rotate(90deg);
110
+ }
111
+
112
+ .badge-group-title {
113
+ color: var(--white-60p);
114
+ }
115
+
116
+ .badge-group-hint {
117
+ font-size: 0.7rem;
118
+ color: var(--white-25p);
119
+ }
120
+ .badge-group[open] .badge-group-hint[data-hint="collapsed"],
121
+ .badge-group:not([open]) .badge-group-hint[data-hint="expanded"] {
122
+ display: none;
123
+ }
124
+
125
+ .badge-group-items {
126
+ padding-top: 0.35rem;
127
+ }
128
+
129
+ .badges {
130
+ display: flex;
131
+ justify-content: center;
132
+ align-items: center;
133
+ flex-wrap: wrap;
134
+ gap: 0.5rem;
135
+ }
136
+
137
+ .badge {
138
+ height: 20px;
139
+ vertical-align: middle;
140
+ }
141
+
142
+ @media (max-width: 768px) {
143
+ .badges {
144
+ gap: 0.3rem;
145
+ }
146
+ .badge {
147
+ height: 18px;
148
+ }
149
+ }
150
+ </style>
@@ -0,0 +1,91 @@
1
+ ---
2
+ import { getCollection } from "astro:content";
3
+ import { createTranslator } from "@utils/i18n";
4
+ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
5
+
6
+ interface Props {
7
+ icp?: string;
8
+ pubsec?: string;
9
+ pubsecNumber?: string;
10
+ }
11
+
12
+ const { icp, pubsec, pubsecNumber } = Astro.props;
13
+
14
+ const configEntries = await getCollection("config");
15
+ const siteConfig = getSiteData(configEntries);
16
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
17
+
18
+ const showBeian = !!(icp || pubsec);
19
+ ---
20
+
21
+ {
22
+ showBeian && (
23
+ <div class="beian-info">
24
+ {icp && (
25
+ <a
26
+ href="https://beian.miit.gov.cn/"
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ >
30
+ {icp}
31
+ </a>
32
+ )}
33
+ {pubsec && pubsecNumber && (
34
+ <a
35
+ href={`http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=${pubsecNumber}`}
36
+ target="_blank"
37
+ rel="noopener noreferrer"
38
+ class="public-security-beian"
39
+ >
40
+ <img
41
+ src="https://beian.mps.gov.cn/img/logo01.dd7ff50e.png"
42
+ alt={t("footer.securityIcon")}
43
+ />
44
+ {pubsec}
45
+ </a>
46
+ )}
47
+ </div>
48
+ )
49
+ }
50
+
51
+ <style>
52
+ .beian-info {
53
+ display: flex;
54
+ justify-content: center;
55
+ flex-wrap: wrap;
56
+ gap: 1rem;
57
+ }
58
+
59
+ .beian-info a {
60
+ color: rgba(255, 255, 255, 0.7);
61
+ text-decoration: none;
62
+ transition: color 0.3s ease;
63
+ font-size: 0.9rem;
64
+ }
65
+
66
+ .beian-info a:hover {
67
+ color: rgba(255, 255, 255, 0.9);
68
+ text-decoration: none;
69
+ }
70
+
71
+ .public-security-beian {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ gap: 5px;
75
+ color: rgba(255, 255, 255, 0.7);
76
+ text-decoration: none;
77
+ transition: color 0.3s ease;
78
+ font-size: 0.9rem;
79
+ }
80
+
81
+ .public-security-beian:hover {
82
+ color: rgba(255, 255, 255, 0.9);
83
+ text-decoration: none;
84
+ }
85
+
86
+ .public-security-beian img {
87
+ width: 15px;
88
+ height: 15px;
89
+ display: inline-block;
90
+ }
91
+ </style>
@@ -0,0 +1,40 @@
1
+ ---
2
+ import { getCollection } from "astro:content";
3
+ import { createTranslator } from "@utils/i18n";
4
+ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
5
+
6
+ interface Props {
7
+ author?: string;
8
+ startYear?: number;
9
+ }
10
+
11
+ const props = Astro.props as Props;
12
+
13
+ const configEntries = await getCollection("config");
14
+ const siteConfig = getSiteData(configEntries);
15
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
16
+
17
+ const currentYear = new Date().getFullYear();
18
+ const copyrightYear =
19
+ props.startYear && props.startYear < currentYear
20
+ ? `${props.startYear}-${currentYear}`
21
+ : currentYear;
22
+ ---
23
+
24
+ <small class="copyright">
25
+ <span>&copy; {copyrightYear} {props.author || ""}. {t("footer.rightsReserved")}</span>
26
+ </small>
27
+
28
+ <style>
29
+ .copyright {
30
+ font-weight: 500;
31
+ display: flex;
32
+ gap: 0.3em;
33
+ justify-content: center;
34
+ flex-wrap: wrap;
35
+ }
36
+
37
+ .copyright span {
38
+ margin: 0;
39
+ }
40
+ </style>