@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.
Files changed (103) hide show
  1. package/README.md +29 -0
  2. package/package.json +41 -0
  3. package/src/assets/icons/IconArchive.svg +1 -0
  4. package/src/assets/icons/IconArrowLeft.svg +1 -0
  5. package/src/assets/icons/IconArrowNarrowUp.svg +1 -0
  6. package/src/assets/icons/IconArrowRight.svg +1 -0
  7. package/src/assets/icons/IconArticle.svg +1 -0
  8. package/src/assets/icons/IconBrandX.svg +1 -0
  9. package/src/assets/icons/IconCalendar.svg +1 -0
  10. package/src/assets/icons/IconChevronLeft.svg +1 -0
  11. package/src/assets/icons/IconChevronRight.svg +1 -0
  12. package/src/assets/icons/IconEdit.svg +1 -0
  13. package/src/assets/icons/IconFacebook.svg +1 -0
  14. package/src/assets/icons/IconGitHub.svg +1 -0
  15. package/src/assets/icons/IconHash.svg +1 -0
  16. package/src/assets/icons/IconHome.svg +1 -0
  17. package/src/assets/icons/IconLinkedin.svg +1 -0
  18. package/src/assets/icons/IconMail.svg +1 -0
  19. package/src/assets/icons/IconMenuDeep.svg +1 -0
  20. package/src/assets/icons/IconMoon.svg +1 -0
  21. package/src/assets/icons/IconPinterest.svg +1 -0
  22. package/src/assets/icons/IconProject.svg +1 -0
  23. package/src/assets/icons/IconRss.svg +1 -0
  24. package/src/assets/icons/IconSearch.svg +1 -0
  25. package/src/assets/icons/IconSeries.svg +1 -0
  26. package/src/assets/icons/IconSunHigh.svg +1 -0
  27. package/src/assets/icons/IconTag.svg +1 -0
  28. package/src/assets/icons/IconTelegram.svg +1 -0
  29. package/src/assets/icons/IconUser.svg +1 -0
  30. package/src/assets/icons/IconWhatsapp.svg +1 -0
  31. package/src/assets/icons/IconX.svg +1 -0
  32. package/src/components/ai/AIChatWidget.astro +377 -0
  33. package/src/components/blog/Comments.astro +527 -0
  34. package/src/components/blog/Copyright.astro +152 -0
  35. package/src/components/blog/EditPost.astro +59 -0
  36. package/src/components/blog/FloatingTOC.astro +260 -0
  37. package/src/components/blog/InlineTOC.astro +223 -0
  38. package/src/components/blog/PostActions.astro +306 -0
  39. package/src/components/blog/RelatedPosts.astro +60 -0
  40. package/src/components/blog/SeriesNav.astro +176 -0
  41. package/src/components/blog/ShareLinks.astro +26 -0
  42. package/src/components/nav/BackButton.astro +37 -0
  43. package/src/components/nav/BackToTopButton.astro +223 -0
  44. package/src/components/nav/Breadcrumb.astro +57 -0
  45. package/src/components/nav/FloatingActions.astro +206 -0
  46. package/src/components/nav/Footer.astro +107 -0
  47. package/src/components/nav/Header.astro +252 -0
  48. package/src/components/nav/Pagination.astro +45 -0
  49. package/src/components/social/Socials.astro +19 -0
  50. package/src/components/social/Sponsors.astro +34 -0
  51. package/src/components/social/Sponsorship.astro +44 -0
  52. package/src/components/ui/Alert.astro +28 -0
  53. package/src/components/ui/Card.astro +206 -0
  54. package/src/components/ui/Collapse.astro +82 -0
  55. package/src/components/ui/ColorPreview.astro +29 -0
  56. package/src/components/ui/Datetime.astro +61 -0
  57. package/src/components/ui/GithubCard.astro +191 -0
  58. package/src/components/ui/LinkButton.astro +21 -0
  59. package/src/components/ui/Tag.astro +37 -0
  60. package/src/components/ui/TagCloud.astro +69 -0
  61. package/src/components/ui/Timeline.astro +39 -0
  62. package/src/layouts/AboutLayout.astro +24 -0
  63. package/src/layouts/Layout.astro +329 -0
  64. package/src/layouts/Main.astro +42 -0
  65. package/src/layouts/PostDetails.astro +445 -0
  66. package/src/plugins/rehype-autolink-headings.ts +46 -0
  67. package/src/plugins/rehype-external-links.ts +35 -0
  68. package/src/plugins/rehype-table-scroll.ts +35 -0
  69. package/src/plugins/remark-add-zoomable.ts +28 -0
  70. package/src/plugins/remark-reading-time.ts +18 -0
  71. package/src/plugins/shiki-transformers.ts +212 -0
  72. package/src/scripts/lightbox.ts +63 -0
  73. package/src/scripts/reading-position.ts +56 -0
  74. package/src/scripts/theme-utils.ts +19 -0
  75. package/src/scripts/theme.ts +179 -0
  76. package/src/scripts/web-vitals.ts +96 -0
  77. package/src/styles/code-blocks.css +194 -0
  78. package/src/styles/components.css +252 -0
  79. package/src/styles/global.css +403 -0
  80. package/src/styles/typography.css +149 -0
  81. package/src/types.ts +89 -0
  82. package/src/utils/generateOgImages.ts +38 -0
  83. package/src/utils/getCategoryPath.ts +23 -0
  84. package/src/utils/getPath.ts +52 -0
  85. package/src/utils/getPostsByCategory.ts +17 -0
  86. package/src/utils/getPostsByGroupCondition.ts +25 -0
  87. package/src/utils/getPostsByLang.ts +27 -0
  88. package/src/utils/getPostsByTag.ts +10 -0
  89. package/src/utils/getReadingTime.ts +33 -0
  90. package/src/utils/getRelatedPosts.ts +59 -0
  91. package/src/utils/getSeriesData.ts +57 -0
  92. package/src/utils/getSortedPosts.ts +18 -0
  93. package/src/utils/getTagsWithCount.ts +38 -0
  94. package/src/utils/getUniqueCategories.ts +81 -0
  95. package/src/utils/getUniqueTags.ts +23 -0
  96. package/src/utils/i18n.ts +249 -0
  97. package/src/utils/loadGoogleFont.ts +38 -0
  98. package/src/utils/og-templates/post.js +229 -0
  99. package/src/utils/og-templates/site.js +128 -0
  100. package/src/utils/pathUtils.ts +17 -0
  101. package/src/utils/postFilter.ts +11 -0
  102. package/src/utils/slugify.ts +23 -0
  103. package/src/utils/toc.ts +27 -0
@@ -0,0 +1,329 @@
1
+ ---
2
+ import { ClientRouter } from "astro:transitions";
3
+ import { PUBLIC_GOOGLE_SITE_VERIFICATION } from "astro:env/client";
4
+ import { SITE } from "@/config";
5
+ import FloatingActions from "../components/nav/FloatingActions.astro";
6
+ import AIChatWidget from "../components/ai/AIChatWidget.astro";
7
+ import "../styles/global.css";
8
+
9
+ type Props = {
10
+ title?: string;
11
+ author?: string;
12
+ profile?: string;
13
+ description?: string;
14
+ ogImage?: string;
15
+ canonicalURL?: string;
16
+ pubDatetime?: Date;
17
+ modDatetime?: Date | null;
18
+ scrollSmooth?: boolean;
19
+ lang?: string;
20
+ tags?: string[];
21
+ };
22
+
23
+ const {
24
+ title = SITE.title,
25
+ lang = SITE.lang,
26
+ author = SITE.author,
27
+ profile = SITE.profile,
28
+ description = SITE.desc,
29
+ ogImage = SITE.ogImage ? `/${SITE.ogImage}` : "/og.png",
30
+ canonicalURL = new URL(Astro.url.pathname, Astro.url),
31
+ pubDatetime,
32
+ modDatetime,
33
+ scrollSmooth = false,
34
+ tags = [],
35
+ } = Astro.props;
36
+
37
+ const socialImageURL = new URL(ogImage, Astro.url);
38
+
39
+ const pathname = Astro.url.pathname;
40
+ const altLang = lang === "zh" ? "en" : "zh";
41
+ const altPath = pathname.startsWith(`/${lang}/`)
42
+ ? pathname.replace(`/${lang}/`, `/${altLang}/`)
43
+ : null;
44
+ const altURL = altPath ? new URL(altPath, Astro.url) : null;
45
+
46
+ const structuredData = pubDatetime
47
+ ? {
48
+ "@context": "https://schema.org",
49
+ "@type": "BlogPosting",
50
+ headline: `${title}`,
51
+ description: `${description}`,
52
+ image: `${socialImageURL}`,
53
+ datePublished: pubDatetime.toISOString(),
54
+ ...(modDatetime && { dateModified: modDatetime.toISOString() }),
55
+ author: [
56
+ {
57
+ "@type": "Person",
58
+ name: `${author}`,
59
+ ...(profile && { url: profile }),
60
+ },
61
+ ],
62
+ publisher: {
63
+ "@type": "Organization",
64
+ name: SITE.title,
65
+ url: SITE.website,
66
+ },
67
+ mainEntityOfPage: {
68
+ "@type": "WebPage",
69
+ "@id": `${canonicalURL}`,
70
+ },
71
+ }
72
+ : {
73
+ "@context": "https://schema.org",
74
+ "@type": "WebSite",
75
+ name: SITE.title,
76
+ description: `${description}`,
77
+ url: SITE.website,
78
+ potentialAction: {
79
+ "@type": "SearchAction",
80
+ target: `${SITE.website}search?q={search_term_string}`,
81
+ "query-input": "required name=search_term_string",
82
+ },
83
+ };
84
+ ---
85
+
86
+ <!doctype html>
87
+ <html
88
+ dir={SITE.dir}
89
+ lang={lang ?? "zh"}
90
+ class={`${scrollSmooth && "scroll-smooth"}`}
91
+ >
92
+ <head>
93
+ <meta charset="UTF-8" />
94
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
95
+ <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
96
+ <!-- PWA manifest loaded asynchronously to avoid render blocking -->
97
+ <script is:inline>
98
+ // Defer manifest registration using requestIdleCallback
99
+ if ('requestIdleCallback' in window) {
100
+ requestIdleCallback(() => {
101
+ const link = document.createElement('link');
102
+ link.rel = 'manifest';
103
+ link.href = '/manifest.json';
104
+ document.head.appendChild(link);
105
+ });
106
+ } else {
107
+ // Fallback for browsers without requestIdleCallback
108
+ setTimeout(() => {
109
+ const link = document.createElement('link');
110
+ link.rel = 'manifest';
111
+ link.href = '/manifest.json';
112
+ document.head.appendChild(link);
113
+ }, 100);
114
+ }
115
+ </script>
116
+ <link rel="canonical" href={canonicalURL} />
117
+ <meta name="generator" content={Astro.generator} />
118
+
119
+ <!-- KaTeX CSS for math equations — loaded async to avoid render blocking -->
120
+ <link
121
+ rel="preload"
122
+ as="style"
123
+ href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css"
124
+ integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+"
125
+ crossorigin="anonymous"
126
+ data-preload-style
127
+ />
128
+ <script is:inline>
129
+ // Convert preload links to stylesheets after load
130
+ document.querySelectorAll('link[data-preload-style]').forEach(link => {
131
+ link.onload = function() { this.rel = 'stylesheet'; };
132
+ });
133
+ </script>
134
+ <noscript>
135
+ <link
136
+ rel="stylesheet"
137
+ href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css"
138
+ integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+"
139
+ crossorigin="anonymous"
140
+ />
141
+ </noscript>
142
+
143
+ <!-- General Meta Tags -->
144
+ <title>{title}</title>
145
+ <meta name="title" content={title} />
146
+ <meta name="description" content={description} />
147
+ <meta name="author" content={author} />
148
+ <link rel="sitemap" href="/sitemap-index.xml" />
149
+ <link
150
+ rel="alternate"
151
+ type="application/rss+xml"
152
+ title={SITE.title}
153
+ href="/rss.xml"
154
+ />
155
+
156
+ <!-- hreflang for multilingual SEO -->
157
+ {
158
+ altURL && (
159
+ <>
160
+ <link
161
+ rel="alternate"
162
+ hreflang={lang === "zh" ? "zh-Hans" : "en"}
163
+ href={canonicalURL}
164
+ />
165
+ <link
166
+ rel="alternate"
167
+ hreflang={altLang === "zh" ? "zh-Hans" : "en"}
168
+ href={altURL}
169
+ />
170
+ <link
171
+ rel="alternate"
172
+ hreflang="x-default"
173
+ href={
174
+ new URL(`/zh${pathname.replace(/^\/(zh|en)/, "")}`, Astro.url)
175
+ }
176
+ />
177
+ </>
178
+ )
179
+ }
180
+
181
+ <!-- Open Graph / Facebook -->
182
+ <meta property="og:type" content="article" />
183
+ <meta property="og:site_name" content={SITE.title} />
184
+ <meta property="og:title" content={title} />
185
+ <meta property="og:description" content={description} />
186
+ <meta property="og:url" content={canonicalURL} />
187
+ <meta property="og:image" content={socialImageURL} />
188
+ <meta property="og:image:alt" content={title} />
189
+ <meta property="og:locale" content={lang === "zh" ? "zh_CN" : "en_US"} />
190
+
191
+ <!-- Article Published/Modified time -->
192
+ {
193
+ pubDatetime && (
194
+ <meta
195
+ property="article:published_time"
196
+ content={pubDatetime.toISOString()}
197
+ />
198
+ )
199
+ }
200
+ {
201
+ modDatetime && (
202
+ <meta
203
+ property="article:modified_time"
204
+ content={modDatetime.toISOString()}
205
+ />
206
+ )
207
+ }
208
+
209
+ <!-- Article Tags -->
210
+ {
211
+ tags.length > 0 &&
212
+ tags.map(tag => <meta property="article:tag" content={tag} />)
213
+ }
214
+
215
+ <!-- Twitter -->
216
+ <meta name="twitter:card" content="summary_large_image" />
217
+ <meta name="twitter:url" content={canonicalURL} />
218
+ <meta name="twitter:title" content={title} />
219
+ <meta name="twitter:description" content={description} />
220
+ <meta name="twitter:image" content={socialImageURL} />
221
+ <meta name="twitter:image:alt" content={title} />
222
+
223
+ <!-- Google JSON-LD Structured data -->
224
+ <script
225
+ type="application/ld+json"
226
+ is:inline
227
+ set:html={JSON.stringify(structuredData)}
228
+ />
229
+
230
+ <!-- Enable RSS feed auto-discovery -->
231
+ <link
232
+ rel="alternate"
233
+ type="application/rss+xml"
234
+ title={SITE.title}
235
+ href={new URL("rss.xml", Astro.site)}
236
+ />
237
+ {
238
+ lang && (
239
+ <link
240
+ rel="alternate"
241
+ type="application/rss+xml"
242
+ title={`${SITE.title} (${lang === "zh" ? "中文" : "English"})`}
243
+ href={new URL(`${lang}/rss.xml`, Astro.site)}
244
+ />
245
+ )
246
+ }
247
+
248
+ <meta name="theme-color" content="" />
249
+
250
+ {
251
+ // If PUBLIC_GOOGLE_SITE_VERIFICATION is set in the environment variable,
252
+ // include google-site-verification tag in the heading
253
+ // Learn more: https://support.google.com/webmasters/answer/9008080#meta_tag_verification&zippy=%2Chtml-tag
254
+ PUBLIC_GOOGLE_SITE_VERIFICATION && (
255
+ <meta
256
+ name="google-site-verification"
257
+ content={PUBLIC_GOOGLE_SITE_VERIFICATION}
258
+ />
259
+ )
260
+ }
261
+
262
+ <ClientRouter />
263
+
264
+ {/* Umami Analytics */}
265
+ {
266
+ SITE.umami?.enabled && SITE.umami.websiteId && (
267
+ <script
268
+ is:inline
269
+ defer
270
+ src={SITE.umami.src}
271
+ data-website-id={SITE.umami.websiteId}
272
+ />
273
+ )
274
+ }
275
+
276
+ <!-- Minimal inline script to prevent FOUC - sets theme immediately -->
277
+ <script is:inline>
278
+ (function () {
279
+ const initialColorScheme = ""; // "light" | "dark"
280
+ const currentTheme = localStorage.getItem("theme");
281
+
282
+ function getPreferTheme() {
283
+ if (currentTheme) return currentTheme;
284
+ if (initialColorScheme) return initialColorScheme;
285
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
286
+ ? "dark"
287
+ : "light";
288
+ }
289
+
290
+ const themeValue = getPreferTheme();
291
+
292
+ // Set theme immediately to prevent flash
293
+ document.firstElementChild?.setAttribute("data-theme", themeValue);
294
+
295
+ // Export minimal API for external script
296
+ window.theme = {
297
+ themeValue: themeValue,
298
+ getTheme: () => window.theme.themeValue,
299
+ setTheme: val => {
300
+ window.theme.themeValue = val;
301
+ },
302
+ };
303
+ })();
304
+ </script>
305
+ </head>
306
+ <body>
307
+ <slot />
308
+ <FloatingActions />
309
+ <AIChatWidget lang={lang} />
310
+
311
+ <script>
312
+ import '../scripts/theme.ts';
313
+ </script>
314
+ <script>
315
+ import '../scripts/lightbox.ts';
316
+ </script>
317
+ <script>
318
+ import '../scripts/reading-position.ts';
319
+ </script>
320
+ <script>
321
+ import '../scripts/web-vitals.ts';
322
+ </script>
323
+ <script is:inline>
324
+ if ("serviceWorker" in navigator) {
325
+ navigator.serviceWorker.register("/sw.js").catch(function () {});
326
+ }
327
+ </script>
328
+ </body>
329
+ </html>
@@ -0,0 +1,42 @@
1
+ ---
2
+ import Breadcrumb from "../components/nav/Breadcrumb.astro";
3
+ import { SITE } from "@/config";
4
+
5
+ type StringTitle = { pageTitle: string };
6
+ type ArrayTitle = { pageTitle: [string, string]; titleTransition: string };
7
+
8
+ type Props = (StringTitle | ArrayTitle) & { pageDesc?: string };
9
+
10
+ const { props } = Astro;
11
+
12
+ const backUrl = SITE.showBackButton ? Astro.url.pathname : "/";
13
+ ---
14
+
15
+ <Breadcrumb />
16
+ <main data-backUrl={backUrl} id="main-content" class="app-layout pb-4">
17
+ {
18
+ "titleTransition" in props ? (
19
+ <h1 class="text-2xl font-semibold sm:text-3xl">
20
+ {props.pageTitle[0]}
21
+ <span transition:name={props.titleTransition}>
22
+ {props.pageTitle[1]}
23
+ </span>
24
+ </h1>
25
+ ) : (
26
+ <h1 class="text-2xl font-semibold sm:text-3xl">{props.pageTitle}</h1>
27
+ )
28
+ }
29
+ <p class="mt-2 mb-6 italic">{props.pageDesc}</p>
30
+ <slot />
31
+ </main>
32
+
33
+ <script>
34
+ document.addEventListener("astro:page-load", () => {
35
+ const mainContent: HTMLElement | null =
36
+ document.querySelector("#main-content");
37
+ const backUrl = mainContent?.dataset?.backurl;
38
+ if (backUrl) {
39
+ sessionStorage.setItem("backUrl", backUrl);
40
+ }
41
+ });
42
+ </script>