@xingwangzhe/stalux 1.6.0 → 1.6.2
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 +1 -1
- package/src/internal/font-subset-dev.ts +146 -12
- package/src/internal/font-subset.ts +116 -7
- package/src/pages/archives.astro +1 -1
- package/src/pages/categories/[category].astro +1 -1
- package/src/pages/categories/index.astro +3 -1
- package/src/pages/index.astro +1 -1
- package/src/pages/links.astro +7 -7
- package/src/pages/tags/[tag].astro +1 -1
- package/src/pages/tags/index.astro +3 -1
package/package.json
CHANGED
|
@@ -63,22 +63,156 @@ function getRouteChars(projectRoot: string, subsetId: string): string | null {
|
|
|
63
63
|
return all.join("\n") || null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// ──
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// ── Archives ────────────────────────────────────────────
|
|
67
|
+
if (subsetId === "archives") {
|
|
68
|
+
return getAllPostContent(contentRoot);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Tags index ──────────────────────────────────────────
|
|
72
|
+
if (subsetId === "tags") {
|
|
73
|
+
return getAllTagsAndPostContent(contentRoot);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Tags detail ─────────────────────────────────────────
|
|
77
|
+
if (subsetId.startsWith("tags-")) {
|
|
78
|
+
const tagName = decodeURIComponent(subsetId.slice(5));
|
|
79
|
+
return getTagContent(contentRoot, tagName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Categories index ────────────────────────────────────
|
|
83
|
+
if (subsetId === "categories") {
|
|
84
|
+
return getAllCategoriesAndPostContent(contentRoot);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Categories detail ───────────────────────────────────
|
|
88
|
+
if (subsetId.startsWith("categories-")) {
|
|
89
|
+
const catName = decodeURIComponent(subsetId.slice(11));
|
|
90
|
+
return getCategoryContent(contentRoot, catName);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Home page ───────────────────────────────────────────
|
|
94
|
+
if (subsetId === "home") {
|
|
95
|
+
return getAllConfigContent(contentRoot);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Links page ──────────────────────────────────────────
|
|
99
|
+
if (subsetId === "links") {
|
|
100
|
+
return getAllConfigContent(contentRoot);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Other HTML routes (common) ──
|
|
104
|
+
// The common subset includes UI text from source + config YAML
|
|
105
|
+
if (subsetId === "common") {
|
|
106
|
+
return getAllConfigContent(contentRoot);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Get all config YAML content */
|
|
113
|
+
function getAllConfigContent(contentRoot: string): string | null {
|
|
114
|
+
const configDir = join(contentRoot, "config");
|
|
115
|
+
if (!existsSync(configDir)) return null;
|
|
116
|
+
const all: string[] = [];
|
|
117
|
+
for (const f of readdirSync(configDir)) {
|
|
118
|
+
if (f.endsWith(".yml") || f.endsWith(".yaml")) {
|
|
119
|
+
all.push(readFileSync(join(configDir, f), "utf-8"));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return all.join("\n") || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Get all post content concatenated (including template/_ files) */
|
|
126
|
+
function getAllPostContent(contentRoot: string): string | null {
|
|
127
|
+
const postsDir = join(contentRoot, "posts");
|
|
128
|
+
if (!existsSync(postsDir)) return null;
|
|
129
|
+
const all: string[] = [];
|
|
130
|
+
for (const f of readdirSync(postsDir)) {
|
|
131
|
+
if (f.endsWith(".md") || f.endsWith(".mdx")) {
|
|
132
|
+
all.push(readFileSync(join(postsDir, f), "utf-8"));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return all.join("\n") || null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Get all tags + all post content */
|
|
139
|
+
function getAllTagsAndPostContent(contentRoot: string): string | null {
|
|
140
|
+
const postContent = getAllPostContent(contentRoot);
|
|
141
|
+
if (!postContent) return null;
|
|
142
|
+
const tags = extractAllYamlValues(postContent, "tags");
|
|
143
|
+
return postContent + " " + tags.join(" ");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Get all categories + all post content */
|
|
147
|
+
function getAllCategoriesAndPostContent(contentRoot: string): string | null {
|
|
148
|
+
const postContent = getAllPostContent(contentRoot);
|
|
149
|
+
if (!postContent) return null;
|
|
150
|
+
const categories = extractAllYamlValues(postContent, "categories");
|
|
151
|
+
return postContent + " " + categories.join(" ");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Get content for a specific tag: tag name + posts with that tag */
|
|
155
|
+
function getTagContent(contentRoot: string, tagName: string): string | null {
|
|
156
|
+
const postsDir = join(contentRoot, "posts");
|
|
157
|
+
if (!existsSync(postsDir)) return null;
|
|
158
|
+
const all: string[] = [tagName];
|
|
159
|
+
for (const f of readdirSync(postsDir)) {
|
|
160
|
+
if ((f.endsWith(".md") || f.endsWith(".mdx")) && !f.startsWith("_")) {
|
|
161
|
+
const content = readFileSync(join(postsDir, f), "utf-8");
|
|
162
|
+
const tags = extractYamlListFromContent(content, "tags");
|
|
163
|
+
if (tags.includes(tagName)) {
|
|
164
|
+
all.push(content);
|
|
76
165
|
}
|
|
77
166
|
}
|
|
78
|
-
return all.join("\n") || null;
|
|
79
167
|
}
|
|
168
|
+
return all.join("\n") || null;
|
|
169
|
+
}
|
|
80
170
|
|
|
81
|
-
|
|
171
|
+
/** Get content for a specific category: category name + posts in that category */
|
|
172
|
+
function getCategoryContent(contentRoot: string, catName: string): string | null {
|
|
173
|
+
const postsDir = join(contentRoot, "posts");
|
|
174
|
+
if (!existsSync(postsDir)) return null;
|
|
175
|
+
const all: string[] = [catName];
|
|
176
|
+
for (const f of readdirSync(postsDir)) {
|
|
177
|
+
if ((f.endsWith(".md") || f.endsWith(".mdx")) && !f.startsWith("_")) {
|
|
178
|
+
const content = readFileSync(join(postsDir, f), "utf-8");
|
|
179
|
+
const categories = extractYamlListFromContent(content, "categories");
|
|
180
|
+
if (categories.includes(catName)) {
|
|
181
|
+
all.push(content);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return all.join("\n") || null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Extract a YAML list value from frontmatter by key */
|
|
189
|
+
function extractYamlListFromContent(content: string, key: string): string[] {
|
|
190
|
+
const lines: string[] = [];
|
|
191
|
+
const regex = new RegExp(`^${key}:$`, "m");
|
|
192
|
+
const match = content.match(regex);
|
|
193
|
+
if (!match) return [];
|
|
194
|
+
const startIdx = match.index! + match[0].length;
|
|
195
|
+
const afterKey = content.slice(startIdx);
|
|
196
|
+
const listMatch = afterKey.match(/^\n((?:\s+-\s+.+\n?)*)/);
|
|
197
|
+
if (!listMatch) return [];
|
|
198
|
+
for (const line of listMatch[1].split("\n")) {
|
|
199
|
+
const item = line.match(/^\s+-\s+(.+)/)?.[1];
|
|
200
|
+
if (item) lines.push(item.trim());
|
|
201
|
+
}
|
|
202
|
+
return lines;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Extract all YAML list values from all posts by key */
|
|
206
|
+
function extractAllYamlValues(postContent: string, key: string): string[] {
|
|
207
|
+
const values = new Set<string>();
|
|
208
|
+
// Split by frontmatter boundaries (---)
|
|
209
|
+
const parts = postContent.split(/^---$/m);
|
|
210
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
211
|
+
const fm = parts[i];
|
|
212
|
+
const list = extractYamlListFromContent("---\n" + fm + "\n---\n", key);
|
|
213
|
+
for (const v of list) values.add(v);
|
|
214
|
+
}
|
|
215
|
+
return [...values];
|
|
82
216
|
}
|
|
83
217
|
|
|
84
218
|
/**
|
|
@@ -103,19 +103,100 @@ function scanContent(projectRoot: string): ContentFile[] {
|
|
|
103
103
|
|
|
104
104
|
// Posts
|
|
105
105
|
const postsDir = join(contentRoot, "posts");
|
|
106
|
+
const allPostContents: {
|
|
107
|
+
abbrlink: string;
|
|
108
|
+
tags: string[];
|
|
109
|
+
categories: string[];
|
|
110
|
+
content: string;
|
|
111
|
+
}[] = [];
|
|
106
112
|
if (existsSync(postsDir)) {
|
|
107
113
|
for (const f of readdirSync(postsDir)) {
|
|
108
114
|
if (!f.endsWith(".md") && !f.endsWith(".mdx")) continue;
|
|
109
|
-
if (f.startsWith("_")) continue; // skip template files
|
|
110
115
|
const content = readFileSync(join(postsDir, f), "utf-8");
|
|
111
|
-
|
|
116
|
+
const isTemplate = f.startsWith("_");
|
|
117
|
+
|
|
118
|
+
// Per-post routes: skip template (_) files
|
|
119
|
+
if (!isTemplate) {
|
|
120
|
+
const abbrlinkMatch = content.match(/^abbrlink:\s*(.+)$/m);
|
|
121
|
+
const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
|
|
122
|
+
files.push({
|
|
123
|
+
route: `posts/${abbrlink}`,
|
|
124
|
+
filePath: join(postsDir, f),
|
|
125
|
+
abbrlink,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Virtual routes (archives/tags/categories): include ALL posts including templates
|
|
130
|
+
// because content collections load them and they render on those pages
|
|
112
131
|
const abbrlinkMatch = content.match(/^abbrlink:\s*(.+)$/m);
|
|
113
132
|
const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
133
|
+
const tags = extractYamlList(content, "tags");
|
|
134
|
+
const categories = extractYamlList(content, "categories");
|
|
135
|
+
allPostContents.push({ abbrlink, tags, categories, content });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Build virtual routes for archives, tags, categories (aggregated pages)
|
|
140
|
+
// These pages render content from ALL posts
|
|
141
|
+
if (allPostContents.length > 0) {
|
|
142
|
+
const allContent = allPostContents.map((p) => p.content).join("\n");
|
|
143
|
+
|
|
144
|
+
// Archives: all posts' content (titles, dates, etc.)
|
|
145
|
+
virtualRouteCache.set("archives", extractChars(allContent));
|
|
146
|
+
|
|
147
|
+
// Tags index: all tags + all posts' content
|
|
148
|
+
const allTags = [...new Set(allPostContents.flatMap((p) => p.tags))].join(" ");
|
|
149
|
+
virtualRouteCache.set("tags", extractChars(allContent + allTags));
|
|
150
|
+
|
|
151
|
+
// Categories index: all categories + all posts' content
|
|
152
|
+
const allCategories = [...new Set(allPostContents.flatMap((p) => p.categories))].join(" ");
|
|
153
|
+
virtualRouteCache.set("categories", extractChars(allContent + allCategories));
|
|
154
|
+
|
|
155
|
+
// Per-tag routes: tag name + posts with that tag
|
|
156
|
+
const tagPosts = new Map<string, string[]>();
|
|
157
|
+
for (const p of allPostContents) {
|
|
158
|
+
for (const tag of p.tags) {
|
|
159
|
+
const list = tagPosts.get(tag) ?? [];
|
|
160
|
+
list.push(p.content);
|
|
161
|
+
tagPosts.set(tag, list);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const [tag, contents] of tagPosts) {
|
|
165
|
+
const tagContent = tag + " " + contents.join("\n");
|
|
166
|
+
virtualRouteCache.set(`tags-${tag}`, extractChars(tagContent));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Per-category routes: category name + posts in that category
|
|
170
|
+
const catPosts = new Map<string, string[]>();
|
|
171
|
+
for (const p of allPostContents) {
|
|
172
|
+
for (const cat of p.categories) {
|
|
173
|
+
const list = catPosts.get(cat) ?? [];
|
|
174
|
+
list.push(p.content);
|
|
175
|
+
catPosts.set(cat, list);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [cat, contents] of catPosts) {
|
|
179
|
+
const catContent = cat + " " + contents.join("\n");
|
|
180
|
+
virtualRouteCache.set(`categories-${cat}`, extractChars(catContent));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build virtual routes for config-driven pages (home, links)
|
|
185
|
+
// These pages render text from config YAML files (nav titles, site title, etc.)
|
|
186
|
+
const configDir = join(contentRoot, "config");
|
|
187
|
+
if (existsSync(configDir)) {
|
|
188
|
+
const allConfigContent: string[] = [];
|
|
189
|
+
for (const f of readdirSync(configDir)) {
|
|
190
|
+
if (f.endsWith(".yml") || f.endsWith(".yaml")) {
|
|
191
|
+
allConfigContent.push(readFileSync(join(configDir, f), "utf-8"));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (allConfigContent.length > 0) {
|
|
195
|
+
const configText = allConfigContent.join("\n");
|
|
196
|
+
// Home page: site title, author, navs, typetexts, social media, footer
|
|
197
|
+
virtualRouteCache.set("home", extractChars(configText));
|
|
198
|
+
// Links page: links config + navs + footer
|
|
199
|
+
virtualRouteCache.set("links", extractChars(configText));
|
|
119
200
|
}
|
|
120
201
|
}
|
|
121
202
|
|
|
@@ -156,11 +237,37 @@ function scanContent(projectRoot: string): ContentFile[] {
|
|
|
156
237
|
}
|
|
157
238
|
}
|
|
158
239
|
|
|
240
|
+
// Add virtual routes (archives, tags, categories) as content files
|
|
241
|
+
for (const [route] of virtualRouteCache) {
|
|
242
|
+
files.push({
|
|
243
|
+
route,
|
|
244
|
+
filePath: resolve(projectRoot, CONTENT_ROOT, `_virtual/${route}.md`),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
159
248
|
return files;
|
|
160
249
|
}
|
|
161
250
|
|
|
251
|
+
/** Extract a YAML list value from frontmatter by key */
|
|
252
|
+
function extractYamlList(content: string, key: string): string[] {
|
|
253
|
+
const lines: string[] = [];
|
|
254
|
+
const regex = new RegExp(`^${key}:$`, "m");
|
|
255
|
+
const match = content.match(regex);
|
|
256
|
+
if (!match) return [];
|
|
257
|
+
const startIdx = match.index! + match[0].length;
|
|
258
|
+
const afterKey = content.slice(startIdx);
|
|
259
|
+
const listMatch = afterKey.match(/^\n((?:\s+-\s+.+\n?)*)/);
|
|
260
|
+
if (!listMatch) return [];
|
|
261
|
+
for (const line of listMatch[1].split("\n")) {
|
|
262
|
+
const item = line.match(/^\s+-\s+(.+)/)?.[1];
|
|
263
|
+
if (item) lines.push(item.trim());
|
|
264
|
+
}
|
|
265
|
+
return lines;
|
|
266
|
+
}
|
|
267
|
+
|
|
162
268
|
// Cache for virtual route char sets
|
|
163
269
|
const wordCharsCache = new Map<string, string>();
|
|
270
|
+
const virtualRouteCache = new Map<string, string>();
|
|
164
271
|
|
|
165
272
|
// ---------------------------------------------------------------------------
|
|
166
273
|
// Common subset: extract chars from UI source files
|
|
@@ -311,6 +418,8 @@ export async function runFontSubsetting(
|
|
|
311
418
|
let content: string;
|
|
312
419
|
if (file.route === "words" && wordCharsCache.has("words")) {
|
|
313
420
|
content = wordCharsCache.get("words")!;
|
|
421
|
+
} else if (virtualRouteCache.has(file.route)) {
|
|
422
|
+
content = virtualRouteCache.get(file.route)!;
|
|
314
423
|
} else {
|
|
315
424
|
content = readFileSync(file.filePath, "utf-8");
|
|
316
425
|
}
|
package/src/pages/archives.astro
CHANGED
|
@@ -12,7 +12,7 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
|
12
12
|
const posts = await getCollection("posts", ({ data }) => !data.draft);
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
<Stalux title={t("archives.title") + " | " + siteConfig.title} description={t("archives.description", { title: siteConfig.title })}>
|
|
15
|
+
<Stalux title={t("archives.title") + " | " + siteConfig.title} description={t("archives.description", { title: siteConfig.title })} fontSubsetId={posts.length > 0 ? "archives" : undefined}>
|
|
16
16
|
<div style="height: 3em;"></div>
|
|
17
17
|
<Navs />
|
|
18
18
|
<h1 class={styles.h1}>{t("archives.heading")}</h1>
|
|
@@ -27,7 +27,7 @@ const props = Astro.props as Props;
|
|
|
27
27
|
<Stalux
|
|
28
28
|
title={t("taxonomy.category") + ": " + props.category + " | " + siteConfig.title}
|
|
29
29
|
description={t("taxonomy.browseCategory", { title: siteConfig.title, category: props.category, count: props.count })}
|
|
30
|
-
|
|
30
|
+
fontSubsetId={props.posts.length > 0 ? `categories-${encodeURIComponent(props.category)}` : undefined}
|
|
31
31
|
>
|
|
32
32
|
<div style="height: 3em;"></div>
|
|
33
33
|
<Navs />
|
|
@@ -9,12 +9,14 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
|
|
|
9
9
|
const configEntries2 = await getCollection("config");
|
|
10
10
|
const siteConfig = getSiteData(configEntries2);
|
|
11
11
|
const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
12
|
+
const posts = await getCollection("posts", ({ data }) => !data.draft);
|
|
13
|
+
const hasContent = posts.length > 0;
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
<Stalux
|
|
15
17
|
title={t("categories.title") + " | " + siteConfig.title}
|
|
16
18
|
description={t("categories.description", { title: siteConfig.title })}
|
|
17
|
-
|
|
19
|
+
fontSubsetId={hasContent ? "categories" : undefined}
|
|
18
20
|
>
|
|
19
21
|
<div style="height: 3em;"></div>
|
|
20
22
|
<Navs />
|
package/src/pages/index.astro
CHANGED
|
@@ -15,7 +15,7 @@ import ToArchive from "@components/stalux/archives/toArchive.astro";
|
|
|
15
15
|
import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
<Stalux title={siteTitle} description={siteDescription}>
|
|
18
|
+
<Stalux title={siteTitle} description={siteDescription} fontSubsetId="home">
|
|
19
19
|
<div class={styles.homeBody}>
|
|
20
20
|
<h1 class={styles.homeTitle}>{siteTitle}</h1>
|
|
21
21
|
<div class={styles.homeNavs}>
|
package/src/pages/links.astro
CHANGED
|
@@ -13,12 +13,12 @@ const linksTitle = linksConfig.title;
|
|
|
13
13
|
const linksDescription = linksConfig.description;
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
<ListPage
|
|
17
|
+
title={siteTitle}
|
|
18
|
+
description={siteDescription}
|
|
19
|
+
heading={linksTitle}
|
|
20
|
+
subheading={linksDescription}
|
|
21
|
+
fontSubsetId="links"
|
|
22
|
+
>
|
|
23
23
|
<LinkList />
|
|
24
24
|
</ListPage>
|
|
@@ -29,7 +29,7 @@ const props = Astro.props as Props;
|
|
|
29
29
|
title={t("taxonomy.tag") + ": " + props.tag + " | " + siteConfig.title}
|
|
30
30
|
description={t("taxonomy.browseTag", { title: siteConfig.title, tag: props.tag, count: props.count })}
|
|
31
31
|
noindex={props.count < 3}
|
|
32
|
-
|
|
32
|
+
fontSubsetId={props.posts.length > 0 ? `tags-${encodeURIComponent(props.tag)}` : undefined}
|
|
33
33
|
>
|
|
34
34
|
<div style="height: 3em;"></div>
|
|
35
35
|
<Navs />
|
|
@@ -9,12 +9,14 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
|
|
|
9
9
|
const configEntries = await getCollection("config");
|
|
10
10
|
const siteConfig = getSiteData(configEntries);
|
|
11
11
|
const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
12
|
+
const posts = await getCollection("posts", ({ data }) => !data.draft);
|
|
13
|
+
const hasContent = posts.length > 0;
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
<Stalux
|
|
15
17
|
title={t("tags.title") + " | " + siteConfig.title}
|
|
16
18
|
description={t("tags.description", { title: siteConfig.title })}
|
|
17
|
-
|
|
19
|
+
fontSubsetId={hasContent ? "tags" : undefined}
|
|
18
20
|
>
|
|
19
21
|
<div style="height: 3em;"></div>
|
|
20
22
|
<Navs />
|