@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.
- package/package.json +25 -14
- package/src/assets/cc-icon.svg +6 -3
- package/src/components/stalux/analytics/umami.astro +5 -2
- package/src/components/stalux/archives/archivesList.astro +29 -40
- package/src/components/stalux/archives/toArchive.astro +1 -1
- package/src/components/stalux/categories/categoriesCard.astro +7 -3
- package/src/components/stalux/categories/categoriesList.astro +4 -6
- package/src/components/stalux/common/author.astro +4 -11
- package/src/components/stalux/common/date.astro +2 -53
- package/src/components/stalux/common/search.astro +1 -1
- package/src/components/stalux/common/textType.astro +5 -8
- package/src/components/stalux/common/verCount.astro +1 -2
- package/src/components/stalux/footer/FooterBadges.astro +75 -97
- package/src/components/stalux/footer/FooterBeian.astro +37 -39
- package/src/components/stalux/footer/FooterCopyright.astro +12 -12
- package/src/components/stalux/footer/FooterStats.astro +21 -27
- package/src/components/stalux/footer/FooterThemeInfo.astro +23 -25
- package/src/components/stalux/layout/footer.astro +50 -46
- package/src/components/stalux/layout/head.astro +46 -287
- package/src/components/stalux/layout/listPage.astro +3 -3
- package/src/components/stalux/layout/navs.astro +14 -128
- package/src/components/stalux/links/linkCard.astro +4 -9
- package/src/components/stalux/links/linkList.astro +3 -5
- package/src/components/stalux/links/socialMedia.astro +21 -42
- package/src/components/stalux/posts/PhotoSwipeStyle.astro +2 -0
- package/src/components/stalux/posts/PostTaxonomy.astro +3 -5
- package/src/components/stalux/posts/WalineComment.astro +378 -478
- package/src/components/stalux/posts/postAuthor.astro +3 -5
- package/src/components/stalux/posts/postCC.astro +14 -16
- package/src/components/stalux/posts/postCard.astro +14 -25
- package/src/components/stalux/posts/postContent.astro +14 -152
- package/src/components/stalux/posts/postLeft.astro +13 -15
- package/src/components/stalux/posts/postList.astro +5 -7
- package/src/components/stalux/posts/postNavigation.astro +11 -6
- package/src/components/stalux/posts/postRight.astro +5 -4
- package/src/components/stalux/posts/postToc.astro +22 -23
- package/src/components/stalux/posts/randomPost.astro +42 -7
- package/src/components/stalux/tags/tagHeader.astro +6 -2
- package/src/components/stalux/tags/tagsCloud.astro +12 -68
- package/src/components/stalux/words/wordCard.astro +3 -7
- package/src/components/stalux/words/wordList.astro +4 -36
- package/src/expressive-code.ts +1 -0
- package/src/index.ts +33 -168
- package/src/internal/font-slices.ts +5 -3
- package/src/internal/injected-routes.ts +15 -0
- package/src/internal/override.ts +3 -2
- package/src/internal/satteri-config.ts +59 -0
- package/src/internal/version.ts +3 -0
- package/src/layouts/PostLayout.astro +20 -24
- package/src/layouts/Stalux.astro +74 -75
- package/src/pages/404.astro +51 -44
- package/src/pages/about.astro +10 -11
- package/src/pages/api/post.abbrlink.json.ts +1 -1
- package/src/pages/api/posts.json.ts +1 -1
- package/src/pages/archives.astro +9 -5
- package/src/pages/atom.xml.ts +5 -6
- package/src/pages/categories/[category].astro +5 -4
- package/src/pages/categories/[category].md.ts +1 -1
- package/src/pages/categories/index.astro +6 -7
- package/src/pages/index.astro +42 -31
- package/src/pages/links.astro +4 -3
- package/src/pages/llms-full.txt.ts +1 -1
- package/src/pages/llms.txt.ts +2 -3
- package/src/pages/posts/[post].astro +33 -17
- package/src/pages/posts/[post].md.ts +4 -4
- package/src/pages/rss.xml.ts +5 -6
- package/src/pages/tags/[tag].astro +5 -4
- package/src/pages/tags/[tag].md.ts +1 -1
- package/src/pages/tags/index.astro +6 -7
- package/src/pages/words.astro +5 -5
- package/src/plugins/feature-flags.ts +37 -16
- package/src/plugins/satteri-temml.ts +11 -9
- package/src/schemas/collections.ts +7 -15
- package/src/schemas/config.ts +3 -3
- package/src/scripts/background.ts +2 -0
- package/src/scripts/clarity.ts +8 -2
- package/src/scripts/core-runtime.ts +7 -0
- package/src/scripts/countdown.ts +6 -15
- package/src/scripts/date-time.ts +40 -0
- package/src/scripts/footer-badges.ts +18 -0
- package/src/scripts/google-analytics.ts +3 -2
- package/src/scripts/navigation.ts +110 -0
- package/src/scripts/page-runtime.ts +84 -0
- package/src/scripts/photoswipe.ts +99 -0
- package/src/scripts/random-posts.ts +47 -22
- package/src/scripts/runtime-counter.ts +15 -24
- package/src/scripts/tags-cloud.ts +59 -0
- package/src/scripts/toc-scrollspy.ts +8 -0
- package/src/scripts/vercount.ts +1 -0
- package/src/scripts/waline.ts +38 -0
- package/src/scripts/webmcp.ts +11 -11
- package/src/scripts/word-list.ts +39 -0
- package/src/styles/base/init.css +1 -6
- package/src/styles/base/math.css +3 -3
- package/src/styles/components/layout/navs.module.css +0 -1
- package/src/styles/components/links/socialLinks.module.css +46 -0
- package/src/styles/components/markdown.css +1 -6
- package/src/styles/components/tags/tagHeader.module.css +1 -1
- package/src/styles/components/tags/tagsCloud.module.css +48 -1
- package/src/utils/ai-discovery.ts +23 -26
- package/src/utils/badge-generator.ts +8 -11
- package/src/utils/cc.ts +13 -4
- package/src/utils/collections-stats.ts +15 -14
- package/src/utils/config-utils.ts +36 -54
- package/src/utils/content-index.ts +139 -0
- package/src/utils/dayjs.ts +4 -2
- package/src/utils/feed.ts +2 -2
- package/src/utils/public-routes.ts +54 -0
- package/src/utils/route-catalog.ts +36 -0
- package/src/utils/semantic-time.ts +1 -1
- package/src/utils/seo-document.ts +272 -0
- package/src/utils/taxonomy.ts +6 -30
- package/src/utils/toc-scrollspy.ts +36 -14
- package/src/utils/view-transitions.ts +3 -36
- package/src/utils/waline.ts +47 -24
- 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.
|
|
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": "
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
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
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
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"
|
package/src/assets/cc-icon.svg
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
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
|
-
|
|
18
|
+
transition:persist="stalux-umami"
|
|
19
|
+
></script>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
47
|
-
for (const
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
.map(
|
|
55
|
-
|
|
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
|
-
|
|
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:
|
|
67
|
+
{t("archives.postCount", { n: archiveYear.count })}
|
|
72
68
|
</div>
|
|
73
69
|
</div>
|
|
74
|
-
{
|
|
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
|
-
{
|
|
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<
|
|
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
|
|
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 {
|
|
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
|
|
7
|
-
<time id={styles
|
|
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>
|
|
@@ -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 {
|
|
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,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:
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
.badge-group-items {
|
|
110
|
+
padding-top: 0.35rem;
|
|
111
|
+
}
|
|
121
112
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
.badge {
|
|
122
|
+
height: 20px;
|
|
123
|
+
vertical-align: middle;
|
|
124
|
+
}
|
|
134
125
|
|
|
126
|
+
@media (max-width: 768px) {
|
|
135
127
|
.badges {
|
|
136
|
-
|
|
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:
|
|
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>
|