@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.25.12",
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.7",
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.33.0",
93
- "@mcp-b/webmcp-polyfill": "^4.0.0",
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.5.0",
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.10",
112
+ "@biomejs/biome": "2.5.11",
113
113
  "@vitest/coverage-v8": "4.1.11",
114
- "astro": "^7.2.4",
115
- "knip": "^6.32.2",
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.4"
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")} transition:persist-props>
18
+ <nav aria-label={t("nav.main")}>
19
19
  <!-- 汉堡菜单按钮 (仅窄屏显示) -->
20
20
  <button
21
21
  type="button"
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import { fade } from "astro:transitions";
3
+ import "photoswipe/dist/photoswipe.css";
3
4
  import style from "@styles/components/posts/postContent.module.css";
4
5
  import "@styles/components/markdown.css";
5
6
  import { getCollection } from "astro:content";
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: getViteAliases(srcDir),
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
+ }
@@ -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: () => void;
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 Promise.all([
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 }, { default: css }] =
42
- await Promise.all([
43
- import("photoswipe/lightbox"),
44
- import("photoswipe"),
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 = () => (initPromise ??= initialize());
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
+ }
@@ -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,