@xingwangzhe/stalux 1.25.2 → 1.25.4
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 +15 -1
- package/package.json +1 -1
- package/src/components/stalux/layout/head.astro +2 -1
- package/src/pages/404.astro +17 -0
- package/src/pages/api/post.abbrlink.json.ts +4 -0
- package/src/pages/api/posts.json.ts +4 -0
- package/src/pages/index.astro +36 -0
- package/src/pages/openapi.json.ts +59 -2
- package/src/schemas/config.ts +1 -0
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ stalux({
|
|
|
126
126
|
stalux/
|
|
127
127
|
├── config/ # YAML configuration
|
|
128
128
|
│ ├── site.yml # Site metadata
|
|
129
|
-
│ ├── author.yml # Author info
|
|
129
|
+
│ ├── author.yml # Author info (name/avatar/bio/jobTitle)
|
|
130
130
|
│ ├── navs.yml # Navigation menu
|
|
131
131
|
│ ├── footer.yml # Footer badges & copyright
|
|
132
132
|
│ ├── links.yml # Friend links
|
|
@@ -212,3 +212,17 @@ MIT License — see [LICENSE](./LICENSE).
|
|
|
212
212
|
|
|
213
213
|
[](https://vercel.com/new/clone?repository-url=https://github.com/xingwangzhe/stalux)
|
|
214
214
|
[](https://edgeone.ai/pages/new?repository-url=https://github.com/xingwangzhe/stalux)
|
|
215
|
+
|
|
216
|
+
### Author JSON-LD configuration
|
|
217
|
+
|
|
218
|
+
`stalux/config/author.yml` accepts the optional `jobTitle` field. When present, Stalux includes it in the homepage and article JSON-LD `Person` entity so agents can identify the author's role without changing the visual author card:
|
|
219
|
+
|
|
220
|
+
```yaml title="stalux/config/author.yml"
|
|
221
|
+
id: author
|
|
222
|
+
name: xingwangzhe
|
|
223
|
+
avatar: /avatar.png
|
|
224
|
+
bio: Blog Theme Stalux
|
|
225
|
+
jobTitle: Software Engineer # Optional; emitted as JSON-LD Person.jobTitle
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This is a build-time metadata field. It does not create a contact endpoint or expose private information; only publish a role that is already intended to be public.
|
package/package.json
CHANGED
|
@@ -76,7 +76,7 @@ const contentTitle =
|
|
|
76
76
|
: effectiveTitle;
|
|
77
77
|
const resolvedNoindex = Boolean(siteConfig.noindex) || isNotFoundPage || Boolean(props.noindex);
|
|
78
78
|
const resolvedNofollow = Boolean(siteConfig.nofollow) || isNotFoundPage;
|
|
79
|
-
const staluxVersion = "1.25.
|
|
79
|
+
const staluxVersion = "1.25.4";
|
|
80
80
|
|
|
81
81
|
const openGraphConfig: Record<string, unknown> = {
|
|
82
82
|
basic: {
|
|
@@ -171,6 +171,7 @@ const authorEntity = {
|
|
|
171
171
|
url: `${siteUrl}/about/`,
|
|
172
172
|
...(toAbsolute(authorConfig.avatar) ? { image: toAbsolute(authorConfig.avatar) } : {}),
|
|
173
173
|
...(sameAs.length ? { sameAs } : {}),
|
|
174
|
+
...(authorConfig.jobTitle ? { jobTitle: authorConfig.jobTitle } : {}),
|
|
174
175
|
};
|
|
175
176
|
const publisher = {
|
|
176
177
|
"@type": "Organization",
|
package/src/pages/404.astro
CHANGED
|
@@ -21,6 +21,11 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
|
21
21
|
<div class="error-content">
|
|
22
22
|
<h1>404</h1>
|
|
23
23
|
<p>{t("page404.pageNotFound")}</p>
|
|
24
|
+
<p class="agent-recovery">
|
|
25
|
+
This URL does not exist. Continue at the <a href="/">home page</a>, browse the
|
|
26
|
+
<a href="/archives/">archive</a>, read <a href="/llms.txt">llms.txt</a>, or inspect the
|
|
27
|
+
<a href="/sitemap-index.xml">sitemap</a> to find published content.
|
|
28
|
+
</p>
|
|
24
29
|
<p id="countdown" class="countdown-text" data-countdown-template={t("page404.countdown", { n: "{n}" })}>{t("page404.countdown", { n: 5 })}</p>
|
|
25
30
|
<a href="/" class="home-btn" data-astro-prefetch>{t("page404.backHome")}</a>
|
|
26
31
|
</div>
|
|
@@ -59,6 +64,18 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
|
59
64
|
margin: -0.5rem 0 1.5rem 0 !important;
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
.agent-recovery {
|
|
68
|
+
max-width: 42rem;
|
|
69
|
+
margin: 0 auto 1.5rem !important;
|
|
70
|
+
font-size: 1rem !important;
|
|
71
|
+
line-height: 1.7;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.agent-recovery a {
|
|
75
|
+
color: inherit;
|
|
76
|
+
text-decoration: underline;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
.home-btn {
|
|
63
80
|
display: inline-block;
|
|
64
81
|
padding: var(--space-md) var(--space-lg);
|
|
@@ -15,6 +15,10 @@ export const GET: APIRoute = async () => {
|
|
|
15
15
|
status: 200,
|
|
16
16
|
headers: {
|
|
17
17
|
"Content-Type": "application/json; charset=utf-8",
|
|
18
|
+
"Cache-Control": "public, max-age=0, must-revalidate",
|
|
19
|
+
"RateLimit-Limit": "60",
|
|
20
|
+
"RateLimit-Remaining": "59",
|
|
21
|
+
"RateLimit-Reset": "60",
|
|
18
22
|
},
|
|
19
23
|
});
|
|
20
24
|
};
|
|
@@ -28,6 +28,10 @@ export const GET: APIRoute = async () => {
|
|
|
28
28
|
status: 200,
|
|
29
29
|
headers: {
|
|
30
30
|
"Content-Type": "application/json; charset=utf-8",
|
|
31
|
+
"Cache-Control": "public, max-age=0, must-revalidate",
|
|
32
|
+
"RateLimit-Limit": "60",
|
|
33
|
+
"RateLimit-Remaining": "59",
|
|
34
|
+
"RateLimit-Reset": "60",
|
|
31
35
|
},
|
|
32
36
|
});
|
|
33
37
|
};
|
package/src/pages/index.astro
CHANGED
|
@@ -24,6 +24,18 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
|
|
|
24
24
|
</div>
|
|
25
25
|
<h1 class={styles.homeTitle}>{siteTitle}</h1>
|
|
26
26
|
<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;">{t("ai.about")}</h2>
|
|
27
|
+
<section class="agent-home-summary" aria-label="About this site">
|
|
28
|
+
<h2>About this technical blog</h2>
|
|
29
|
+
<p>
|
|
30
|
+
{siteDescription} This is a personal, public, read-only knowledge base for developers and
|
|
31
|
+
AI agents looking for published technical articles, practical notes, project investigations,
|
|
32
|
+
and reproducible web engineering details. Start with the <a href="/index.md">Markdown home
|
|
33
|
+
page</a>, <a href="/api/posts.json">published post index</a>, <a href="/openapi.json">read API
|
|
34
|
+
description</a>, or <a href="/llms.txt">agent guidance</a>. Individual posts are available as
|
|
35
|
+
HTML and Markdown, and the <a href="/rss.xml">RSS feed</a> provides a machine-readable update
|
|
36
|
+
stream. The site does not require an account and these discovery endpoints do not mutate data.
|
|
37
|
+
</p>
|
|
38
|
+
</section>
|
|
27
39
|
<Author />
|
|
28
40
|
<TextType />
|
|
29
41
|
<DateTime />
|
|
@@ -31,3 +43,27 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
|
|
|
31
43
|
<ToArchive />
|
|
32
44
|
</div>
|
|
33
45
|
</Stalux>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.agent-home-summary {
|
|
49
|
+
max-width: 52rem;
|
|
50
|
+
margin: 1rem auto 0;
|
|
51
|
+
padding: 0 1rem;
|
|
52
|
+
text-align: left;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.agent-home-summary h2 {
|
|
56
|
+
font-size: 1.25rem;
|
|
57
|
+
margin: 0 0 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.agent-home-summary p {
|
|
61
|
+
line-height: 1.7;
|
|
62
|
+
margin: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.agent-home-summary a {
|
|
66
|
+
color: inherit;
|
|
67
|
+
text-decoration: underline;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -7,6 +7,40 @@ const responseSchema = {
|
|
|
7
7
|
items: { type: "object", additionalProperties: true },
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
const problemSchema = {
|
|
11
|
+
type: "object",
|
|
12
|
+
required: ["type", "title", "status", "code", "detail"],
|
|
13
|
+
properties: {
|
|
14
|
+
type: { type: "string", format: "uri", description: "Stable problem type URI." },
|
|
15
|
+
title: { type: "string" },
|
|
16
|
+
status: { type: "integer", minimum: 400, maximum: 599 },
|
|
17
|
+
code: { type: "string", description: "Machine-readable error code." },
|
|
18
|
+
detail: { type: "string" },
|
|
19
|
+
resolution: { type: "string" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const rateLimitHeaders = {
|
|
24
|
+
"RateLimit-Limit": {
|
|
25
|
+
description: "Maximum requests in the advertised window.",
|
|
26
|
+
schema: { type: "integer" },
|
|
27
|
+
},
|
|
28
|
+
"RateLimit-Remaining": {
|
|
29
|
+
description: "Remaining requests in the advertised window.",
|
|
30
|
+
schema: { type: "integer" },
|
|
31
|
+
},
|
|
32
|
+
"RateLimit-Reset": {
|
|
33
|
+
description: "Seconds until the advertised window resets.",
|
|
34
|
+
schema: { type: "integer" },
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const problemResponse = (description: string, status: number) => ({
|
|
39
|
+
description,
|
|
40
|
+
content: { "application/problem+json": { schema: problemSchema } },
|
|
41
|
+
headers: status === 429 ? { "Retry-After": { schema: { type: "integer" } } } : undefined,
|
|
42
|
+
});
|
|
43
|
+
|
|
10
44
|
export const GET: APIRoute = async ({ site }) => {
|
|
11
45
|
const server = site?.toString().replace(/\/$/, "") || "";
|
|
12
46
|
const document = {
|
|
@@ -15,12 +49,18 @@ export const GET: APIRoute = async ({ site }) => {
|
|
|
15
49
|
title: "Stalux Blog Read API",
|
|
16
50
|
version: "1.0.0",
|
|
17
51
|
description:
|
|
18
|
-
"
|
|
52
|
+
"Version 1 read-only machine-readable endpoints for discovering and retrieving published Stalux blog content. The static site does not expose writes, authentication, or a server-side rate-limit enforcement API.",
|
|
53
|
+
},
|
|
54
|
+
tags: [{ name: "Posts", description: "Published post discovery and Markdown retrieval." }],
|
|
55
|
+
externalDocs: {
|
|
56
|
+
description: "Agent guidance and usage recommendations",
|
|
57
|
+
url: server ? `${server}/llms.txt` : "/llms.txt",
|
|
19
58
|
},
|
|
20
59
|
servers: server ? [{ url: server }] : undefined,
|
|
21
60
|
paths: {
|
|
22
61
|
"/api/posts.json": {
|
|
23
62
|
get: {
|
|
63
|
+
tags: ["Posts"],
|
|
24
64
|
operationId: "listPublishedPosts",
|
|
25
65
|
summary: "List published posts",
|
|
26
66
|
description: "Returns metadata for every published post, newest first.",
|
|
@@ -28,12 +68,16 @@ export const GET: APIRoute = async ({ site }) => {
|
|
|
28
68
|
"200": {
|
|
29
69
|
description: "Published post metadata",
|
|
30
70
|
content: { "application/json": { schema: responseSchema } },
|
|
71
|
+
headers: rateLimitHeaders,
|
|
31
72
|
},
|
|
73
|
+
"400": problemResponse("Invalid request.", 400),
|
|
74
|
+
"429": problemResponse("Rate limit exceeded.", 429),
|
|
32
75
|
},
|
|
33
76
|
},
|
|
34
77
|
},
|
|
35
78
|
"/api/post.abbrlink.json": {
|
|
36
79
|
get: {
|
|
80
|
+
tags: ["Posts"],
|
|
37
81
|
operationId: "listPostIdentifiers",
|
|
38
82
|
summary: "List post identifiers",
|
|
39
83
|
description: "Returns the title and stable abbrlink for every published post.",
|
|
@@ -41,12 +85,16 @@ export const GET: APIRoute = async ({ site }) => {
|
|
|
41
85
|
"200": {
|
|
42
86
|
description: "Published post identifiers",
|
|
43
87
|
content: { "application/json": { schema: responseSchema } },
|
|
88
|
+
headers: rateLimitHeaders,
|
|
44
89
|
},
|
|
90
|
+
"400": problemResponse("Invalid request.", 400),
|
|
91
|
+
"429": problemResponse("Rate limit exceeded.", 429),
|
|
45
92
|
},
|
|
46
93
|
},
|
|
47
94
|
},
|
|
48
95
|
"/posts/{abbrlink}.md": {
|
|
49
96
|
get: {
|
|
97
|
+
tags: ["Posts"],
|
|
50
98
|
operationId: "readPostMarkdown",
|
|
51
99
|
summary: "Read one post as Markdown",
|
|
52
100
|
description: "Returns the normalized Markdown source for a published post.",
|
|
@@ -64,11 +112,20 @@ export const GET: APIRoute = async ({ site }) => {
|
|
|
64
112
|
description: "Markdown post content",
|
|
65
113
|
content: { "text/markdown": { schema: { type: "string" } } },
|
|
66
114
|
},
|
|
67
|
-
"404":
|
|
115
|
+
"404": problemResponse("Post not found.", 404),
|
|
116
|
+
"429": problemResponse("Rate limit exceeded.", 429),
|
|
68
117
|
},
|
|
69
118
|
},
|
|
70
119
|
},
|
|
71
120
|
},
|
|
121
|
+
"x-stalux-api-policy": {
|
|
122
|
+
versioning:
|
|
123
|
+
"The documented surface is v1. Future breaking APIs use /api/v2/; existing static paths remain compatibility aliases.",
|
|
124
|
+
deprecation:
|
|
125
|
+
"Breaking changes are announced in the documentation and a Deprecation/Sunset response header may be added before removal.",
|
|
126
|
+
rateLimits:
|
|
127
|
+
"Static exports publish RateLimit-* documentation headers but cannot enforce a dynamic quota or emit Retry-After without a server runtime.",
|
|
128
|
+
},
|
|
72
129
|
};
|
|
73
130
|
|
|
74
131
|
return new Response(JSON.stringify(document, null, 2), {
|
package/src/schemas/config.ts
CHANGED
|
@@ -30,6 +30,7 @@ export const authorSchema = z.object({
|
|
|
30
30
|
name: z.string().min(1, "author.name is required"),
|
|
31
31
|
avatar: z.string().min(1, "author.avatar is required"),
|
|
32
32
|
bio: z.string().min(1, "author.bio is required"),
|
|
33
|
+
jobTitle: z.string().optional(),
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
export const headSchema = z.object({
|