@astro-minimax/core 0.1.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/README.md +29 -0
- package/package.json +41 -0
- package/src/assets/icons/IconArchive.svg +1 -0
- package/src/assets/icons/IconArrowLeft.svg +1 -0
- package/src/assets/icons/IconArrowNarrowUp.svg +1 -0
- package/src/assets/icons/IconArrowRight.svg +1 -0
- package/src/assets/icons/IconArticle.svg +1 -0
- package/src/assets/icons/IconBrandX.svg +1 -0
- package/src/assets/icons/IconCalendar.svg +1 -0
- package/src/assets/icons/IconChevronLeft.svg +1 -0
- package/src/assets/icons/IconChevronRight.svg +1 -0
- package/src/assets/icons/IconEdit.svg +1 -0
- package/src/assets/icons/IconFacebook.svg +1 -0
- package/src/assets/icons/IconGitHub.svg +1 -0
- package/src/assets/icons/IconHash.svg +1 -0
- package/src/assets/icons/IconHome.svg +1 -0
- package/src/assets/icons/IconLinkedin.svg +1 -0
- package/src/assets/icons/IconMail.svg +1 -0
- package/src/assets/icons/IconMenuDeep.svg +1 -0
- package/src/assets/icons/IconMoon.svg +1 -0
- package/src/assets/icons/IconPinterest.svg +1 -0
- package/src/assets/icons/IconProject.svg +1 -0
- package/src/assets/icons/IconRss.svg +1 -0
- package/src/assets/icons/IconSearch.svg +1 -0
- package/src/assets/icons/IconSeries.svg +1 -0
- package/src/assets/icons/IconSunHigh.svg +1 -0
- package/src/assets/icons/IconTag.svg +1 -0
- package/src/assets/icons/IconTelegram.svg +1 -0
- package/src/assets/icons/IconUser.svg +1 -0
- package/src/assets/icons/IconWhatsapp.svg +1 -0
- package/src/assets/icons/IconX.svg +1 -0
- package/src/components/ai/AIChatWidget.astro +377 -0
- package/src/components/blog/Comments.astro +527 -0
- package/src/components/blog/Copyright.astro +152 -0
- package/src/components/blog/EditPost.astro +59 -0
- package/src/components/blog/FloatingTOC.astro +260 -0
- package/src/components/blog/InlineTOC.astro +223 -0
- package/src/components/blog/PostActions.astro +306 -0
- package/src/components/blog/RelatedPosts.astro +60 -0
- package/src/components/blog/SeriesNav.astro +176 -0
- package/src/components/blog/ShareLinks.astro +26 -0
- package/src/components/nav/BackButton.astro +37 -0
- package/src/components/nav/BackToTopButton.astro +223 -0
- package/src/components/nav/Breadcrumb.astro +57 -0
- package/src/components/nav/FloatingActions.astro +206 -0
- package/src/components/nav/Footer.astro +107 -0
- package/src/components/nav/Header.astro +252 -0
- package/src/components/nav/Pagination.astro +45 -0
- package/src/components/social/Socials.astro +19 -0
- package/src/components/social/Sponsors.astro +34 -0
- package/src/components/social/Sponsorship.astro +44 -0
- package/src/components/ui/Alert.astro +28 -0
- package/src/components/ui/Card.astro +206 -0
- package/src/components/ui/Collapse.astro +82 -0
- package/src/components/ui/ColorPreview.astro +29 -0
- package/src/components/ui/Datetime.astro +61 -0
- package/src/components/ui/GithubCard.astro +191 -0
- package/src/components/ui/LinkButton.astro +21 -0
- package/src/components/ui/Tag.astro +37 -0
- package/src/components/ui/TagCloud.astro +69 -0
- package/src/components/ui/Timeline.astro +39 -0
- package/src/layouts/AboutLayout.astro +24 -0
- package/src/layouts/Layout.astro +329 -0
- package/src/layouts/Main.astro +42 -0
- package/src/layouts/PostDetails.astro +445 -0
- package/src/plugins/rehype-autolink-headings.ts +46 -0
- package/src/plugins/rehype-external-links.ts +35 -0
- package/src/plugins/rehype-table-scroll.ts +35 -0
- package/src/plugins/remark-add-zoomable.ts +28 -0
- package/src/plugins/remark-reading-time.ts +18 -0
- package/src/plugins/shiki-transformers.ts +212 -0
- package/src/scripts/lightbox.ts +63 -0
- package/src/scripts/reading-position.ts +56 -0
- package/src/scripts/theme-utils.ts +19 -0
- package/src/scripts/theme.ts +179 -0
- package/src/scripts/web-vitals.ts +96 -0
- package/src/styles/code-blocks.css +194 -0
- package/src/styles/components.css +252 -0
- package/src/styles/global.css +403 -0
- package/src/styles/typography.css +149 -0
- package/src/types.ts +89 -0
- package/src/utils/generateOgImages.ts +38 -0
- package/src/utils/getCategoryPath.ts +23 -0
- package/src/utils/getPath.ts +52 -0
- package/src/utils/getPostsByCategory.ts +17 -0
- package/src/utils/getPostsByGroupCondition.ts +25 -0
- package/src/utils/getPostsByLang.ts +27 -0
- package/src/utils/getPostsByTag.ts +10 -0
- package/src/utils/getReadingTime.ts +33 -0
- package/src/utils/getRelatedPosts.ts +59 -0
- package/src/utils/getSeriesData.ts +57 -0
- package/src/utils/getSortedPosts.ts +18 -0
- package/src/utils/getTagsWithCount.ts +38 -0
- package/src/utils/getUniqueCategories.ts +81 -0
- package/src/utils/getUniqueTags.ts +23 -0
- package/src/utils/i18n.ts +249 -0
- package/src/utils/loadGoogleFont.ts +38 -0
- package/src/utils/og-templates/post.js +229 -0
- package/src/utils/og-templates/site.js +128 -0
- package/src/utils/pathUtils.ts +17 -0
- package/src/utils/postFilter.ts +11 -0
- package/src/utils/slugify.ts +23 -0
- package/src/utils/toc.ts +27 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
title: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { class: className = "", title, ...props } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<collapse-component class="group/expand">
|
|
11
|
+
<div
|
|
12
|
+
class:list={[
|
|
13
|
+
"my-4 rounded-xl border px-3 sm:px-4 group-[.expanded]/expand:bg-muted",
|
|
14
|
+
className,
|
|
15
|
+
]}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<slot name="before" />
|
|
19
|
+
<div
|
|
20
|
+
class="group/highlight expand-title sticky top-0 z-20 flex cursor-pointer items-center justify-between py-1.5 group-[.expanded]/expand:bg-muted sm:py-2"
|
|
21
|
+
>
|
|
22
|
+
<p
|
|
23
|
+
class="m-0 transition-colors group-hover/highlight:text-accent"
|
|
24
|
+
>
|
|
25
|
+
{title}
|
|
26
|
+
</p>
|
|
27
|
+
<div class="expand-button">
|
|
28
|
+
<svg
|
|
29
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
30
|
+
width="16"
|
|
31
|
+
height="16"
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke-width="2.5"
|
|
35
|
+
stroke-linecap="round"
|
|
36
|
+
stroke-linejoin="round"
|
|
37
|
+
class="my-1 stroke-foreground-soft transition-all duration-300 group-hover/highlight:stroke-accent group-[.expanded]/expand:-rotate-90"
|
|
38
|
+
>
|
|
39
|
+
<line
|
|
40
|
+
x1="5"
|
|
41
|
+
y1="12"
|
|
42
|
+
x2="19"
|
|
43
|
+
y2="12"
|
|
44
|
+
class="translate-x-1 scale-x-100 duration-300 ease-in-out group-[.expanded]/expand:translate-x-4 group-[.expanded]/expand:scale-x-0"
|
|
45
|
+
></line>
|
|
46
|
+
<polyline
|
|
47
|
+
points="12 5 19 12 12 19"
|
|
48
|
+
class="translate-x-1 duration-300 ease-in-out group-[.expanded]/expand:translate-x-0"
|
|
49
|
+
></polyline>
|
|
50
|
+
</svg>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div
|
|
54
|
+
class="expand-content grid opacity-0 transition-all duration-300 ease-in-out group-[.expanded]/expand:mb-3 group-[.expanded]/expand:opacity-100 sm:group-[.expanded]/expand:mb-4"
|
|
55
|
+
>
|
|
56
|
+
<div class="overflow-hidden">
|
|
57
|
+
<slot />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</collapse-component>
|
|
62
|
+
|
|
63
|
+
<script>
|
|
64
|
+
class CollapseElement extends HTMLElement {
|
|
65
|
+
connectedCallback() {
|
|
66
|
+
const expandTitle = this.querySelector(".expand-title");
|
|
67
|
+
expandTitle?.addEventListener("click", () => {
|
|
68
|
+
this.classList.toggle("expanded");
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
customElements.define("collapse-component", CollapseElement);
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
.expand-content {
|
|
77
|
+
grid-template-rows: 0fr;
|
|
78
|
+
}
|
|
79
|
+
.expanded .expand-content {
|
|
80
|
+
grid-template-rows: 1fr;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Color preview component
|
|
4
|
+
* Usage: <ColorPreview colors={["#FF6B6B", "#4ECDC4"]} />
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
colors: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { colors } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div class="color-preview my-4 flex flex-wrap gap-3">
|
|
15
|
+
{
|
|
16
|
+
colors.map(color => (
|
|
17
|
+
<div class="flex items-center gap-2">
|
|
18
|
+
<span
|
|
19
|
+
class="color-swatch h-6 w-6 rounded-md border border-border shadow-sm"
|
|
20
|
+
style={`background-color: ${color}`}
|
|
21
|
+
title={color}
|
|
22
|
+
/>
|
|
23
|
+
<code class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
24
|
+
{color}
|
|
25
|
+
</code>
|
|
26
|
+
</div>
|
|
27
|
+
))
|
|
28
|
+
}
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import utc from "dayjs/plugin/utc";
|
|
4
|
+
import timezone from "dayjs/plugin/timezone";
|
|
5
|
+
import IconCalendar from "../../assets/icons/IconCalendar.svg";
|
|
6
|
+
import { SITE } from "@/config";
|
|
7
|
+
import { t } from "../../utils/i18n";
|
|
8
|
+
|
|
9
|
+
dayjs.extend(utc);
|
|
10
|
+
dayjs.extend(timezone);
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
class?: string;
|
|
14
|
+
size?: "sm" | "lg";
|
|
15
|
+
pubDatetime: string | Date;
|
|
16
|
+
timezone?: string;
|
|
17
|
+
modDatetime?: string | Date | null;
|
|
18
|
+
lang?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
pubDatetime,
|
|
23
|
+
modDatetime,
|
|
24
|
+
size = "sm",
|
|
25
|
+
class: className = "",
|
|
26
|
+
timezone: postTimezone,
|
|
27
|
+
lang,
|
|
28
|
+
} = Astro.props;
|
|
29
|
+
|
|
30
|
+
const currentLang = lang ?? SITE.lang;
|
|
31
|
+
const isModified = modDatetime && modDatetime > pubDatetime;
|
|
32
|
+
|
|
33
|
+
const datetime = dayjs(isModified ? modDatetime : pubDatetime).tz(
|
|
34
|
+
postTimezone || SITE.timezone
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const date =
|
|
38
|
+
currentLang === "zh"
|
|
39
|
+
? datetime.format("YYYY 年 M 月 D 日")
|
|
40
|
+
: datetime.format("D MMM, YYYY");
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<div class:list={["flex items-center gap-x-2 opacity-80", className]}>
|
|
44
|
+
<IconCalendar
|
|
45
|
+
class:list={[
|
|
46
|
+
"inline-block size-6 min-w-5.5",
|
|
47
|
+
{ "scale-90": size === "sm" },
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
{
|
|
51
|
+
isModified && (
|
|
52
|
+
<span class:list={["text-sm", { "sm:text-base": size === "lg" }]}>
|
|
53
|
+
{t("post.updated", currentLang)}
|
|
54
|
+
</span>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
<time
|
|
58
|
+
class:list={["text-sm", { "sm:text-base": size === "lg" }]}
|
|
59
|
+
datetime={datetime.toISOString()}>{date}</time
|
|
60
|
+
>
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
repo: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { repo: repoRaw } = Astro.props;
|
|
7
|
+
const repo = repoRaw.replace(/^https:\/\/github\.com\//, "");
|
|
8
|
+
const [owner, repoName] = repo.split("/");
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<github-card class="not-prose loading" data-repo={repo}>
|
|
12
|
+
<a
|
|
13
|
+
href={`https://github.com/${repo}`}
|
|
14
|
+
target="_blank"
|
|
15
|
+
rel="noopener noreferrer"
|
|
16
|
+
class="group flex flex-col gap-y-2 rounded-xl border px-4 py-3 transition-colors hover:bg-muted sm:px-5 sm:py-4"
|
|
17
|
+
>
|
|
18
|
+
<div class="flex items-center justify-between gap-x-2">
|
|
19
|
+
<div
|
|
20
|
+
class="flex min-w-0 flex-1 items-center gap-x-2 text-foreground group-hover:text-accent"
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
id="gh-avatar"
|
|
24
|
+
class="load-block me-2 size-7 flex-shrink-0 rounded-full bg-cover sm:size-8"
|
|
25
|
+
>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="min-w-0 flex-1">
|
|
28
|
+
<div class="flex items-center gap-x-1 max-sm:flex-wrap">
|
|
29
|
+
<span class="truncate text-base transition-colors sm:text-lg"
|
|
30
|
+
>{owner}</span
|
|
31
|
+
>
|
|
32
|
+
<span class="text-foreground-soft max-sm:hidden">/</span>
|
|
33
|
+
<span
|
|
34
|
+
class="truncate text-base font-medium transition-colors sm:text-lg max-sm:w-full"
|
|
35
|
+
>{repoName}</span
|
|
36
|
+
>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="flex-shrink-0 rounded-full bg-card p-1">
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
width="22"
|
|
44
|
+
height="22"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
fill="currentColor"
|
|
47
|
+
>
|
|
48
|
+
<path
|
|
49
|
+
d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.161 22 16.416 22 12c0-5.523-4.477-10-10-10z"
|
|
50
|
+
></path>
|
|
51
|
+
</svg>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<p id="gh-description" class="load-block text-sm sm:text-base"
|
|
55
|
+
>Loading...</p
|
|
56
|
+
>
|
|
57
|
+
<div class="flex items-center justify-between gap-x-2">
|
|
58
|
+
<div
|
|
59
|
+
class="load-block flex flex-wrap items-center gap-x-3 gap-y-1 sm:gap-x-5"
|
|
60
|
+
>
|
|
61
|
+
<div class="flex items-center gap-x-1.5 sm:gap-x-2">
|
|
62
|
+
<svg
|
|
63
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
64
|
+
width="18"
|
|
65
|
+
height="18"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
stroke-width="2"
|
|
70
|
+
stroke-linecap="round"
|
|
71
|
+
stroke-linejoin="round"
|
|
72
|
+
>
|
|
73
|
+
<polygon
|
|
74
|
+
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
|
75
|
+
></polygon>
|
|
76
|
+
</svg>
|
|
77
|
+
<span id="gh-stars" class="text-sm leading-tight sm:text-base"
|
|
78
|
+
>???</span
|
|
79
|
+
>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex items-center gap-x-1.5 sm:gap-x-2">
|
|
82
|
+
<svg
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
84
|
+
width="18"
|
|
85
|
+
height="18"
|
|
86
|
+
viewBox="0 0 24 24"
|
|
87
|
+
fill="none"
|
|
88
|
+
stroke="currentColor"
|
|
89
|
+
stroke-width="2"
|
|
90
|
+
stroke-linecap="round"
|
|
91
|
+
stroke-linejoin="round"
|
|
92
|
+
>
|
|
93
|
+
<circle cx="18" cy="18" r="3"></circle>
|
|
94
|
+
<circle cx="6" cy="6" r="3"></circle>
|
|
95
|
+
<path d="M13 6h3a2 2 0 0 1 2 2v7"></path>
|
|
96
|
+
<line x1="6" y1="9" x2="6" y2="21"></line>
|
|
97
|
+
</svg>
|
|
98
|
+
<span id="gh-forks" class="text-sm leading-tight sm:text-base"
|
|
99
|
+
>???</span
|
|
100
|
+
>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<span id="gh-language" class="load-block text-sm leading-tight sm:text-base"
|
|
104
|
+
>?????</span
|
|
105
|
+
>
|
|
106
|
+
</div>
|
|
107
|
+
</a>
|
|
108
|
+
</github-card>
|
|
109
|
+
|
|
110
|
+
<style>
|
|
111
|
+
@keyframes pulsate {
|
|
112
|
+
0% {
|
|
113
|
+
opacity: 1;
|
|
114
|
+
}
|
|
115
|
+
50% {
|
|
116
|
+
opacity: 0.4;
|
|
117
|
+
}
|
|
118
|
+
to {
|
|
119
|
+
opacity: 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
.loading .load-block {
|
|
123
|
+
color: transparent;
|
|
124
|
+
border-radius: 0.375rem;
|
|
125
|
+
background-color: var(--card);
|
|
126
|
+
animation: pulsate 2s infinite linear;
|
|
127
|
+
user-select: none;
|
|
128
|
+
}
|
|
129
|
+
.loading .load-block:nth-child(2) {
|
|
130
|
+
animation-delay: 1s;
|
|
131
|
+
}
|
|
132
|
+
:not(.loading) #gh-avatar {
|
|
133
|
+
background-color: var(--card);
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
136
|
+
|
|
137
|
+
<script>
|
|
138
|
+
interface GithubRepoData {
|
|
139
|
+
stargazers_count: number;
|
|
140
|
+
forks: number;
|
|
141
|
+
language: string;
|
|
142
|
+
owner: { avatar_url: string };
|
|
143
|
+
license?: { spdx_id: string };
|
|
144
|
+
description: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class GithubCardElement extends HTMLElement {
|
|
148
|
+
async connectedCallback() {
|
|
149
|
+
const repo = this.dataset.repo;
|
|
150
|
+
if (!repo) return;
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(`https://api.github.com/repos/${repo}`, {
|
|
153
|
+
referrerPolicy: "no-referrer",
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) return;
|
|
156
|
+
const data = (await res.json()) as GithubRepoData;
|
|
157
|
+
|
|
158
|
+
this.setText("#gh-stars", this.fmt(data.stargazers_count));
|
|
159
|
+
this.setText("#gh-forks", this.fmt(data.forks));
|
|
160
|
+
this.setText("#gh-language", data.language || "N/A");
|
|
161
|
+
this.setText(
|
|
162
|
+
"#gh-description",
|
|
163
|
+
data.description?.replace(/:[a-zA-Z0-9_]+:/g, "") ||
|
|
164
|
+
"No description"
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const avatar = this.querySelector("#gh-avatar") as HTMLElement;
|
|
168
|
+
if (avatar)
|
|
169
|
+
avatar.style.backgroundImage = `url(${data.owner.avatar_url})`;
|
|
170
|
+
|
|
171
|
+
this.classList.remove("loading");
|
|
172
|
+
} catch {
|
|
173
|
+
this.setText("#gh-description", "Failed to fetch data");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private setText(sel: string, text: string) {
|
|
178
|
+
const el = this.querySelector(sel) as HTMLElement;
|
|
179
|
+
if (el) el.textContent = text;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fmt(n: number) {
|
|
183
|
+
return Intl.NumberFormat("en-us", {
|
|
184
|
+
notation: "compact",
|
|
185
|
+
maximumFractionDigits: 1,
|
|
186
|
+
}).format(n);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
customElements.define("github-card", GithubCardElement);
|
|
191
|
+
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
|
|
4
|
+
type Props = { disabled?: boolean } & HTMLAttributes<"a">;
|
|
5
|
+
|
|
6
|
+
const { disabled, class: className, ...attrs } = Astro.props;
|
|
7
|
+
|
|
8
|
+
const Button = disabled ? "span" : "a";
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<Button
|
|
12
|
+
aria-disabled={disabled}
|
|
13
|
+
class:list={[
|
|
14
|
+
"group inline-flex items-center gap-1",
|
|
15
|
+
{ "hover:text-accent": !disabled },
|
|
16
|
+
className,
|
|
17
|
+
]}
|
|
18
|
+
{...attrs}
|
|
19
|
+
>
|
|
20
|
+
<slot />
|
|
21
|
+
</Button>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import IconHash from "../../assets/icons/IconHash.svg";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
tag: string;
|
|
6
|
+
tagName: string;
|
|
7
|
+
size?: "sm" | "lg";
|
|
8
|
+
lang?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const { tag, tagName, size = "lg", lang } = Astro.props;
|
|
12
|
+
const tagHref = lang ? `/${lang}/tags/${tag}/` : `/tags/${tag}/`;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<li>
|
|
16
|
+
<a
|
|
17
|
+
href={tagHref}
|
|
18
|
+
transition:name={tag}
|
|
19
|
+
class:list={[
|
|
20
|
+
"flex items-center gap-0.5",
|
|
21
|
+
"border-b-2 border-dashed border-foreground",
|
|
22
|
+
"hover:-mt-0.5 hover:border-accent hover:text-accent",
|
|
23
|
+
"focus-visible:border-none focus-visible:text-accent",
|
|
24
|
+
{ "text-sm": size === "sm" },
|
|
25
|
+
{ "text-lg": size === "lg" },
|
|
26
|
+
]}
|
|
27
|
+
>
|
|
28
|
+
<IconHash
|
|
29
|
+
class:list={[
|
|
30
|
+
"opacity-80",
|
|
31
|
+
{ "size-5": size === "lg" },
|
|
32
|
+
{ "size-4": size === "sm" },
|
|
33
|
+
]}
|
|
34
|
+
/>
|
|
35
|
+
{tagName}
|
|
36
|
+
</a>
|
|
37
|
+
</li>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import IconHash from "../../assets/icons/IconHash.svg";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
tag: string;
|
|
6
|
+
tagName: string;
|
|
7
|
+
count: number;
|
|
8
|
+
maxCount: number;
|
|
9
|
+
minCount: number;
|
|
10
|
+
lang?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const { tag, tagName, count, maxCount, minCount, lang = "zh" } = Astro.props;
|
|
14
|
+
|
|
15
|
+
// Calculate relative size based on count
|
|
16
|
+
const getTagSize = (currentCount: number, max: number, min: number): string => {
|
|
17
|
+
if (max === min) return "text-base";
|
|
18
|
+
|
|
19
|
+
// Normalize to 0-1 range
|
|
20
|
+
const normalized = (currentCount - min) / (max - min);
|
|
21
|
+
|
|
22
|
+
// Map to size classes with more granular steps
|
|
23
|
+
if (normalized > 0.9) return "text-3xl font-bold";
|
|
24
|
+
if (normalized > 0.8) return "text-2xl font-semibold";
|
|
25
|
+
if (normalized > 0.7) return "text-xl font-semibold";
|
|
26
|
+
if (normalized > 0.6) return "text-lg font-medium";
|
|
27
|
+
if (normalized > 0.5) return "text-base font-medium";
|
|
28
|
+
if (normalized > 0.4) return "text-sm font-medium";
|
|
29
|
+
if (normalized > 0.3) return "text-sm";
|
|
30
|
+
if (normalized > 0.2) return "text-xs";
|
|
31
|
+
return "text-xs";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Calculate opacity based on count
|
|
35
|
+
const getTagOpacity = (
|
|
36
|
+
currentCount: number,
|
|
37
|
+
max: number,
|
|
38
|
+
min: number
|
|
39
|
+
): string => {
|
|
40
|
+
if (max === min) return "opacity-90";
|
|
41
|
+
|
|
42
|
+
const normalized = (currentCount - min) / (max - min);
|
|
43
|
+
|
|
44
|
+
if (normalized > 0.7) return "opacity-100";
|
|
45
|
+
if (normalized > 0.5) return "opacity-90";
|
|
46
|
+
if (normalized > 0.3) return "opacity-80";
|
|
47
|
+
return "opacity-70";
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const sizeClass = getTagSize(count, maxCount, minCount);
|
|
51
|
+
const opacityClass = getTagOpacity(count, maxCount, minCount);
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<a
|
|
55
|
+
href={`/${lang}/tags/${tag}/`}
|
|
56
|
+
transition:name={tag}
|
|
57
|
+
class:list={[
|
|
58
|
+
"inline-flex items-center gap-0.5 rounded-md px-2 py-1 transition-all duration-300",
|
|
59
|
+
"hover:scale-105 hover:bg-accent/10 hover:text-accent",
|
|
60
|
+
"focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-dashed",
|
|
61
|
+
sizeClass,
|
|
62
|
+
opacityClass,
|
|
63
|
+
]}
|
|
64
|
+
title={`${count} post${count > 1 ? "s" : ""}`}
|
|
65
|
+
>
|
|
66
|
+
<IconHash class="size-4 opacity-70" />
|
|
67
|
+
<span>{tagName}</span>
|
|
68
|
+
<span class="text-xs opacity-60">({count})</span>
|
|
69
|
+
</a>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface TimelineEvent {
|
|
3
|
+
date: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
class?: string;
|
|
9
|
+
events: TimelineEvent[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { class: className, events, ...props } = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class={className} {...props}>
|
|
16
|
+
<ul class="ps-0 sm:ps-2">
|
|
17
|
+
{
|
|
18
|
+
events.map((event, index) => (
|
|
19
|
+
<li class="group relative flex list-none gap-x-3 rounded-full ps-0 sm:gap-x-2">
|
|
20
|
+
<span class="z-10 my-2 ms-2 h-3 w-3 min-w-3 rounded-full border-2 border-foreground-soft transition-transform group-hover:scale-125" />
|
|
21
|
+
{index !== events.length - 1 && (
|
|
22
|
+
<span
|
|
23
|
+
class="absolute start-[12px] top-[20px] w-1 bg-border"
|
|
24
|
+
style={{ height: "calc(100% - 4px)" }}
|
|
25
|
+
/>
|
|
26
|
+
)}
|
|
27
|
+
<div class="flex gap-2 max-sm:flex-col">
|
|
28
|
+
<samp class="w-fit grow-0 rounded-md py-1 text-sm max-sm:bg-card max-sm:px-2 sm:min-w-[82px] sm:text-right">
|
|
29
|
+
{event.date}
|
|
30
|
+
</samp>
|
|
31
|
+
<div>
|
|
32
|
+
<Fragment set:html={event.content} />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</li>
|
|
36
|
+
))
|
|
37
|
+
}
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { MarkdownLayoutProps } from "astro";
|
|
3
|
+
import Header from "../components/nav/Header.astro";
|
|
4
|
+
import Footer from "../components/nav/Footer.astro";
|
|
5
|
+
import Breadcrumb from "../components/nav/Breadcrumb.astro";
|
|
6
|
+
import Layout from "./Layout.astro";
|
|
7
|
+
import { SITE } from "@/config";
|
|
8
|
+
|
|
9
|
+
type Props = MarkdownLayoutProps<{ title: string }>;
|
|
10
|
+
|
|
11
|
+
const { frontmatter } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<Layout title={`${frontmatter.title} | ${SITE.title}`}>
|
|
15
|
+
<Header />
|
|
16
|
+
<Breadcrumb />
|
|
17
|
+
<main id="main-content" class="app-layout">
|
|
18
|
+
<section id="about" class="app-prose mb-28 max-w-app prose-img:border-0">
|
|
19
|
+
<h1 class="text-2xl tracking-wider sm:text-3xl">{frontmatter.title}</h1>
|
|
20
|
+
<slot />
|
|
21
|
+
</section>
|
|
22
|
+
</main>
|
|
23
|
+
<Footer />
|
|
24
|
+
</Layout>
|