@xingwangzhe/stalux 1.17.0 → 1.18.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/package.json +2 -1
- package/src/internal/font-subset.ts +50 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"astro-seo": "^1.1.0",
|
|
91
91
|
"astro-typewriter": "^1.2.0",
|
|
92
92
|
"badge-maker": "^6.0.0",
|
|
93
|
+
"js-yaml": "^5.2.3",
|
|
93
94
|
"pagefind": "^1.5.2",
|
|
94
95
|
"photoswipe": "^5.4.4",
|
|
95
96
|
"satteri": "^0.9.5",
|
|
@@ -27,6 +27,10 @@ import { resolve, basename, extname, join, dirname } from "node:path";
|
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
|
|
29
29
|
import type { AstroIntegrationLogger } from "astro";
|
|
30
|
+
// 与 Astro 内容集合同款的 YAML 解析器(astro 依赖 js-yaml 解析 frontmatter,
|
|
31
|
+
// 见 node_modules/astro/dist/content/loaders/file.js)。用它解析 frontmatter
|
|
32
|
+
// 得到的结果与 getCollection 的 data 一致:引号剥离、类型还原、结构完整。
|
|
33
|
+
import { load as parseYaml } from "js-yaml";
|
|
30
34
|
import subsetFont from "subset-font";
|
|
31
35
|
|
|
32
36
|
// ---------------------------------------------------------------------------
|
|
@@ -100,6 +104,43 @@ interface ContentFile {
|
|
|
100
104
|
abbrlink?: string;
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
/**
|
|
108
|
+
* 用 js-yaml(内容集合同款解析器)解析 frontmatter。
|
|
109
|
+
*
|
|
110
|
+
* 结果与 getCollection("posts") 的 data 一致:
|
|
111
|
+
* - abbrlink:number 转 string、引号剥离(YAML 解析天然处理)
|
|
112
|
+
* - tags/categories:单行字符串 "Bing" 按 schema preprocess 转 ["Bing"]
|
|
113
|
+
*/
|
|
114
|
+
function parsePostFrontmatter(content: string): {
|
|
115
|
+
abbrlink?: string;
|
|
116
|
+
tags: string[];
|
|
117
|
+
categories: string[];
|
|
118
|
+
} {
|
|
119
|
+
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
120
|
+
if (!m) return { abbrlink: undefined, tags: [], categories: [] };
|
|
121
|
+
|
|
122
|
+
let data: Record<string, unknown>;
|
|
123
|
+
try {
|
|
124
|
+
data = (parseYaml(m[1]) as Record<string, unknown>) ?? {};
|
|
125
|
+
} catch {
|
|
126
|
+
return { abbrlink: undefined, tags: [], categories: [] };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 复刻 posts schema 的 preprocess:单行字符串转数组
|
|
130
|
+
const toList = (v: unknown): string[] => {
|
|
131
|
+
if (Array.isArray(v)) return v.map((x) => String(x));
|
|
132
|
+
if (typeof v === "string") return v.trim() ? [v.trim()] : [];
|
|
133
|
+
return [];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const abbr = data.abbrlink;
|
|
137
|
+
return {
|
|
138
|
+
abbrlink: abbr === undefined || abbr === null ? undefined : String(abbr),
|
|
139
|
+
tags: toList(data.tags),
|
|
140
|
+
categories: toList(data.categories),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
103
144
|
function scanContent(projectRoot: string): ContentFile[] {
|
|
104
145
|
const files: ContentFile[] = [];
|
|
105
146
|
const contentRoot = resolve(projectRoot, CONTENT_ROOT);
|
|
@@ -117,11 +158,11 @@ function scanContent(projectRoot: string): ContentFile[] {
|
|
|
117
158
|
if (!f.endsWith(".md") && !f.endsWith(".mdx")) continue;
|
|
118
159
|
const content = readFileSync(join(postsDir, f), "utf-8");
|
|
119
160
|
const isTemplate = f.startsWith("_");
|
|
161
|
+
const fm = parsePostFrontmatter(content);
|
|
120
162
|
|
|
121
163
|
// Per-post routes: skip template (_) files
|
|
122
164
|
if (!isTemplate) {
|
|
123
|
-
const
|
|
124
|
-
const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
|
|
165
|
+
const abbrlink = fm.abbrlink ?? basename(f, extname(f));
|
|
125
166
|
files.push({
|
|
126
167
|
route: `posts/${abbrlink}`,
|
|
127
168
|
filePath: join(postsDir, f),
|
|
@@ -131,11 +172,13 @@ function scanContent(projectRoot: string): ContentFile[] {
|
|
|
131
172
|
|
|
132
173
|
// Virtual routes (archives/tags/categories): include ALL posts including templates
|
|
133
174
|
// because content collections load them and they render on those pages
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
const abbrlink = fm.abbrlink ?? basename(f, extname(f));
|
|
176
|
+
allPostContents.push({
|
|
177
|
+
abbrlink,
|
|
178
|
+
tags: fm.tags,
|
|
179
|
+
categories: fm.categories,
|
|
180
|
+
content,
|
|
181
|
+
});
|
|
139
182
|
}
|
|
140
183
|
}
|
|
141
184
|
|
|
@@ -251,23 +294,6 @@ function scanContent(projectRoot: string): ContentFile[] {
|
|
|
251
294
|
return files;
|
|
252
295
|
}
|
|
253
296
|
|
|
254
|
-
/** Extract a YAML list value from frontmatter by key */
|
|
255
|
-
function extractYamlList(content: string, key: string): string[] {
|
|
256
|
-
const lines: string[] = [];
|
|
257
|
-
const regex = new RegExp(`^${key}:$`, "m");
|
|
258
|
-
const match = content.match(regex);
|
|
259
|
-
if (!match) return [];
|
|
260
|
-
const startIdx = match.index! + match[0].length;
|
|
261
|
-
const afterKey = content.slice(startIdx);
|
|
262
|
-
const listMatch = afterKey.match(/^\n((?:\s+-\s+.+\n?)*)/);
|
|
263
|
-
if (!listMatch) return [];
|
|
264
|
-
for (const line of listMatch[1].split("\n")) {
|
|
265
|
-
const item = line.match(/^\s+-\s+(.+)/)?.[1];
|
|
266
|
-
if (item) lines.push(item.trim());
|
|
267
|
-
}
|
|
268
|
-
return lines;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
297
|
// Cache for virtual route char sets
|
|
272
298
|
const wordCharsCache = new Map<string, string>();
|
|
273
299
|
const virtualRouteCache = new Map<string, string>();
|