@xingwangzhe/stalux 1.25.12 → 1.25.14
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 +9 -9
- package/src/components/stalux/layout/navs.astro +1 -1
- package/src/components/stalux/posts/postContent.astro +1 -0
- package/src/index.ts +8 -19
- package/src/internal/runtime-cache-key.ts +35 -0
- package/src/internal/vite-aliases.ts +16 -0
- package/src/layouts/Stalux.astro +3 -0
- package/src/pages/posts/[post].astro +2 -0
- package/src/scripts/navigation.ts +17 -6
- package/src/scripts/photoswipe.ts +6 -16
- package/src/scripts/retryable-initializer.ts +13 -0
- package/src/scripts/search-dialog.ts +18 -0
- package/src/utils/taxonomy.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.14",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -85,12 +85,12 @@
|
|
|
85
85
|
"prepare": "git config core.hooksPath scripts/git-hooks"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@astrojs/markdown-satteri": "0.3.
|
|
88
|
+
"@astrojs/markdown-satteri": "0.3.8",
|
|
89
89
|
"@astrojs/rss": "^4.0.19",
|
|
90
90
|
"@astrojs/sitemap": "^3.7.3",
|
|
91
91
|
"@expressive-code/plugin-line-numbers": "^0.44.1",
|
|
92
|
-
"@lucide/astro": "^1.
|
|
93
|
-
"@mcp-b/webmcp-polyfill": "^
|
|
92
|
+
"@lucide/astro": "^1.35.0",
|
|
93
|
+
"@mcp-b/webmcp-polyfill": "^5.0.1",
|
|
94
94
|
"@pagefind/component-ui": "^1.5.2",
|
|
95
95
|
"@waline/client": "^3.15.2",
|
|
96
96
|
"@xingwangzhe/satteri-mermaid": "0.7.7",
|
|
@@ -104,20 +104,20 @@
|
|
|
104
104
|
"photoswipe": "^5.4.4",
|
|
105
105
|
"satteri": "^0.10.5",
|
|
106
106
|
"simple-icons": "^16.28.0",
|
|
107
|
-
"subset-font": "^2.
|
|
107
|
+
"subset-font": "^2.6.0",
|
|
108
108
|
"temml": "^0.13.4"
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
111
|
"@astrojs/check": "0.9.10",
|
|
112
|
-
"@biomejs/biome": "2.5.
|
|
112
|
+
"@biomejs/biome": "2.5.11",
|
|
113
113
|
"@vitest/coverage-v8": "4.1.11",
|
|
114
|
-
"astro": "^7.2.
|
|
115
|
-
"knip": "^6.32.
|
|
114
|
+
"astro": "^7.2.9",
|
|
115
|
+
"knip": "^6.32.3",
|
|
116
116
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
117
117
|
"vite": "8.2.2",
|
|
118
118
|
"vitest": "4.1.11"
|
|
119
119
|
},
|
|
120
120
|
"peerDependencies": {
|
|
121
|
-
"astro": "7.2.
|
|
121
|
+
"astro": "7.2.9"
|
|
122
122
|
}
|
|
123
123
|
}
|
|
@@ -15,7 +15,7 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
|
15
15
|
const navs = navsConfig?.items ?? [];
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
<nav aria-label={t("nav.main")}
|
|
18
|
+
<nav aria-label={t("nav.main")}>
|
|
19
19
|
<!-- 汉堡菜单按钮 (仅窄屏显示) -->
|
|
20
20
|
<button
|
|
21
21
|
type="button"
|
package/src/index.ts
CHANGED
|
@@ -26,11 +26,13 @@ import { expressiveCode } from "./expressive-code";
|
|
|
26
26
|
import { staluxComponentsAlias } from "./internal/components-plugin";
|
|
27
27
|
import { type FontSlice, runFontSlicing } from "./internal/font-slices";
|
|
28
28
|
import { createInjectedRoutes } from "./internal/injected-routes";
|
|
29
|
+
import { createRuntimeCacheKey } from "./internal/runtime-cache-key";
|
|
29
30
|
import {
|
|
30
31
|
appendUniquePlugin,
|
|
31
32
|
collectPluginNames,
|
|
32
33
|
prepareSatteriProcessor,
|
|
33
34
|
} from "./internal/satteri-config";
|
|
35
|
+
import { createViteAliases } from "./internal/vite-aliases";
|
|
34
36
|
import { featureFlagsHast, featureFlagsMdast } from "./plugins/feature-flags";
|
|
35
37
|
import { temml } from "./plugins/satteri-temml";
|
|
36
38
|
|
|
@@ -117,24 +119,6 @@ function syncBackgroundSvgs(srcDir: string): { copied: number; skipped: number }
|
|
|
117
119
|
return { copied, skipped };
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
// Vite 别名配置(供页面使用 @components etc.)
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
function getViteAliases(srcDir: string) {
|
|
125
|
-
return {
|
|
126
|
-
"@components": path.resolve(srcDir, "components"),
|
|
127
|
-
"@assets": path.resolve(srcDir, "assets"),
|
|
128
|
-
"@layouts": path.resolve(srcDir, "layouts"),
|
|
129
|
-
"@scripts": path.resolve(srcDir, "scripts"),
|
|
130
|
-
"@styles": path.resolve(srcDir, "styles"),
|
|
131
|
-
"@utils": path.resolve(srcDir, "utils"),
|
|
132
|
-
"@i18n": path.resolve(srcDir, "i18n"),
|
|
133
|
-
"@plugins": path.resolve(srcDir, "plugins"),
|
|
134
|
-
"@schemas": path.resolve(srcDir, "schemas"),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
122
|
// ---------------------------------------------------------------------------
|
|
139
123
|
// 集成入口
|
|
140
124
|
// ---------------------------------------------------------------------------
|
|
@@ -179,18 +163,23 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
179
163
|
logger,
|
|
180
164
|
}) => {
|
|
181
165
|
const srcDir = fileURLToPath(new URL(".", import.meta.url));
|
|
166
|
+
const runtimeCacheKey = createRuntimeCacheKey(
|
|
167
|
+
path.join(srcDir, "scripts"),
|
|
168
|
+
path.resolve(srcDir, "../package.json"),
|
|
169
|
+
);
|
|
182
170
|
|
|
183
171
|
// 1. 注入 Vite 别名 + 组件覆盖插件 + Vue 特性标记(Waline 依赖)
|
|
184
172
|
updateConfig({
|
|
185
173
|
vite: {
|
|
186
174
|
resolve: {
|
|
187
|
-
alias:
|
|
175
|
+
alias: createViteAliases(srcDir),
|
|
188
176
|
},
|
|
189
177
|
plugins: [staluxComponentsAlias(opt.components)],
|
|
190
178
|
define: {
|
|
191
179
|
__VUE_OPTIONS_API__: true,
|
|
192
180
|
__VUE_PROD_DEVTOOLS__: false,
|
|
193
181
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
|
|
182
|
+
__STALUX_RUNTIME_CACHE_KEY__: JSON.stringify(runtimeCacheKey),
|
|
194
183
|
},
|
|
195
184
|
},
|
|
196
185
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
declare const __STALUX_RUNTIME_CACHE_KEY__: string;
|
|
6
|
+
|
|
7
|
+
export function hashRuntimeSources(sources: ReadonlyArray<readonly [string, string]>): string {
|
|
8
|
+
const hash = createHash("sha256");
|
|
9
|
+
for (const [name, contents] of [...sources].sort(([left], [right]) =>
|
|
10
|
+
left.localeCompare(right),
|
|
11
|
+
)) {
|
|
12
|
+
hash.update(name);
|
|
13
|
+
hash.update("\0");
|
|
14
|
+
hash.update(contents);
|
|
15
|
+
hash.update("\0");
|
|
16
|
+
}
|
|
17
|
+
return hash.digest("hex").slice(0, 16);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function collectFiles(directory: string, root = directory): Array<readonly [string, string]> {
|
|
21
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
22
|
+
const target = path.join(directory, entry.name);
|
|
23
|
+
if (entry.isDirectory()) return collectFiles(target, root);
|
|
24
|
+
return [[path.relative(root, target), readFileSync(target, "utf8")] as const];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createRuntimeCacheKey(scriptsDirectory: string, packageJson: string): string {
|
|
29
|
+
return hashRuntimeSources([
|
|
30
|
+
...collectFiles(scriptsDirectory),
|
|
31
|
+
["package.json", readFileSync(packageJson, "utf8")],
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getRuntimeCacheKey = (): string => __STALUX_RUNTIME_CACHE_KEY__;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
export function createViteAliases(srcDir: string): Record<string, string> {
|
|
4
|
+
return {
|
|
5
|
+
"@components": path.resolve(srcDir, "components"),
|
|
6
|
+
"@assets": path.resolve(srcDir, "assets"),
|
|
7
|
+
"@layouts": path.resolve(srcDir, "layouts"),
|
|
8
|
+
"@scripts": path.resolve(srcDir, "scripts"),
|
|
9
|
+
"@styles": path.resolve(srcDir, "styles"),
|
|
10
|
+
"@utils": path.resolve(srcDir, "utils"),
|
|
11
|
+
"@i18n": path.resolve(srcDir, "i18n"),
|
|
12
|
+
"@plugins": path.resolve(srcDir, "plugins"),
|
|
13
|
+
"@schemas": path.resolve(srcDir, "schemas"),
|
|
14
|
+
"@internal": path.resolve(srcDir, "internal"),
|
|
15
|
+
};
|
|
16
|
+
}
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
3
|
import Footer from "@components/stalux/layout/footer.astro";
|
|
4
4
|
import Head from "@components/stalux/layout/head.astro";
|
|
5
|
+
// Keep Pagefind styles on the layout's static CSS graph. The component JavaScript
|
|
6
|
+
// remains lazy-loaded when search opens, but preview must always have stable CSS.
|
|
7
|
+
import "@pagefind/component-ui/css";
|
|
5
8
|
import "@styles/base/init.css";
|
|
6
9
|
import "@styles/shared/stagger.css";
|
|
7
10
|
import "@styles/shared/view-transitions.css";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { type CollectionEntry, getCollection, render } from "astro:content";
|
|
3
3
|
import Navs from "@components/stalux/layout/navs.astro";
|
|
4
|
+
import { getRuntimeCacheKey } from "@internal/runtime-cache-key";
|
|
4
5
|
import PostLayout from "@layouts/PostLayout.astro";
|
|
5
6
|
import Stalux from "@layouts/Stalux.astro";
|
|
6
7
|
import { analyzeFeatureFlags } from "@plugins/feature-flags";
|
|
@@ -33,6 +34,7 @@ export async function getStaticPaths() {
|
|
|
33
34
|
post.data.updated ?? post.data.date,
|
|
34
35
|
prevPost ? (prevPost.data.updated ?? prevPost.data.date) : "",
|
|
35
36
|
nextPost ? (nextPost.data.updated ?? nextPost.data.date) : "",
|
|
37
|
+
getRuntimeCacheKey(),
|
|
36
38
|
].join("|");
|
|
37
39
|
return {
|
|
38
40
|
params: { post: post.data.abbrlink },
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { registerPageLifecycle } from "./page-runtime";
|
|
2
|
+
import { upgradeAndOpenSearchDialog } from "./search-dialog";
|
|
2
3
|
|
|
3
4
|
interface SearchDialog extends HTMLElement {
|
|
4
|
-
open
|
|
5
|
+
open?: () => void;
|
|
6
|
+
close?: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function openSearchDialog(): Promise<void> {
|
|
10
|
+
await import("@pagefind/component-ui");
|
|
11
|
+
const dialog = document.querySelector<SearchDialog>("pagefind-modal#search");
|
|
12
|
+
await upgradeAndOpenSearchDialog(
|
|
13
|
+
{
|
|
14
|
+
whenDefined: (name) => customElements.whenDefined(name),
|
|
15
|
+
upgrade: (element) => customElements.upgrade(element),
|
|
16
|
+
},
|
|
17
|
+
dialog,
|
|
18
|
+
);
|
|
5
19
|
}
|
|
6
20
|
|
|
7
21
|
registerPageLifecycle("navigation", () => {
|
|
@@ -64,11 +78,7 @@ registerPageLifecycle("navigation", () => {
|
|
|
64
78
|
async (event) => {
|
|
65
79
|
event.preventDefault();
|
|
66
80
|
closeNav();
|
|
67
|
-
await
|
|
68
|
-
import("@pagefind/component-ui"),
|
|
69
|
-
import("@pagefind/component-ui/css"),
|
|
70
|
-
]);
|
|
71
|
-
document.querySelector<SearchDialog>("#search")?.open();
|
|
81
|
+
await openSearchDialog();
|
|
72
82
|
},
|
|
73
83
|
listenerOptions,
|
|
74
84
|
);
|
|
@@ -102,6 +112,7 @@ registerPageLifecycle("navigation", () => {
|
|
|
102
112
|
scheduleUpdate();
|
|
103
113
|
|
|
104
114
|
return () => {
|
|
115
|
+
document.querySelector<SearchDialog>("pagefind-modal#search")?.close?.();
|
|
105
116
|
controller.abort();
|
|
106
117
|
resizeObserver.disconnect();
|
|
107
118
|
if (frameId) cancelAnimationFrame(frameId);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerPageLifecycle } from "./page-runtime";
|
|
2
|
+
import { createRetryableInitializer } from "./retryable-initializer";
|
|
2
3
|
|
|
3
4
|
const GALLERY_SELECTOR = "a.pswp-gallery-item";
|
|
4
5
|
|
|
@@ -34,23 +35,13 @@ registerPageLifecycle("photoswipe", () => {
|
|
|
34
35
|
|
|
35
36
|
const controller = new AbortController();
|
|
36
37
|
let lightbox: import("photoswipe/lightbox").default | undefined;
|
|
37
|
-
let initPromise: Promise<void> | undefined;
|
|
38
38
|
let initialized = false;
|
|
39
39
|
|
|
40
40
|
const initialize = async () => {
|
|
41
|
-
const [{ default: PhotoSwipeLightbox }, { default: PhotoSwipe }
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import("photoswipe/dist/photoswipe.css?raw"),
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
if (!document.querySelector("style[data-photoswipe]") && css) {
|
|
49
|
-
const style = document.createElement("style");
|
|
50
|
-
style.dataset.photoswipe = "true";
|
|
51
|
-
style.textContent = css;
|
|
52
|
-
document.head.appendChild(style);
|
|
53
|
-
}
|
|
41
|
+
const [{ default: PhotoSwipeLightbox }, { default: PhotoSwipe }] = await Promise.all([
|
|
42
|
+
import("photoswipe/lightbox"),
|
|
43
|
+
import("photoswipe"),
|
|
44
|
+
]);
|
|
54
45
|
|
|
55
46
|
void enrichPhotoSwipeData(container);
|
|
56
47
|
lightbox = new PhotoSwipeLightbox({
|
|
@@ -62,7 +53,7 @@ registerPageLifecycle("photoswipe", () => {
|
|
|
62
53
|
lightbox.init();
|
|
63
54
|
};
|
|
64
55
|
|
|
65
|
-
const ensureInitialized = (
|
|
56
|
+
const ensureInitialized = createRetryableInitializer(initialize);
|
|
66
57
|
const handleClick = (event: MouseEvent) => {
|
|
67
58
|
const target = event.target;
|
|
68
59
|
if (!(target instanceof Element)) return;
|
|
@@ -93,7 +84,6 @@ registerPageLifecycle("photoswipe", () => {
|
|
|
93
84
|
controller.abort();
|
|
94
85
|
lightbox?.destroy();
|
|
95
86
|
lightbox = undefined;
|
|
96
|
-
initPromise = undefined;
|
|
97
87
|
initialized = false;
|
|
98
88
|
};
|
|
99
89
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createRetryableInitializer(initialize: () => Promise<void>): () => Promise<void> {
|
|
2
|
+
let pending: Promise<void> | undefined;
|
|
3
|
+
|
|
4
|
+
return () => {
|
|
5
|
+
if (!pending) {
|
|
6
|
+
pending = initialize().catch((error: unknown) => {
|
|
7
|
+
pending = undefined;
|
|
8
|
+
throw error;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
return pending;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SearchDialogLike {
|
|
2
|
+
open?: () => void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface CustomElementRegistryLike<ElementType> {
|
|
6
|
+
whenDefined(name: string): Promise<unknown>;
|
|
7
|
+
upgrade(element: ElementType): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function upgradeAndOpenSearchDialog<ElementType extends SearchDialogLike>(
|
|
11
|
+
registry: CustomElementRegistryLike<ElementType>,
|
|
12
|
+
dialog: ElementType | null,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
await registry.whenDefined("pagefind-modal");
|
|
15
|
+
if (!dialog) return;
|
|
16
|
+
registry.upgrade(dialog);
|
|
17
|
+
dialog.open?.();
|
|
18
|
+
}
|
package/src/utils/taxonomy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GetStaticPathsResult } from "astro";
|
|
2
2
|
|
|
3
|
+
import { getRuntimeCacheKey } from "../internal/runtime-cache-key";
|
|
3
4
|
import { getPostContentIndex, type TaxonomyKey } from "./content-index";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -17,7 +18,7 @@ export async function buildTaxonomyStaticPaths(key: TaxonomyKey): Promise<GetSta
|
|
|
17
18
|
return [...taxonomy].map(([name, entry]) => {
|
|
18
19
|
return {
|
|
19
20
|
params: { [paramName]: name },
|
|
20
|
-
cacheKey: entry.cacheKey
|
|
21
|
+
cacheKey: `${entry.cacheKey}|${getRuntimeCacheKey()}`,
|
|
21
22
|
props: {
|
|
22
23
|
[paramName]: name,
|
|
23
24
|
count: entry.posts.length,
|