@xingwangzhe/stalux 1.15.0 → 1.17.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/components/stalux/layout/head.astro +2 -2
- package/src/index.ts +56 -1
- package/src/internal/font-subset.ts +44 -0
- package/src/layouts/Stalux.astro +6 -18
- package/src/scripts/background.ts +28 -24
- package/src/scripts/webmcp.ts +4 -2
- /package/{src/assets → public}/background/pattern-1.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-10.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-11.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-12.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-13.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-14.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-15.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-16.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-17.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-18.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-19.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-2.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-20.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-21.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-22.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-23.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-24.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-25.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-26.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-27.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-28.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-29.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-3.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-30.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-31.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-32.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-33.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-34.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-35.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-36.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-37.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-38.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-39.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-4.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-40.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-41.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-42.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-5.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-6.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-7.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-8.min.svg +0 -0
- /package/{src/assets → public}/background/pattern-9.min.svg +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"src/plugins/**/*.ts",
|
|
39
39
|
"src/internal/**/*.ts",
|
|
40
40
|
"src/scripts/**/*.ts",
|
|
41
|
+
"public/background/**",
|
|
41
42
|
"src/assets/**/*",
|
|
42
43
|
"scripts/cli.mjs",
|
|
43
44
|
"!src/content.config.ts"
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { SEO } from "astro-seo";
|
|
3
3
|
import { ClientRouter } from "astro:transitions";
|
|
4
4
|
import { getCollection } from "astro:content";
|
|
5
|
-
import { Font } from "astro:assets";
|
|
6
5
|
import VerCount from "@components/stalux/common/verCount.astro";
|
|
7
6
|
import GoogleAnalytics from "@components/stalux/analytics/google.astro";
|
|
8
7
|
import BingClarity from "@components/stalux/analytics/clarity.astro";
|
|
@@ -175,7 +174,8 @@ const ldJson = JSON.stringify(schema, null, 2).replace(/<\//g, "<\\/");
|
|
|
175
174
|
}}
|
|
176
175
|
/>
|
|
177
176
|
<style set:html={`:root{--font-body:"LXGW WenKai Subset","Noto Sans SC","Noto Sans CJK SC",system-ui,-apple-system,sans-serif}`} />
|
|
178
|
-
|
|
177
|
+
<!-- 代码字体:静态 code.css(font-subset.ts 生成,确定性,不经过 Astro <Font> 管线) -->
|
|
178
|
+
<link rel="stylesheet" href="/fonts/code.css" />
|
|
179
179
|
{
|
|
180
180
|
(headConfig?.anyhead) && (
|
|
181
181
|
<Fragment set:html={headConfig.anyhead} />
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 基于 Astro 7.1.3 Integration API(astro:config:setup / injectRoute / injectScript)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
11
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -93,6 +93,45 @@ function getRoutes(baseDir: URL): RouteEntry[] {
|
|
|
93
93
|
];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// 背景 SVG 判别式同步
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 把主题包的 public/background/*.svg 同步到用户项目 public/background/。
|
|
102
|
+
*
|
|
103
|
+
* 判别式(非无条件覆盖):
|
|
104
|
+
* - 同名文件:内容一致则跳过(幂等);内容不同才覆盖(主题更新了 SVG)
|
|
105
|
+
* - 用户新增的文件:保留,不删除(避免误删用户自定义背景)
|
|
106
|
+
* - 不做整目录 force 复制
|
|
107
|
+
*/
|
|
108
|
+
function syncBackgroundSvgs(srcDir: string): { copied: number; skipped: number } {
|
|
109
|
+
const srcBg = path.join(srcDir, "../public/background");
|
|
110
|
+
if (!existsSync(srcBg)) return { copied: 0, skipped: 0 };
|
|
111
|
+
|
|
112
|
+
const destBg = path.resolve(process.cwd(), "public", "background");
|
|
113
|
+
mkdirSync(destBg, { recursive: true });
|
|
114
|
+
|
|
115
|
+
let copied = 0;
|
|
116
|
+
let skipped = 0;
|
|
117
|
+
for (const name of readdirSync(srcBg)) {
|
|
118
|
+
if (!name.endsWith(".svg")) continue;
|
|
119
|
+
const srcFile = path.join(srcBg, name);
|
|
120
|
+
const destFile = path.join(destBg, name);
|
|
121
|
+
if (existsSync(destFile)) {
|
|
122
|
+
const srcBuf = readFileSync(srcFile);
|
|
123
|
+
const destBuf = readFileSync(destFile);
|
|
124
|
+
if (srcBuf.equals(destBuf)) {
|
|
125
|
+
skipped++; // 内容一致,跳过
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
copyFileSync(srcFile, destFile);
|
|
130
|
+
copied++;
|
|
131
|
+
}
|
|
132
|
+
return { copied, skipped };
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
// ---------------------------------------------------------------------------
|
|
97
136
|
// Vite 别名配置(供页面使用 @components etc.)
|
|
98
137
|
// ---------------------------------------------------------------------------
|
|
@@ -172,6 +211,22 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration {
|
|
|
172
211
|
// 2. 注入全局 CSS
|
|
173
212
|
injectScript("page-ssr", `import "${srcDir}/styles/base/init.css";`);
|
|
174
213
|
|
|
214
|
+
// 2.5 同步背景 SVG 到用户项目 public/background/
|
|
215
|
+
// 纯客户端引入:SVG 以静态 URL(/background/pattern-*.svg)被 background.ts 硬编码引用,
|
|
216
|
+
// 不经过 Astro 资源管线(import.meta.glob 会打上每次构建随机的 __ASTRO_ASSET_IMAGE__ 占位符,
|
|
217
|
+
// 破坏增量构建依赖图 hash)。Astro 不会合并集成的 public 目录,这里显式同步一次。
|
|
218
|
+
// 判别式:内容一致跳过(幂等),内容不同才覆盖,用户新增文件保留。
|
|
219
|
+
try {
|
|
220
|
+
const { copied, skipped } = syncBackgroundSvgs(srcDir);
|
|
221
|
+
if (copied > 0) {
|
|
222
|
+
logger.info(`Stalux: 同步背景 SVG ${copied} 个(跳过 ${skipped} 个不变)`);
|
|
223
|
+
} else if (skipped > 0) {
|
|
224
|
+
logger.debug(`Stalux: 背景 SVG 无变化(跳过 ${skipped} 个)`);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
logger.warn(`Stalux: 同步背景 SVG 失败: ${String(error)}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
175
230
|
// 3. 注入所有页面路由(仅插件模式,源码模式下使用文件路由)
|
|
176
231
|
if (isPluginMode) {
|
|
177
232
|
const routes = getRoutes(new URL(".", import.meta.url));
|
|
@@ -39,6 +39,9 @@ const CONTENT_ROOT = "stalux";
|
|
|
39
39
|
/** Full font input path (relative to project root or package) */
|
|
40
40
|
const FONT_INPUT = "src/assets/fonts/LXGWWenKai-Regular.ttf";
|
|
41
41
|
|
|
42
|
+
/** Code font input path (Google Sans Code, deterministic static @font-face) */
|
|
43
|
+
const CODE_FONT_INPUT = "src/assets/fonts/GoogleSansCode.woff2";
|
|
44
|
+
|
|
42
45
|
/** Output directory for generated subsets */
|
|
43
46
|
const FONT_OUT_DIR = "public/fonts";
|
|
44
47
|
|
|
@@ -404,6 +407,47 @@ export async function runFontSubsetting(
|
|
|
404
407
|
}
|
|
405
408
|
}
|
|
406
409
|
|
|
410
|
+
// 1.5 代码字体(Google Sans Code)— 确定性静态 @font-face,不经过 Astro 字体管线
|
|
411
|
+
// Astro 的 <Font> 组件会在 prerender 阶段启动一个随机端口的 HTTP server,
|
|
412
|
+
// 端口号会嵌进 font-file-url-resolver 虚拟模块的编译产物,破坏增量构建依赖图 hash。
|
|
413
|
+
// 这里直接复制 woff2 到 public/fonts/ 并生成静态 code.css,完全绕开该管线。
|
|
414
|
+
try {
|
|
415
|
+
const codeFontPaths = [
|
|
416
|
+
resolve(projectRoot, CODE_FONT_INPUT),
|
|
417
|
+
resolve(projectRoot, "node_modules", "@xingwangzhe", "stalux", CODE_FONT_INPUT),
|
|
418
|
+
resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", CODE_FONT_INPUT),
|
|
419
|
+
];
|
|
420
|
+
const codeFontPath = codeFontPaths.find((p) => existsSync(p));
|
|
421
|
+
if (codeFontPath) {
|
|
422
|
+
const codeFontBuffer = readFileSync(codeFontPath);
|
|
423
|
+
const codeFontOut = join(outDir, "GoogleSansCode.woff2");
|
|
424
|
+
if (!existsSync(codeFontOut) || !readFileSync(codeFontOut).equals(codeFontBuffer)) {
|
|
425
|
+
writeFileSync(codeFontOut, codeFontBuffer);
|
|
426
|
+
logger.info(
|
|
427
|
+
` code font: ${codeFontBuffer.length / 1024} KB → GoogleSansCode.woff2`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
// 静态 CSS:@font-face + --font-code 变量(确定性内容,无 hash)
|
|
431
|
+
const codeCSS = `/* Auto-generated code font (deterministic, no Astro font pipeline) */
|
|
432
|
+
:root {
|
|
433
|
+
--font-code: "Google Sans Code", "JetBrains Mono", "Fira Code", "Consolas", "Courier New", monospace;
|
|
434
|
+
}
|
|
435
|
+
@font-face {
|
|
436
|
+
font-family: "Google Sans Code";
|
|
437
|
+
src: url("/fonts/GoogleSansCode.woff2") format("woff2");
|
|
438
|
+
font-display: swap;
|
|
439
|
+
}
|
|
440
|
+
`;
|
|
441
|
+
writeFileSync(join(outDir, "code.css"), codeCSS);
|
|
442
|
+
} else {
|
|
443
|
+
logger.warn(
|
|
444
|
+
`Code font not found at ${CODE_FONT_INPUT}, --font-code falls back to system monospace`,
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
} catch (error) {
|
|
448
|
+
logger.warn(`Code font setup failed: ${String(error)}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
407
451
|
// 2. Scan content files
|
|
408
452
|
const contentFiles = scanContent(projectRoot);
|
|
409
453
|
logger.info(`Font subsetting: found ${contentFiles.length} content files`);
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -34,19 +34,11 @@ const fontSubsetUrl = props.fontSubsetId
|
|
|
34
34
|
? `/fonts/subset-${props.fontSubsetId}.css`
|
|
35
35
|
: undefined;
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
.map((m) => m.default.src)
|
|
43
|
-
.sort((a, b) => {
|
|
44
|
-
const na = parseInt(a.match(/pattern-(\d+)/)?.[1] || "0", 10);
|
|
45
|
-
const nb = parseInt(b.match(/pattern-(\d+)/)?.[1] || "0", 10);
|
|
46
|
-
return na - nb;
|
|
47
|
-
});
|
|
48
|
-
const randomIndex = bgUrls.length > 0 ? Math.floor(Math.random() * bgUrls.length) : 0;
|
|
49
|
-
const initialBg = bgUrls[randomIndex] || "";
|
|
37
|
+
// 背景 SVG 以静态 URL 硬编码在 background.ts(纯客户端),
|
|
38
|
+
// 集成在 config:setup 时把 SVG 复制到用户项目 public/background/。
|
|
39
|
+
// 这里只取第一张作为 SSR 初始背景,客户端运行时随机替换。
|
|
40
|
+
// 不经过 import.meta.glob 资源管线,保证增量构建依赖图 hash 稳定。
|
|
41
|
+
const initialBg = "/background/pattern-1.min.svg";
|
|
50
42
|
const llmPromoteRaw = promoteConfig?.llm_promote ?? "";
|
|
51
43
|
const llmPromote = llmPromoteRaw
|
|
52
44
|
?.replace(/\{author\}/g, authorConfig.name)
|
|
@@ -72,10 +64,6 @@ const llmPromote = llmPromoteRaw
|
|
|
72
64
|
<!-- Per-route font subset (generated at build time) -->
|
|
73
65
|
<link rel="stylesheet" href="/fonts/common.css" />
|
|
74
66
|
{fontSubsetUrl && <link rel="stylesheet" href={fontSubsetUrl} />}
|
|
75
|
-
<script define:vars={{ bgUrls, randomIndex }}>
|
|
76
|
-
window.__STALUX_BG_URLS__ = bgUrls;
|
|
77
|
-
window.__STALUX_BG_INDEX__ = randomIndex;
|
|
78
|
-
</script>
|
|
79
67
|
<script>
|
|
80
68
|
import "../scripts/background.ts";
|
|
81
69
|
</script>
|
|
@@ -106,7 +94,7 @@ const llmPromote = llmPromoteRaw
|
|
|
106
94
|
<body
|
|
107
95
|
class="stalux-root dark"
|
|
108
96
|
data-stalux-bg-layer="a"
|
|
109
|
-
data-stalux-bg-index=
|
|
97
|
+
data-stalux-bg-index="0"
|
|
110
98
|
>
|
|
111
99
|
{llmPromote && <div style="display:none">{llmPromote}</div>}
|
|
112
100
|
<div
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 双层随机背景 —
|
|
3
|
-
*
|
|
2
|
+
* 双层随机背景 — 纯客户端实现
|
|
3
|
+
*
|
|
4
|
+
* SVG 以静态 URL(/background/pattern-N.min.svg)硬编码,由集成在
|
|
5
|
+
* config:setup 时复制到用户项目 public/background/。不经过 Astro 资源
|
|
6
|
+
* 管线(import.meta.glob 会给 SVG 打上每次构建随机的 __ASTRO_ASSET_IMAGE__
|
|
7
|
+
* 占位符,破坏增量构建依赖图 hash),也不做任何构建期扫描。
|
|
8
|
+
*
|
|
9
|
+
* 背景随机在客户端运行时执行,构建产物完全确定。
|
|
4
10
|
*/
|
|
5
11
|
declare global {
|
|
6
12
|
interface Window {
|
|
7
13
|
__STALUX_BG_URLS__?: string[];
|
|
8
|
-
__STALUX_BG_INDEX__?: number;
|
|
9
14
|
}
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
17
|
+
/** 背景 SVG 静态 URL 列表(与主题包 public/background/ 的 1-42 号一一对应) */
|
|
18
|
+
const BACKGROUND_URLS: string[] = Array.from(
|
|
19
|
+
{ length: 42 },
|
|
20
|
+
(_, i) => `/background/pattern-${i + 1}.min.svg`,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** 优先使用注入列表(若用户自定义),否则用内置硬编码列表 */
|
|
24
|
+
const backgroundImages: string[] = window.__STALUX_BG_URLS__?.length
|
|
25
|
+
? window.__STALUX_BG_URLS__
|
|
26
|
+
: BACKGROUND_URLS;
|
|
15
27
|
|
|
16
28
|
function getLayerEl(layer: "a" | "b"): HTMLElement | null {
|
|
17
29
|
return document.querySelector(`.bg-layer.bg-${layer}`);
|
|
@@ -23,25 +35,17 @@ function setLayerOpacity(layer: "a" | "b", opacity: number): void {
|
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
function initBackground(): void {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} else {
|
|
33
|
-
// Fallback:客户端自行初始化
|
|
34
|
-
const index = Math.floor(Math.random() * backgroundImages.length);
|
|
35
|
-
const layer: "a" | "b" = "a";
|
|
36
|
-
if (backgroundImages.length > 0) {
|
|
37
|
-
const el = getLayerEl(layer);
|
|
38
|
-
if (el) el.style.backgroundImage = `url('${backgroundImages[index]}')`;
|
|
39
|
-
}
|
|
40
|
-
setLayerOpacity(layer, 1);
|
|
41
|
-
setLayerOpacity("b", 0);
|
|
42
|
-
document.body.dataset.staluxBgIndex = String(index);
|
|
43
|
-
document.body.dataset.staluxBgLayer = layer;
|
|
38
|
+
// 客户端运行时随机选图,构建产物保持确定性
|
|
39
|
+
const index = Math.floor(Math.random() * backgroundImages.length);
|
|
40
|
+
const layer: "a" | "b" = "a";
|
|
41
|
+
if (backgroundImages.length > 0) {
|
|
42
|
+
const el = getLayerEl(layer);
|
|
43
|
+
if (el) el.style.backgroundImage = `url('${backgroundImages[index]}')`;
|
|
44
44
|
}
|
|
45
|
+
setLayerOpacity(layer, 1);
|
|
46
|
+
setLayerOpacity("b", 0);
|
|
47
|
+
document.body.dataset.staluxBgIndex = String(index);
|
|
48
|
+
document.body.dataset.staluxBgLayer = layer;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
// 初始化(首次加载 + VT 软导航都走这里)
|
package/src/scripts/webmcp.ts
CHANGED
|
@@ -93,8 +93,10 @@ async function loadPagefind(): Promise<{ search: (q: string) => Promise<unknown>
|
|
|
93
93
|
if (!pagefindLoadPromise) {
|
|
94
94
|
pagefindLoadPromise = (async () => {
|
|
95
95
|
try {
|
|
96
|
-
//
|
|
97
|
-
|
|
96
|
+
// 用完整 URL 构造,让 Vite 在 dev(vite:import-analysis)与 build 都不解析该路径;
|
|
97
|
+
// pagefind 索引是构建期产物,运行时从站点根加载。
|
|
98
|
+
const url = new URL("/pagefind/pagefind.js", location.origin).href;
|
|
99
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
98
100
|
return mod.default ?? mod;
|
|
99
101
|
} catch {
|
|
100
102
|
return null;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|