@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.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/package.json +108 -0
- package/scripts/cli.mjs +103 -0
- package/src/assets/BiCcCircle.svg +1 -0
- package/src/assets/astro.svg +1 -0
- package/src/assets/background/README.md +3 -0
- package/src/assets/background/pattern-1.min.svg +1 -0
- package/src/assets/background/pattern-10.min.svg +1 -0
- package/src/assets/background/pattern-11.min.svg +1 -0
- package/src/assets/background/pattern-12.min.svg +1 -0
- package/src/assets/background/pattern-13.min.svg +1 -0
- package/src/assets/background/pattern-14.min.svg +1 -0
- package/src/assets/background/pattern-15.min.svg +1 -0
- package/src/assets/background/pattern-16.min.svg +1 -0
- package/src/assets/background/pattern-17.min.svg +1 -0
- package/src/assets/background/pattern-18.min.svg +1 -0
- package/src/assets/background/pattern-19.min.svg +1 -0
- package/src/assets/background/pattern-2.min.svg +1 -0
- package/src/assets/background/pattern-20.min.svg +1 -0
- package/src/assets/background/pattern-21.min.svg +1 -0
- package/src/assets/background/pattern-22.min.svg +1 -0
- package/src/assets/background/pattern-23.min.svg +1 -0
- package/src/assets/background/pattern-24.min.svg +1 -0
- package/src/assets/background/pattern-25.min.svg +1 -0
- package/src/assets/background/pattern-26.min.svg +1 -0
- package/src/assets/background/pattern-27.min.svg +1 -0
- package/src/assets/background/pattern-28.min.svg +1 -0
- package/src/assets/background/pattern-29.min.svg +1 -0
- package/src/assets/background/pattern-3.min.svg +1 -0
- package/src/assets/background/pattern-30.min.svg +1 -0
- package/src/assets/background/pattern-31.min.svg +1 -0
- package/src/assets/background/pattern-32.min.svg +1 -0
- package/src/assets/background/pattern-33.min.svg +1 -0
- package/src/assets/background/pattern-34.min.svg +1 -0
- package/src/assets/background/pattern-35.min.svg +1 -0
- package/src/assets/background/pattern-36.min.svg +1 -0
- package/src/assets/background/pattern-37.min.svg +1 -0
- package/src/assets/background/pattern-38.min.svg +1 -0
- package/src/assets/background/pattern-39.min.svg +1 -0
- package/src/assets/background/pattern-4.min.svg +1 -0
- package/src/assets/background/pattern-40.min.svg +1 -0
- package/src/assets/background/pattern-41.min.svg +1 -0
- package/src/assets/background/pattern-42.min.svg +1 -0
- package/src/assets/background/pattern-5.min.svg +1 -0
- package/src/assets/background/pattern-6.min.svg +1 -0
- package/src/assets/background/pattern-7.min.svg +1 -0
- package/src/assets/background/pattern-8.min.svg +1 -0
- package/src/assets/background/pattern-9.min.svg +1 -0
- package/src/assets/background.svg +1 -0
- package/src/assets/cc-icon.svg +4 -0
- package/src/assets/fonts/GoogleSansCode-Italic.woff2 +0 -0
- package/src/assets/fonts/GoogleSansCode.woff2 +0 -0
- package/src/assets/fonts/LXGWWenKai-Regular.ttf +0 -0
- package/src/assets/fonts/LXGWWenKai-Regular.woff2 +0 -0
- package/src/components/stalux/analytics/clarity.astro +25 -0
- package/src/components/stalux/analytics/google.astro +31 -0
- package/src/components/stalux/analytics/umami.astro +16 -0
- package/src/components/stalux/archives/archivesList.astro +116 -0
- package/src/components/stalux/archives/toArchive.astro +14 -0
- package/src/components/stalux/categories/categoriesCard.astro +49 -0
- package/src/components/stalux/categories/categoriesList.astro +17 -0
- package/src/components/stalux/common/author.astro +35 -0
- package/src/components/stalux/common/date.astro +59 -0
- package/src/components/stalux/common/search.astro +14 -0
- package/src/components/stalux/common/textType.astro +16 -0
- package/src/components/stalux/common/verCount.astro +10 -0
- package/src/components/stalux/footer/FooterBadges.astro +150 -0
- package/src/components/stalux/footer/FooterBeian.astro +91 -0
- package/src/components/stalux/footer/FooterCopyright.astro +40 -0
- package/src/components/stalux/footer/FooterStats.astro +108 -0
- package/src/components/stalux/footer/FooterThemeInfo.astro +67 -0
- package/src/components/stalux/layout/footer.astro +87 -0
- package/src/components/stalux/layout/head.astro +194 -0
- package/src/components/stalux/layout/listPage.astro +26 -0
- package/src/components/stalux/layout/navs.astro +174 -0
- package/src/components/stalux/links/linkCard.astro +50 -0
- package/src/components/stalux/links/linkList.astro +20 -0
- package/src/components/stalux/links/socialMedia.astro +102 -0
- package/src/components/stalux/posts/PhotoSwipeStyle.astro +3 -0
- package/src/components/stalux/posts/PostTaxonomy.astro +65 -0
- package/src/components/stalux/posts/WalineComment.astro +609 -0
- package/src/components/stalux/posts/postAuthor.astro +23 -0
- package/src/components/stalux/posts/postCC.astro +52 -0
- package/src/components/stalux/posts/postCard.astro +83 -0
- package/src/components/stalux/posts/postContent.astro +174 -0
- package/src/components/stalux/posts/postLeft.astro +41 -0
- package/src/components/stalux/posts/postList.astro +25 -0
- package/src/components/stalux/posts/postNavigation.astro +137 -0
- package/src/components/stalux/posts/postRight.astro +33 -0
- package/src/components/stalux/posts/postToc.astro +88 -0
- package/src/components/stalux/posts/randomPost.astro +134 -0
- package/src/components/stalux/tags/tagHeader.astro +50 -0
- package/src/components/stalux/tags/tagsCloud.astro +100 -0
- package/src/components/stalux/words/wordCard.astro +62 -0
- package/src/components/stalux/words/wordList.astro +70 -0
- package/src/config.ts +58 -0
- package/src/i18n/en.json +193 -0
- package/src/i18n/zh-CN.json +193 -0
- package/src/index.ts +215 -0
- package/src/internal/components-plugin.ts +54 -0
- package/src/internal/dev-toolbar.ts +33 -0
- package/src/internal/override.ts +99 -0
- package/src/internal/pagefind.ts +40 -0
- package/src/layouts/PostLayout.astro +90 -0
- package/src/layouts/Stalux.astro +91 -0
- package/src/pages/404.astro +103 -0
- package/src/pages/about.astro +59 -0
- package/src/pages/about.md.ts +15 -0
- package/src/pages/api/post.abbrlink.json.ts +20 -0
- package/src/pages/archives.astro +23 -0
- package/src/pages/archives.md.ts +24 -0
- package/src/pages/atom.xml.ts +29 -0
- package/src/pages/categories/[category].astro +35 -0
- package/src/pages/categories/[category].md.ts +34 -0
- package/src/pages/categories/index.astro +23 -0
- package/src/pages/categories/index.md.ts +27 -0
- package/src/pages/index.astro +30 -0
- package/src/pages/index.md.ts +18 -0
- package/src/pages/links.astro +23 -0
- package/src/pages/links.md.ts +18 -0
- package/src/pages/llms-full.txt.ts +25 -0
- package/src/pages/llms.txt.ts +26 -0
- package/src/pages/posts/[post].astro +69 -0
- package/src/pages/posts/[post].md.ts +109 -0
- package/src/pages/rss.xml.ts +29 -0
- package/src/pages/tags/[tag].astro +37 -0
- package/src/pages/tags/[tag].md.ts +34 -0
- package/src/pages/tags/index.astro +23 -0
- package/src/pages/tags/index.md.ts +27 -0
- package/src/pages/words.astro +24 -0
- package/src/pages/words.md.ts +14 -0
- package/src/plugins/feature-flags.ts +68 -0
- package/src/plugins/satteri-temml.ts +81 -0
- package/src/schemas/collections.ts +140 -0
- package/src/schemas/config.ts +226 -0
- package/src/scripts/background.ts +59 -0
- package/src/styles/base/init.css +286 -0
- package/src/styles/base/math.css +411 -0
- package/src/styles/components/archives/archivesList.module.css +378 -0
- package/src/styles/components/archives/toArchive.module.css +3 -0
- package/src/styles/components/categories/categoriesCard.module.css +87 -0
- package/src/styles/components/categories/categoriesList.module.css +27 -0
- package/src/styles/components/common/author.module.css +73 -0
- package/src/styles/components/common/date.module.css +15 -0
- package/src/styles/components/common/textType.module.css +10 -0
- package/src/styles/components/footer/footer.module.css +44 -0
- package/src/styles/components/layout/navs.module.css +250 -0
- package/src/styles/components/links/linkCard.module.css +99 -0
- package/src/styles/components/links/linkList.module.css +67 -0
- package/src/styles/components/links/socialLinks.module.css +22 -0
- package/src/styles/components/markdown.css +528 -0
- package/src/styles/components/posts/postAuthor.module.css +32 -0
- package/src/styles/components/posts/postCC.module.css +138 -0
- package/src/styles/components/posts/postCard.module.css +156 -0
- package/src/styles/components/posts/postCategories.module.css +50 -0
- package/src/styles/components/posts/postContent.module.css +230 -0
- package/src/styles/components/posts/postLayout.module.css +142 -0
- package/src/styles/components/posts/postLeft.module.css +38 -0
- package/src/styles/components/posts/postList.module.css +24 -0
- package/src/styles/components/posts/postNavigation.module.css +187 -0
- package/src/styles/components/posts/postRight.module.css +60 -0
- package/src/styles/components/posts/postSidebar.module.css +57 -0
- package/src/styles/components/posts/postTags.module.css +51 -0
- package/src/styles/components/posts/postTaxonomy.module.css +87 -0
- package/src/styles/components/posts/postToc.module.css +74 -0
- package/src/styles/components/posts/randomPost.module.css +131 -0
- package/src/styles/components/tags/tagHeader.module.css +44 -0
- package/src/styles/components/tags/tagsCloud.module.css +124 -0
- package/src/styles/components/words/wordCard.module.css +130 -0
- package/src/styles/components/words/wordList.module.css +39 -0
- package/src/styles/pages/archives.module.css +18 -0
- package/src/styles/pages/categories.module.css +13 -0
- package/src/styles/pages/home.module.css +51 -0
- package/src/styles/pages/listPage.module.css +13 -0
- package/src/styles/pages/tags.module.css +12 -0
- package/src/styles/pages/words.module.css +13 -0
- package/src/styles/shared/animations.css +87 -0
- package/src/styles/shared/stagger.css +20 -0
- package/src/utils/ai-discovery.ts +547 -0
- package/src/utils/badge-generator.ts +68 -0
- package/src/utils/cc.ts +46 -0
- package/src/utils/collections-stats.ts +48 -0
- package/src/utils/config-utils.ts +107 -0
- package/src/utils/count-words.ts +20 -0
- package/src/utils/dayjs.ts +106 -0
- package/src/utils/feed.ts +70 -0
- package/src/utils/i18n.ts +64 -0
- package/src/utils/lucide-icons.ts +52 -0
- package/src/utils/semantic-time.ts +11 -0
- package/src/utils/taxonomy.ts +44 -0
- package/src/utils/toc-scrollspy.ts +95 -0
- package/src/utils/waline.ts +27 -0
- package/src/utils/word-count-utils.ts +83 -0
- package/template/astro.config.mjs +51 -0
- package/template/package.json +27 -0
- package/template/public/robots.txt +3 -0
- package/template/src/content.config.ts +11 -0
- package/template/stalux/about/index.md +14 -0
- package/template/stalux/config/ai-discovery.yml +2 -0
- package/template/stalux/config/author.yml +4 -0
- package/template/stalux/config/comment.yml +5 -0
- package/template/stalux/config/footer.yml +13 -0
- package/template/stalux/config/head.yml +7 -0
- package/template/stalux/config/links.yml +4 -0
- package/template/stalux/config/media-links.yml +8 -0
- package/template/stalux/config/navs.yml +23 -0
- package/template/stalux/config/promote.yml +2 -0
- package/template/stalux/config/site.yml +7 -0
- package/template/stalux/config/typetexts.yml +5 -0
- package/template/stalux/posts/hello-stalux.md +47 -0
- package/template/stalux/words/_template.md +12 -0
- package/template/stalux/words/einstein-imagination.md +8 -0
- 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,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">▶</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>© {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>
|