@xingwangzhe/stalux 1.25.11 → 1.25.13

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.11",
3
+ "version": "1.25.13",
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
  }
@@ -5,8 +5,8 @@ import { ClientRouter } from "astro:transitions";
5
5
  import BingClarity from "@components/stalux/analytics/clarity.astro";
6
6
  import GoogleAnalytics from "@components/stalux/analytics/google.astro";
7
7
  import VerCount from "@components/stalux/common/verCount.astro";
8
- import { STALUX_VERSION } from "@internal/version";
9
8
  import { SEO } from "astro-seo";
9
+ import packageJson from "../../../../package.json";
10
10
  import "@styles/base/init.css";
11
11
  import Umami from "@components/stalux/analytics/umami.astro";
12
12
  import {
@@ -33,7 +33,7 @@ const seo = buildSeoDocument({
33
33
  pathname: Astro.url.pathname,
34
34
  protocol: Astro.url.protocol,
35
35
  exportMarkdown: Boolean(promoteConfig?.export_md),
36
- version: STALUX_VERSION,
36
+ version: packageJson.version,
37
37
  });
38
38
  const ldJson = JSON.stringify(seo.schema).replace(/<\//g, "<\\/");
39
39
  ---
@@ -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,6 +26,7 @@ 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,
@@ -179,6 +180,10 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
179
180
  logger,
180
181
  }) => {
181
182
  const srcDir = fileURLToPath(new URL(".", import.meta.url));
183
+ const runtimeCacheKey = createRuntimeCacheKey(
184
+ path.join(srcDir, "scripts"),
185
+ path.resolve(srcDir, "../package.json"),
186
+ );
182
187
 
183
188
  // 1. 注入 Vite 别名 + 组件覆盖插件 + Vue 特性标记(Waline 依赖)
184
189
  updateConfig({
@@ -191,6 +196,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
191
196
  __VUE_OPTIONS_API__: true,
192
197
  __VUE_PROD_DEVTOOLS__: false,
193
198
  __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
199
+ __STALUX_RUNTIME_CACHE_KEY__: JSON.stringify(runtimeCacheKey),
194
200
  },
195
201
  },
196
202
  });
@@ -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__;
@@ -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,