@xingwangzhe/stalux 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/package.json +108 -0
  4. package/scripts/cli.mjs +103 -0
  5. package/src/assets/BiCcCircle.svg +1 -0
  6. package/src/assets/astro.svg +1 -0
  7. package/src/assets/background/README.md +3 -0
  8. package/src/assets/background/pattern-1.min.svg +1 -0
  9. package/src/assets/background/pattern-10.min.svg +1 -0
  10. package/src/assets/background/pattern-11.min.svg +1 -0
  11. package/src/assets/background/pattern-12.min.svg +1 -0
  12. package/src/assets/background/pattern-13.min.svg +1 -0
  13. package/src/assets/background/pattern-14.min.svg +1 -0
  14. package/src/assets/background/pattern-15.min.svg +1 -0
  15. package/src/assets/background/pattern-16.min.svg +1 -0
  16. package/src/assets/background/pattern-17.min.svg +1 -0
  17. package/src/assets/background/pattern-18.min.svg +1 -0
  18. package/src/assets/background/pattern-19.min.svg +1 -0
  19. package/src/assets/background/pattern-2.min.svg +1 -0
  20. package/src/assets/background/pattern-20.min.svg +1 -0
  21. package/src/assets/background/pattern-21.min.svg +1 -0
  22. package/src/assets/background/pattern-22.min.svg +1 -0
  23. package/src/assets/background/pattern-23.min.svg +1 -0
  24. package/src/assets/background/pattern-24.min.svg +1 -0
  25. package/src/assets/background/pattern-25.min.svg +1 -0
  26. package/src/assets/background/pattern-26.min.svg +1 -0
  27. package/src/assets/background/pattern-27.min.svg +1 -0
  28. package/src/assets/background/pattern-28.min.svg +1 -0
  29. package/src/assets/background/pattern-29.min.svg +1 -0
  30. package/src/assets/background/pattern-3.min.svg +1 -0
  31. package/src/assets/background/pattern-30.min.svg +1 -0
  32. package/src/assets/background/pattern-31.min.svg +1 -0
  33. package/src/assets/background/pattern-32.min.svg +1 -0
  34. package/src/assets/background/pattern-33.min.svg +1 -0
  35. package/src/assets/background/pattern-34.min.svg +1 -0
  36. package/src/assets/background/pattern-35.min.svg +1 -0
  37. package/src/assets/background/pattern-36.min.svg +1 -0
  38. package/src/assets/background/pattern-37.min.svg +1 -0
  39. package/src/assets/background/pattern-38.min.svg +1 -0
  40. package/src/assets/background/pattern-39.min.svg +1 -0
  41. package/src/assets/background/pattern-4.min.svg +1 -0
  42. package/src/assets/background/pattern-40.min.svg +1 -0
  43. package/src/assets/background/pattern-41.min.svg +1 -0
  44. package/src/assets/background/pattern-42.min.svg +1 -0
  45. package/src/assets/background/pattern-5.min.svg +1 -0
  46. package/src/assets/background/pattern-6.min.svg +1 -0
  47. package/src/assets/background/pattern-7.min.svg +1 -0
  48. package/src/assets/background/pattern-8.min.svg +1 -0
  49. package/src/assets/background/pattern-9.min.svg +1 -0
  50. package/src/assets/background.svg +1 -0
  51. package/src/assets/cc-icon.svg +4 -0
  52. package/src/assets/fonts/GoogleSansCode-Italic.woff2 +0 -0
  53. package/src/assets/fonts/GoogleSansCode.woff2 +0 -0
  54. package/src/assets/fonts/LXGWWenKai-Regular.ttf +0 -0
  55. package/src/assets/fonts/LXGWWenKai-Regular.woff2 +0 -0
  56. package/src/components/stalux/analytics/clarity.astro +25 -0
  57. package/src/components/stalux/analytics/google.astro +31 -0
  58. package/src/components/stalux/analytics/umami.astro +16 -0
  59. package/src/components/stalux/archives/archivesList.astro +116 -0
  60. package/src/components/stalux/archives/toArchive.astro +14 -0
  61. package/src/components/stalux/categories/categoriesCard.astro +49 -0
  62. package/src/components/stalux/categories/categoriesList.astro +17 -0
  63. package/src/components/stalux/common/author.astro +35 -0
  64. package/src/components/stalux/common/date.astro +59 -0
  65. package/src/components/stalux/common/search.astro +14 -0
  66. package/src/components/stalux/common/textType.astro +16 -0
  67. package/src/components/stalux/common/verCount.astro +10 -0
  68. package/src/components/stalux/footer/FooterBadges.astro +150 -0
  69. package/src/components/stalux/footer/FooterBeian.astro +91 -0
  70. package/src/components/stalux/footer/FooterCopyright.astro +40 -0
  71. package/src/components/stalux/footer/FooterStats.astro +108 -0
  72. package/src/components/stalux/footer/FooterThemeInfo.astro +67 -0
  73. package/src/components/stalux/layout/footer.astro +87 -0
  74. package/src/components/stalux/layout/head.astro +194 -0
  75. package/src/components/stalux/layout/listPage.astro +26 -0
  76. package/src/components/stalux/layout/navs.astro +174 -0
  77. package/src/components/stalux/links/linkCard.astro +50 -0
  78. package/src/components/stalux/links/linkList.astro +20 -0
  79. package/src/components/stalux/links/socialMedia.astro +102 -0
  80. package/src/components/stalux/posts/PhotoSwipeStyle.astro +3 -0
  81. package/src/components/stalux/posts/PostTaxonomy.astro +65 -0
  82. package/src/components/stalux/posts/WalineComment.astro +609 -0
  83. package/src/components/stalux/posts/postAuthor.astro +23 -0
  84. package/src/components/stalux/posts/postCC.astro +52 -0
  85. package/src/components/stalux/posts/postCard.astro +83 -0
  86. package/src/components/stalux/posts/postContent.astro +174 -0
  87. package/src/components/stalux/posts/postLeft.astro +41 -0
  88. package/src/components/stalux/posts/postList.astro +25 -0
  89. package/src/components/stalux/posts/postNavigation.astro +137 -0
  90. package/src/components/stalux/posts/postRight.astro +33 -0
  91. package/src/components/stalux/posts/postToc.astro +88 -0
  92. package/src/components/stalux/posts/randomPost.astro +134 -0
  93. package/src/components/stalux/tags/tagHeader.astro +50 -0
  94. package/src/components/stalux/tags/tagsCloud.astro +100 -0
  95. package/src/components/stalux/words/wordCard.astro +62 -0
  96. package/src/components/stalux/words/wordList.astro +70 -0
  97. package/src/config.ts +58 -0
  98. package/src/i18n/en.json +193 -0
  99. package/src/i18n/zh-CN.json +193 -0
  100. package/src/index.ts +215 -0
  101. package/src/internal/components-plugin.ts +54 -0
  102. package/src/internal/dev-toolbar.ts +33 -0
  103. package/src/internal/override.ts +99 -0
  104. package/src/internal/pagefind.ts +40 -0
  105. package/src/layouts/PostLayout.astro +90 -0
  106. package/src/layouts/Stalux.astro +91 -0
  107. package/src/pages/404.astro +103 -0
  108. package/src/pages/about.astro +59 -0
  109. package/src/pages/about.md.ts +15 -0
  110. package/src/pages/api/post.abbrlink.json.ts +20 -0
  111. package/src/pages/archives.astro +23 -0
  112. package/src/pages/archives.md.ts +24 -0
  113. package/src/pages/atom.xml.ts +29 -0
  114. package/src/pages/categories/[category].astro +35 -0
  115. package/src/pages/categories/[category].md.ts +34 -0
  116. package/src/pages/categories/index.astro +23 -0
  117. package/src/pages/categories/index.md.ts +27 -0
  118. package/src/pages/index.astro +30 -0
  119. package/src/pages/index.md.ts +18 -0
  120. package/src/pages/links.astro +23 -0
  121. package/src/pages/links.md.ts +18 -0
  122. package/src/pages/llms-full.txt.ts +25 -0
  123. package/src/pages/llms.txt.ts +26 -0
  124. package/src/pages/posts/[post].astro +69 -0
  125. package/src/pages/posts/[post].md.ts +109 -0
  126. package/src/pages/rss.xml.ts +29 -0
  127. package/src/pages/tags/[tag].astro +37 -0
  128. package/src/pages/tags/[tag].md.ts +34 -0
  129. package/src/pages/tags/index.astro +23 -0
  130. package/src/pages/tags/index.md.ts +27 -0
  131. package/src/pages/words.astro +24 -0
  132. package/src/pages/words.md.ts +14 -0
  133. package/src/plugins/feature-flags.ts +68 -0
  134. package/src/plugins/satteri-temml.ts +81 -0
  135. package/src/schemas/collections.ts +140 -0
  136. package/src/schemas/config.ts +226 -0
  137. package/src/scripts/background.ts +59 -0
  138. package/src/styles/base/init.css +286 -0
  139. package/src/styles/base/math.css +411 -0
  140. package/src/styles/components/archives/archivesList.module.css +378 -0
  141. package/src/styles/components/archives/toArchive.module.css +3 -0
  142. package/src/styles/components/categories/categoriesCard.module.css +87 -0
  143. package/src/styles/components/categories/categoriesList.module.css +27 -0
  144. package/src/styles/components/common/author.module.css +73 -0
  145. package/src/styles/components/common/date.module.css +15 -0
  146. package/src/styles/components/common/textType.module.css +10 -0
  147. package/src/styles/components/footer/footer.module.css +44 -0
  148. package/src/styles/components/layout/navs.module.css +250 -0
  149. package/src/styles/components/links/linkCard.module.css +99 -0
  150. package/src/styles/components/links/linkList.module.css +67 -0
  151. package/src/styles/components/links/socialLinks.module.css +22 -0
  152. package/src/styles/components/markdown.css +528 -0
  153. package/src/styles/components/posts/postAuthor.module.css +32 -0
  154. package/src/styles/components/posts/postCC.module.css +138 -0
  155. package/src/styles/components/posts/postCard.module.css +156 -0
  156. package/src/styles/components/posts/postCategories.module.css +50 -0
  157. package/src/styles/components/posts/postContent.module.css +230 -0
  158. package/src/styles/components/posts/postLayout.module.css +142 -0
  159. package/src/styles/components/posts/postLeft.module.css +38 -0
  160. package/src/styles/components/posts/postList.module.css +24 -0
  161. package/src/styles/components/posts/postNavigation.module.css +187 -0
  162. package/src/styles/components/posts/postRight.module.css +60 -0
  163. package/src/styles/components/posts/postSidebar.module.css +57 -0
  164. package/src/styles/components/posts/postTags.module.css +51 -0
  165. package/src/styles/components/posts/postTaxonomy.module.css +87 -0
  166. package/src/styles/components/posts/postToc.module.css +74 -0
  167. package/src/styles/components/posts/randomPost.module.css +131 -0
  168. package/src/styles/components/tags/tagHeader.module.css +44 -0
  169. package/src/styles/components/tags/tagsCloud.module.css +124 -0
  170. package/src/styles/components/words/wordCard.module.css +130 -0
  171. package/src/styles/components/words/wordList.module.css +39 -0
  172. package/src/styles/pages/archives.module.css +18 -0
  173. package/src/styles/pages/categories.module.css +13 -0
  174. package/src/styles/pages/home.module.css +51 -0
  175. package/src/styles/pages/listPage.module.css +13 -0
  176. package/src/styles/pages/tags.module.css +12 -0
  177. package/src/styles/pages/words.module.css +13 -0
  178. package/src/styles/shared/animations.css +87 -0
  179. package/src/styles/shared/stagger.css +20 -0
  180. package/src/utils/ai-discovery.ts +547 -0
  181. package/src/utils/badge-generator.ts +68 -0
  182. package/src/utils/cc.ts +46 -0
  183. package/src/utils/collections-stats.ts +48 -0
  184. package/src/utils/config-utils.ts +107 -0
  185. package/src/utils/count-words.ts +20 -0
  186. package/src/utils/dayjs.ts +106 -0
  187. package/src/utils/feed.ts +70 -0
  188. package/src/utils/i18n.ts +64 -0
  189. package/src/utils/lucide-icons.ts +52 -0
  190. package/src/utils/semantic-time.ts +11 -0
  191. package/src/utils/taxonomy.ts +44 -0
  192. package/src/utils/toc-scrollspy.ts +95 -0
  193. package/src/utils/waline.ts +27 -0
  194. package/src/utils/word-count-utils.ts +83 -0
  195. package/template/astro.config.mjs +51 -0
  196. package/template/package.json +27 -0
  197. package/template/public/robots.txt +3 -0
  198. package/template/src/content.config.ts +11 -0
  199. package/template/stalux/about/index.md +14 -0
  200. package/template/stalux/config/ai-discovery.yml +2 -0
  201. package/template/stalux/config/author.yml +4 -0
  202. package/template/stalux/config/comment.yml +5 -0
  203. package/template/stalux/config/footer.yml +13 -0
  204. package/template/stalux/config/head.yml +7 -0
  205. package/template/stalux/config/links.yml +4 -0
  206. package/template/stalux/config/media-links.yml +8 -0
  207. package/template/stalux/config/navs.yml +23 -0
  208. package/template/stalux/config/promote.yml +2 -0
  209. package/template/stalux/config/site.yml +7 -0
  210. package/template/stalux/config/typetexts.yml +5 -0
  211. package/template/stalux/posts/hello-stalux.md +47 -0
  212. package/template/stalux/words/_template.md +12 -0
  213. package/template/stalux/words/einstein-imagination.md +8 -0
  214. package/template/tsconfig.json +9 -0
@@ -0,0 +1,83 @@
1
+ ---
2
+ import style from "@styles/components/posts/postCard.module.css";
3
+ import { toMachineDateTime } from "@utils/semantic-time";
4
+ import { getCollection } from "astro:content";
5
+
6
+ import type { CollectionEntry } from "astro:content";
7
+ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
8
+
9
+ interface Props {
10
+ post: CollectionEntry<"posts">;
11
+ }
12
+
13
+ const post = Astro.props.post;
14
+ const configEntries = await getCollection("config");
15
+ const siteConfig = getSiteData(configEntries);
16
+ const timezone = siteConfig.timezone;
17
+
18
+ // 描述:使用文章自身的 desc
19
+ const description = post.data.desc || "";
20
+ ---
21
+
22
+ <a href={`/posts/${post.data.abbrlink}/`} class={style.post + " a-none"} data-astro-prefetch="viewport">
23
+ <div class={style.postContainer}>
24
+ {
25
+ post.data.cover && (
26
+ <div class={style.postCover}>
27
+ <img src={post.data.cover} alt={post.data.title} loading="lazy" />
28
+ </div>
29
+ )
30
+ }
31
+ <h2 class={style.postTitle}>{post.data.title}</h2>
32
+ <div class={style.time}>
33
+ {
34
+ post.data.date && (
35
+ <time
36
+ class={style.postDate}
37
+ datetime={timezone ? toMachineDateTime(post.data.date, timezone) : post.data.date}
38
+ >
39
+ {post.data.date}
40
+ </time>
41
+ )
42
+ }
43
+ {
44
+ post.data.updated && (
45
+ <time
46
+ class={style.postUpdated}
47
+ datetime={timezone ? toMachineDateTime(post.data.updated, timezone) : post.data.updated}
48
+ >
49
+ {post.data.updated}
50
+ </time>
51
+ )
52
+ }
53
+ </div>
54
+ <div class={style.postDescription}>{description}</div>
55
+ <div class={style.postMeta}>
56
+ <div class={style.postTags}>
57
+ {
58
+ post.data.tags?.map((tag) => (
59
+ <a class="a-none" href={`/tags/${tag}`} data-astro-prefetch>
60
+ <div class={style.postTag}>{tag}</div>
61
+ </a>
62
+ ))
63
+ }
64
+ </div>
65
+ <div class={style.postCategories}>
66
+ {
67
+ post.data.categories?.map((category) => (
68
+ <a class="a-none" href={`/categories/${category}`} data-astro-prefetch>
69
+ <div class={style.postCategory}>{category}</div>
70
+ </a>
71
+ ))
72
+ }
73
+ </div>
74
+ </div>
75
+ <a
76
+ href={`/posts/${post.data.abbrlink}`}
77
+ class={style.readMore + " a-none"}
78
+ data-astro-prefetch
79
+ >
80
+ Read More
81
+ </a>
82
+ </div>
83
+ </a>
@@ -0,0 +1,174 @@
1
+ ---
2
+ import { fade } from "astro:transitions";
3
+ import style from "@styles/components/posts/postContent.module.css";
4
+ import "@styles/components/markdown.css";
5
+ import { getCollection } from "astro:content";
6
+ import { createTranslator } from "@utils/i18n";
7
+ import { toMachineDateTime } from "@utils/semantic-time";
8
+ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
9
+
10
+ interface Props {
11
+ title: string;
12
+ date?: string;
13
+ updated?: string;
14
+ readingTime?: string;
15
+ readingMinutes?: number;
16
+ wordCount?: number;
17
+ hasImage?: boolean;
18
+ }
19
+
20
+ const props = Astro.props as Props;
21
+ const configEntries = await getCollection("config");
22
+ const siteConfig = getSiteData(configEntries);
23
+ const promoteConfig = getPromoteData(configEntries);
24
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
25
+ const timezone = siteConfig.timezone;
26
+
27
+ // 仅当开启 export_md 时,才展示“查看 Markdown 原文”按钮,并推导对应 .md 路径
28
+ // /about 等非文章页面固定不显示 Markdown 原文按钮
29
+ const isAboutPage = Astro.url.pathname.startsWith("/about");
30
+ const exportMd = Boolean(promoteConfig?.export_md) && !isAboutPage;
31
+ const mdSourceUrl = exportMd
32
+ ? Astro.url.pathname.replace(/\/$/, "") + ".md"
33
+ : "";
34
+ ---
35
+
36
+ <article
37
+ class={style.articleContent}
38
+ transition:name="post-content"
39
+ transition:animate={fade({ duration: "1.2s" })}>
40
+ <!-- Article Header -->
41
+ <header class={style.articleHeader}>
42
+ <h1 class={style.title}>{props.title}</h1>
43
+ <div class={style.metadata}>
44
+ {
45
+ props.date && (
46
+ <time class={style.metaItem} datetime={timezone ? toMachineDateTime(props.date, timezone) : props.date}>
47
+ {t("post.publishTime")}
48
+ {props.date}
49
+ </time>
50
+ )
51
+ }
52
+
53
+ {
54
+ props.updated && (
55
+ <time class={style.metaItem} datetime={timezone ? toMachineDateTime(props.updated, timezone) : props.updated}>
56
+ {t("post.updateTime")}
57
+ {props.updated}
58
+ </time>
59
+ )
60
+ }
61
+ </div>
62
+ {
63
+ <div class={style.pageStats}>
64
+ {(props.readingTime || props.readingMinutes) && (
65
+ <span class={style.readingTime}>
66
+ {t("post.readingTime")}{props.readingMinutes && props.readingMinutes > 0
67
+ ? (props.readingMinutes < 1
68
+ ? t("common.lessThanOneMinute")
69
+ : t("common.minute", { n: props.readingMinutes }))
70
+ : props.readingTime}
71
+ </span>
72
+ )}
73
+ {props.wordCount && props.wordCount > 0 && (
74
+ <span class={style.wordCount}>
75
+ {t("post.wordCount")}{props.wordCount}
76
+ </span>
77
+ )}
78
+ <span id="vercount_container_page_pv">
79
+ {t("post.viewCount")}
80
+ <span id="vercount_value_page_pv">Loading...</span>
81
+ </span>
82
+ </div>
83
+ }
84
+ {
85
+ exportMd && (
86
+ <div class={style.sourceLinkRow}>
87
+ <a
88
+ class={style.sourceLink}
89
+ href={mdSourceUrl}
90
+ target="_blank"
91
+ rel="noopener noreferrer"
92
+ >
93
+ {t("post.viewSource")}
94
+ </a>
95
+ </div>
96
+ )
97
+ }
98
+ </header>
99
+
100
+ <!-- Article Body -->
101
+ <section class={style.body} data-pagefind-body>
102
+ <slot />
103
+ </section>
104
+ </article>
105
+
106
+ {
107
+ props.hasImage && (
108
+ <script>
109
+ import PhotoSwipeLightbox from "photoswipe/lightbox";
110
+ import PhotoSwipe from "photoswipe";
111
+
112
+ // 选择器必须与 @xingwangzhe/satteri-photoswipe HAST 插件生成的 class 一致
113
+ const GALLERY_SELECTOR = "a.pswp-gallery-item";
114
+
115
+ async function enrichPhotoSwipeData() {
116
+ const container = document.querySelector("[data-pagefind-body]");
117
+ if (!container) return;
118
+
119
+ const links = container.querySelectorAll(GALLERY_SELECTOR);
120
+ await Promise.all(
121
+ [...links].map(async (link) => {
122
+ const img = link.querySelector("img");
123
+ if (!img) return;
124
+
125
+ const tempImg = new Image();
126
+ tempImg.src = img.currentSrc || img.src;
127
+ try {
128
+ await tempImg.decode();
129
+ } catch {
130
+ /* 解码失败时跳过尺寸写入 */
131
+ return;
132
+ }
133
+ link.dataset.pswpWidth = String(tempImg.naturalWidth);
134
+ link.dataset.pswpHeight = String(tempImg.naturalHeight);
135
+ }),
136
+ );
137
+ }
138
+
139
+ let lightbox = null;
140
+
141
+ async function initLightbox() {
142
+ // 销毁旧实例(View Transition 导航后,旧 DOM 已移除,需安全销毁)
143
+ if (lightbox) {
144
+ try {
145
+ lightbox.destroy();
146
+ } catch {
147
+ /* ignore */
148
+ }
149
+ lightbox = null;
150
+ }
151
+
152
+ await enrichPhotoSwipeData();
153
+
154
+ lightbox = new PhotoSwipeLightbox({
155
+ gallery: "[data-pagefind-body]",
156
+ children: GALLERY_SELECTOR,
157
+ pswpModule: PhotoSwipe,
158
+ initialZoomLevel: "fill",
159
+ });
160
+ lightbox.init();
161
+ }
162
+
163
+ // 1) View Transition 导航后触发
164
+ document.addEventListener("astro:page-load", initLightbox);
165
+
166
+ // 2) 首屏兜底:避免 astro:page-load 在监听器注册前已触发
167
+ if (document.readyState !== "loading") {
168
+ setTimeout(initLightbox, 0);
169
+ } else {
170
+ document.addEventListener("DOMContentLoaded", () => setTimeout(initLightbox, 0));
171
+ }
172
+ </script>
173
+ )
174
+ }
@@ -0,0 +1,41 @@
1
+ ---
2
+ import PostAuthor from "@components/stalux/posts/postAuthor.astro";
3
+ import style from "@styles/components/posts/postSidebar.module.css";
4
+ import SocialMedia from "@components/stalux/links/socialMedia.astro";
5
+ import RandomPost from "@components/stalux/posts/randomPost.astro";
6
+ import { fade } from "astro:transitions";
7
+ // 判断是否为文章页面
8
+ const isPostPage = Astro.url.pathname.includes("/posts/");
9
+ ---
10
+
11
+ <aside
12
+ class={style.sidebar}
13
+ transition:name="left-sidebar"
14
+ transition:animate={fade({ duration: "0.3s" })}
15
+ >
16
+ <PostAuthor />
17
+ <div class={style.sidebarContainer}>
18
+ <SocialMedia />
19
+ </div>
20
+ <div class={style.sidebarContainer}>
21
+ <RandomPost />
22
+ </div>
23
+
24
+ <style>
25
+ :global(.stalux-root) {
26
+ --sidebar-width: 280px;
27
+ }
28
+
29
+ @media (max-width: 1200px) {
30
+ :global(.stalux-root) {
31
+ --sidebar-width: 240px;
32
+ }
33
+ }
34
+
35
+ @media (max-width: 768px) {
36
+ :global(.stalux-root) {
37
+ --sidebar-width: 0;
38
+ }
39
+ }
40
+ </style>
41
+ </aside>
@@ -0,0 +1,25 @@
1
+ ---
2
+ import styles from "@styles/components/posts/postList.module.css";
3
+ import { getCollection, type CollectionEntry } from "astro:content";
4
+ import PostCard from "@components/stalux/posts/postCard.astro";
5
+
6
+ interface Props {
7
+ posts?: CollectionEntry<"posts">[];
8
+ }
9
+
10
+ const props = Astro.props as Props;
11
+ // Allow callers to pass filtered posts to avoid re-fetching the full list.
12
+ const posts = props.posts ?? (await getCollection("posts", ({ data }) => !data.draft));
13
+ ---
14
+
15
+ <div data-stalux="postlist" class={styles["container"]}>
16
+ <ul class={styles["post-list"]}>
17
+ {
18
+ posts.map((post) => (
19
+ <li>
20
+ <PostCard post={post} />
21
+ </li>
22
+ ))
23
+ }
24
+ </ul>
25
+ </div>
@@ -0,0 +1,137 @@
1
+ ---
2
+ import style from "@styles/components/posts/postNavigation.module.css";
3
+ import { getCollection } from "astro:content";
4
+ import { createTranslator } from "@utils/i18n";
5
+
6
+ interface Props {
7
+ prev?: { url: string; title: string };
8
+ next?: { url: string; title: string };
9
+ }
10
+
11
+ const props = Astro.props as Props;
12
+ const configEntries = await getCollection("config");
13
+ const siteConfig = (configEntries.find(e => e.id === "site")?.data ?? {}) as Record<string, unknown>;
14
+ const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
15
+ ---
16
+
17
+ {
18
+ (props.prev || props.next) && (
19
+ <nav class={style.postNavigation} aria-label="文章导航">
20
+ <div class={style.postNavigationInner}>
21
+ {props.prev ? (
22
+ <a
23
+ href={props.prev.url}
24
+ class={`${style.postNavLink} ${style.prevPost}`}
25
+ rel="prev"
26
+ data-astro-prefetch
27
+ >
28
+ <span class={style.postNavArrow}>
29
+ <svg
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ viewBox="0 0 24 24"
32
+ width="24"
33
+ height="24"
34
+ fill="none"
35
+ stroke="currentColor"
36
+ stroke-width="2"
37
+ stroke-linecap="round"
38
+ stroke-linejoin="round"
39
+ >
40
+ <path d="M19 12H5M12 19l-7-7 7-7" />
41
+ </svg>
42
+ </span>
43
+ <div class={style.postNavContent}>
44
+ <span class={style.postNavLabel}>{t("post.prevPost")}</span>
45
+ <span class={style.postNavTitle}>
46
+ {props.prev.title}
47
+ </span>
48
+ </div>
49
+ </a>
50
+ ) : (
51
+ <span
52
+ class={`${style.postNavLink} ${style.prevPost} ${style.disabled}`}
53
+ >
54
+ <span class={style.postNavArrow}>
55
+ <svg
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ viewBox="0 0 24 24"
58
+ width="24"
59
+ height="24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ stroke-width="2"
63
+ stroke-linecap="round"
64
+ stroke-linejoin="round"
65
+ >
66
+ <path d="M19 12H5M12 19l-7-7 7-7" />
67
+ </svg>
68
+ </span>
69
+ <div class={style.postNavContent}>
70
+ <span class={style.postNavLabel}>{t("post.prevPost")}</span>
71
+ <span class={style.postNavTitle}>
72
+ {t("post.noPrev")}
73
+ </span>
74
+ </div>
75
+ </span>
76
+ )}
77
+
78
+ {props.next ? (
79
+ <a
80
+ href={props.next.url}
81
+ class={`${style.postNavLink} ${style.nextPost}`}
82
+ rel="next"
83
+ data-astro-prefetch
84
+ >
85
+ <div class={style.postNavContent}>
86
+ <span class={style.postNavLabel}>{t("post.nextPost")}</span>
87
+ <span class={style.postNavTitle}>
88
+ {props.next.title}
89
+ </span>
90
+ </div>
91
+ <span class={style.postNavArrow}>
92
+ <svg
93
+ xmlns="http://www.w3.org/2000/svg"
94
+ viewBox="0 0 24 24"
95
+ width="24"
96
+ height="24"
97
+ fill="none"
98
+ stroke="currentColor"
99
+ stroke-width="2"
100
+ stroke-linecap="round"
101
+ stroke-linejoin="round"
102
+ >
103
+ <path d="M5 12h14M12 5l7 7-7 7" />
104
+ </svg>
105
+ </span>
106
+ </a>
107
+ ) : (
108
+ <span
109
+ class={`${style.postNavLink} ${style.nextPost} ${style.disabled}`}
110
+ >
111
+ <div class={style.postNavContent}>
112
+ <span class={style.postNavLabel}>{t("post.nextPost")}</span>
113
+ <span class={style.postNavTitle}>
114
+ {t("post.noNext")}
115
+ </span>
116
+ </div>
117
+ <span class={style.postNavArrow}>
118
+ <svg
119
+ xmlns="http://www.w3.org/2000/svg"
120
+ viewBox="0 0 24 24"
121
+ width="24"
122
+ height="24"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ stroke-width="2"
126
+ stroke-linecap="round"
127
+ stroke-linejoin="round"
128
+ >
129
+ <path d="M5 12h14M12 5l7 7-7 7" />
130
+ </svg>
131
+ </span>
132
+ </span>
133
+ )}
134
+ </div>
135
+ </nav>
136
+ )
137
+ }
@@ -0,0 +1,33 @@
1
+ ---
2
+ import style from "@styles/components/posts/postSidebar.module.css";
3
+ import Date from "@components/stalux/common/date.astro";
4
+ import PostToc from "@components/stalux/posts/postToc.astro";
5
+ import PostTaxonomy from "@components/stalux/posts/PostTaxonomy.astro";
6
+ import { fade } from "astro:transitions";
7
+ interface Props {
8
+ headings?: Array<{ depth: number; slug: string; text: string }>;
9
+ tags?: string[];
10
+ categories?: string[];
11
+ }
12
+
13
+ const { headings = [], tags = [], categories = [] } = Astro.props;
14
+ ---
15
+
16
+ <aside
17
+ class={style.sidebarRight}
18
+ transition:name="right-sidebar"
19
+ transition:animate={fade({ duration: "0.3s" })}
20
+ >
21
+ <div class={style.sidebarContainer}>
22
+ <Date />
23
+ </div>
24
+ <div class={style.sidebarContainer}>
25
+ <PostToc headings={headings} />
26
+ </div>
27
+ <div class={style.sidebarContainer}>
28
+ <PostTaxonomy kind="tag" items={tags} />
29
+ </div>
30
+ <div class={style.sidebarContainer}>
31
+ <PostTaxonomy kind="category" items={categories} />
32
+ </div>
33
+ </aside>
@@ -0,0 +1,88 @@
1
+ ---
2
+ import { fade } from "astro:transitions";
3
+ import style from "@styles/components/posts/postToc.module.css";
4
+ import { getCollection } from "astro:content";
5
+ import { createTranslator } from "@utils/i18n";
6
+ import { initTocScrollSpy } from "@utils/toc-scrollspy";
7
+
8
+ interface TocHeading {
9
+ depth: number;
10
+ slug: string;
11
+ text: string;
12
+ }
13
+
14
+ interface Props {
15
+ headings?: TocHeading[];
16
+ title?: string;
17
+ }
18
+
19
+ const configEntries = await getCollection("config");
20
+ const siteConfig = (configEntries.find(e => e.id === "site")?.data ?? {}) as Record<string, unknown>;
21
+ const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
22
+ const { headings = [], title = t("taxonomy.toc") } = Astro.props;
23
+
24
+ const tocItems = headings
25
+ .filter((item) => item && item.slug && item.text)
26
+ .map((item) => ({
27
+ ...item,
28
+ depth: Math.min(Math.max(item.depth ?? 1, 1), 6),
29
+ }));
30
+
31
+ // 定义目录内容的过渡动画 - 平滑褶皱效果
32
+ const tocContentAnimation = {
33
+ old: {
34
+ name: "fadeSlideDown",
35
+ duration: "0.3s",
36
+ easing: "ease-out",
37
+ },
38
+ new: {
39
+ name: "fadeSlideDown",
40
+ duration: "0.3s",
41
+ easing: "ease-out",
42
+ },
43
+ };
44
+ ---
45
+
46
+ <nav transition:name="toc-container" data-toc-active-class={style.active} aria-label={t("taxonomy.toc")}>
47
+ <h3 class={style.title}>{title}</h3>
48
+ {
49
+ tocItems.length > 0 ? (
50
+ <ul
51
+ class={style.list}
52
+ transition:name="toc-content"
53
+ transition:animate={fade({ duration: "0.3s" })}
54
+ >
55
+ {tocItems.map((heading) => (
56
+ <li>
57
+ <a
58
+ class={`${style.item} ${style[`depth-${heading.depth}`] || ""} a-none`}
59
+ href={`#${heading.slug}`}
60
+ data-toc-link
61
+ >
62
+ {heading.text}
63
+ </a>
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ ) : (
68
+ <div class={style.empty}>{t("taxonomy.noToc")}</div>
69
+ )
70
+ }
71
+ </nav>
72
+
73
+ <script>
74
+ import { initTocScrollSpy } from "@utils/toc-scrollspy";
75
+
76
+ document.addEventListener("astro:page-load", () => {
77
+ const container = document.querySelector("[data-toc-active-class]");
78
+ const activeClass = container?.getAttribute("data-toc-active-class");
79
+ if (activeClass) initTocScrollSpy(activeClass);
80
+ });
81
+ </script>
82
+
83
+ <style is:global>
84
+ @keyframes fadeSlideDown {
85
+ from { opacity: 0; transform: translateY(-8px); }
86
+ to { opacity: 1; transform: translateY(0); }
87
+ }
88
+ </style>