@xingwangzhe/stalux 1.20.3 → 1.21.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.20.3",
3
+ "version": "1.21.1",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -84,6 +84,7 @@
84
84
  "@waline/client": "^3.15.2",
85
85
  "@xingwangzhe/satteri-mermaid": "0.7.3",
86
86
  "@xingwangzhe/satteri-photoswipe": "^0.2.2",
87
+ "@xingwangzhe/stalux": "1.21.0",
87
88
  "@xingwangzhe/tags-cloud": "^1.2.5",
88
89
  "animejs": "^4.5.0",
89
90
  "astro-expressive-code": "^0.44.1",
@@ -8,13 +8,21 @@ import BingClarity from "@components/stalux/analytics/clarity.astro";
8
8
  import "@styles/base/init.css";
9
9
  import Umami from "@components/stalux/analytics/umami.astro";
10
10
  import { langToOpenGraphLocale } from "@utils/i18n";
11
- import { getAuthorData, getHeadData, getPromoteData, getSiteData } from "@utils/config-utils";
11
+ import {
12
+ getAuthorData,
13
+ getHeadData,
14
+ getMediaLinksData,
15
+ getPromoteData,
16
+ getSiteData,
17
+ } from "@utils/config-utils";
18
+ import { getRouteKind } from "@utils/view-transitions";
12
19
 
13
20
  const configEntries = await getCollection("config");
14
21
  const siteConfig = getSiteData(configEntries);
15
22
  const authorConfig = getAuthorData(configEntries);
16
23
  const headConfig = getHeadData(configEntries);
17
24
  const promoteConfig = getPromoteData(configEntries);
25
+ const mediaLinksConfig = getMediaLinksData(configEntries);
18
26
 
19
27
  // Twitter 卡片类型定义
20
28
  type TwitterCardType = "summary" | "summary_large_image";
@@ -34,21 +42,31 @@ interface Props {
34
42
  }
35
43
 
36
44
  const props = Astro.props as Props;
37
- const effectiveTitle = props.title;
45
+ const effectiveTitle = siteConfig.seoTitle || props.title;
38
46
  const effectiveDescription = props.description;
39
47
  const isArticle = props.isArticle ?? false;
40
48
 
41
49
  const canonicalBase = siteConfig.canonical || siteConfig.url;
50
+ const siteUrl = new URL(canonicalBase || Astro.url.origin).href.replace(/\/$/, "");
51
+ const siteName = siteConfig.title || effectiveTitle;
42
52
  const canonicalUrl = canonicalBase
43
- ? new URL(Astro.url.pathname, canonicalBase).href
44
- : Astro.url.href;
53
+ ? new URL(Astro.url.pathname, `${canonicalBase.replace(/\/$/, "")}/`).href
54
+ : new URL(Astro.url.pathname, Astro.url).href;
45
55
  const faviconPath = siteConfig.favicon || "/favicon.ico";
46
56
  // 社交分享图片 / JSON-LD image 必须用绝对 URL
47
- const baseImgUrl = canonicalBase.replace(/\/$/, "");
48
- const toAbsolute = (p: string) => !p || p.startsWith("http") ? p : baseImgUrl + (p.startsWith("/") ? p : "/" + p);
57
+ const baseImgUrl = canonicalBase?.replace(/\/$/, "") || Astro.url.origin;
58
+ const toAbsolute = (value?: string) => {
59
+ if (!value) return undefined;
60
+ if (/^(?:https?:|data:|\/\/)/i.test(value)) {
61
+ return value.startsWith("//") ? `${Astro.url.protocol}${value}` : value;
62
+ }
63
+ return `${baseImgUrl}/${value.replace(/^\/+/, "")}`;
64
+ };
49
65
  const ogImagePath = toAbsolute(props.cover || authorConfig.avatar || faviconPath);
50
66
  const isNotFoundPage =
51
67
  Astro.url.pathname === "/404" || Astro.url.pathname === "/404/";
68
+ const routeKind = getRouteKind(Astro.url.pathname);
69
+ const isCollectionPage = routeKind.endsWith("-index") || routeKind.endsWith("-detail");
52
70
  const resolvedNoindex = Boolean(siteConfig.noindex) || isNotFoundPage || Boolean(props.noindex);
53
71
  const resolvedNofollow = Boolean(siteConfig.nofollow) || isNotFoundPage;
54
72
 
@@ -62,6 +80,7 @@ const openGraphConfig: Record<string, unknown> = {
62
80
  optional: {
63
81
  description: effectiveDescription,
64
82
  locale: langToOpenGraphLocale(siteConfig.lang || "zh-CN"),
83
+ siteName,
65
84
  },
66
85
  image: {
67
86
  url: ogImagePath,
@@ -123,6 +142,21 @@ const extendedMeta = [
123
142
  ];
124
143
 
125
144
  let schema: Record<string, unknown>;
145
+ const sameAs = mediaLinksConfig?.items
146
+ .map((item) => item.link)
147
+ .filter((link) => /^https?:\/\//i.test(link));
148
+ const authorEntity = {
149
+ "@type": "Person",
150
+ name: authorConfig.name,
151
+ ...(toAbsolute(authorConfig.avatar) ? { image: toAbsolute(authorConfig.avatar) } : {}),
152
+ ...(sameAs.length ? { sameAs } : {}),
153
+ };
154
+ const publisher = {
155
+ "@type": "Organization",
156
+ name: siteName,
157
+ url: siteUrl,
158
+ ...(authorConfig.avatar ? { logo: toAbsolute(authorConfig.avatar) } : {}),
159
+ };
126
160
  if (isArticle) {
127
161
  schema = {
128
162
  "@context": "https://schema.org",
@@ -130,28 +164,38 @@ if (isArticle) {
130
164
  headline: effectiveTitle,
131
165
  description: effectiveDescription,
132
166
  url: canonicalUrl,
133
- image: ogImagePath,
134
- datePublished: props.date,
135
- dateModified: props.updated || props.date,
136
- keywords: props.tags?.join(", "),
137
- author: props.author
138
- ? {
139
- "@type": "Person",
140
- name: props.author,
141
- }
142
- : undefined,
167
+ ...(ogImagePath ? { image: ogImagePath } : {}),
168
+ ...(props.date ? { datePublished: props.date } : {}),
169
+ ...(props.updated || props.date ? { dateModified: props.updated || props.date } : {}),
170
+ ...(props.tags?.length ? { keywords: props.tags.join(", ") } : {}),
171
+ author: props.author ? { ...authorEntity, name: props.author } : authorEntity,
172
+ publisher,
173
+ inLanguage: siteConfig.lang || "zh-CN",
143
174
  mainEntityOfPage: {
144
175
  "@type": "WebPage",
145
176
  "@id": canonicalUrl,
146
177
  },
147
178
  };
148
- } else {
179
+ } else if (routeKind === "home") {
149
180
  schema = {
150
181
  "@context": "https://schema.org",
151
182
  "@type": "WebSite",
152
183
  url: canonicalUrl,
153
184
  name: effectiveTitle,
154
185
  description: effectiveDescription,
186
+ inLanguage: siteConfig.lang || "zh-CN",
187
+ publisher,
188
+ author: authorEntity,
189
+ };
190
+ } else {
191
+ schema = {
192
+ "@context": "https://schema.org",
193
+ "@type": isCollectionPage ? "CollectionPage" : routeKind === "about" ? "AboutPage" : "WebPage",
194
+ url: canonicalUrl,
195
+ name: effectiveTitle,
196
+ description: effectiveDescription,
197
+ inLanguage: siteConfig.lang || "zh-CN",
198
+ isPartOf: { "@type": "WebSite", url: siteUrl, name: siteName },
155
199
  };
156
200
  }
157
201
  const ldJson = JSON.stringify(schema, null, 2).replace(/<\//g, "<\\/");
@@ -21,6 +21,7 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
21
21
  <div class={styles.homeNavs}>
22
22
  <Navs />
23
23
  </div>
24
+ <h2 class="sr-only" style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;">关于本站</h2>
24
25
  <Author />
25
26
  <TextType />
26
27
  <DateTime />
@@ -16,6 +16,7 @@ export const siteSchema = z.object({
16
16
  title: z.string().min(1, "site.title is required"),
17
17
  url: z.string().url("site.url must be a valid URL"),
18
18
  description: z.string().min(1, "site.description is required"),
19
+ seoTitle: z.string().min(1).optional(),
19
20
  timezone: z.string().optional().default("Asia/Shanghai"),
20
21
  canonical: z.string().url("site.canonical must be a valid URL").optional(),
21
22
  twitterSite: z.string().optional(),