@xingwangzhe/stalux 1.54.1 → 1.55.1
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 +22 -21
- package/src/env.d.ts +1 -0
- package/src/index.ts +1 -3
- package/src/plugins/satteri-temml.ts +1 -9
- package/src/scripts/background.ts +12 -2
- package/src/scripts/search-engine.ts +3 -8
- package/src/scripts/webmcp.ts +14 -27
- package/src/internal/webmcp-mutations.ts +0 -43
- package/src/scripts/webmcp-mutations.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.1",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -103,13 +103,12 @@
|
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
105
|
"@astrojs/compiler": "^4.0.0",
|
|
106
|
-
"@astrojs/markdown-satteri": "^0.4.
|
|
106
|
+
"@astrojs/markdown-satteri": "^0.4.8",
|
|
107
107
|
"@astrojs/rss": "^4.0.19",
|
|
108
108
|
"@astrojs/sitemap": "^3.7.4",
|
|
109
109
|
"@expressive-code/plugin-line-numbers": "^0.44.3",
|
|
110
|
-
"@lucide/astro": "^1.
|
|
111
|
-
"@
|
|
112
|
-
"@microsoft/clarity": "1.0.2",
|
|
110
|
+
"@lucide/astro": "^1.71.0",
|
|
111
|
+
"@microsoft/clarity": "^1.0.2",
|
|
113
112
|
"@pagefind/component-ui": "^1.6.0",
|
|
114
113
|
"@swup/a11y-plugin": "^5.2.1",
|
|
115
114
|
"@swup/body-class-plugin": "^3.3.0",
|
|
@@ -122,7 +121,8 @@
|
|
|
122
121
|
"@swup/route-name-plugin": "^4.1.0",
|
|
123
122
|
"@swup/scripts-plugin": "^2.1.0",
|
|
124
123
|
"@swup/scroll-plugin": "^4.0.0",
|
|
125
|
-
"@takumi-rs/core": "2.
|
|
124
|
+
"@takumi-rs/core": "^2.20.0",
|
|
125
|
+
"@w3c/webmcp-polyfill": "^0.1.0",
|
|
126
126
|
"@waline/client": "^3.16.2",
|
|
127
127
|
"@xingwangzhe/cjk-font-split-native": "^0.4.10",
|
|
128
128
|
"@xingwangzhe/satteri-mermaid": "^0.8.2",
|
|
@@ -132,39 +132,40 @@
|
|
|
132
132
|
"astro-seo": "^1.2.0",
|
|
133
133
|
"badge-maker": "^6.0.0",
|
|
134
134
|
"css-tree": "^3.2.1",
|
|
135
|
-
"
|
|
135
|
+
"escape-string-regexp": "^5.0.0",
|
|
136
|
+
"html-escaper": "^3.0.3",
|
|
137
|
+
"image-size": "^2.1.0",
|
|
136
138
|
"magic-string": "^1.4.5",
|
|
137
139
|
"pagefind": "^1.6.0",
|
|
138
|
-
"parse5": "^8.1.
|
|
140
|
+
"parse5": "^8.1.1",
|
|
139
141
|
"photoswipe": "^5.4.4",
|
|
140
|
-
"satteri": "0.11.
|
|
141
|
-
"simple-icons": "^16.
|
|
142
|
-
"swup": "4.10.0",
|
|
142
|
+
"satteri": "^0.11.7",
|
|
143
|
+
"simple-icons": "^16.37.0",
|
|
144
|
+
"swup": "^4.10.0",
|
|
143
145
|
"temml": "^0.14.0",
|
|
146
|
+
"webmcp-types": "^0.1.10",
|
|
144
147
|
"yaml": "^2.9.1"
|
|
145
148
|
},
|
|
146
149
|
"devDependencies": {
|
|
147
150
|
"@astrojs/check": "^0.9.12",
|
|
148
|
-
"@biomejs/biome": "^2.6.
|
|
151
|
+
"@biomejs/biome": "^2.6.6",
|
|
149
152
|
"@swup/debug-plugin": "^4.1.0",
|
|
150
|
-
"@types/bun": "1.4.4",
|
|
153
|
+
"@types/bun": "^1.4.4",
|
|
151
154
|
"@types/css-tree": "^3.2.0",
|
|
155
|
+
"@types/html-escaper": "^3.0.4",
|
|
152
156
|
"@unocss/astro": "^66.12.0",
|
|
153
157
|
"@vitest/coverage-v8": "^5.1.0",
|
|
154
|
-
"astro": "^7.4.
|
|
158
|
+
"astro": "^7.4.10",
|
|
155
159
|
"happy-dom": "^21.0.4",
|
|
156
|
-
"knip": "^6.
|
|
157
|
-
"sharp": "0.35.5",
|
|
160
|
+
"knip": "^6.45.0",
|
|
161
|
+
"sharp": "^0.35.5",
|
|
158
162
|
"sonda": "^0.14.0",
|
|
159
163
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
160
164
|
"unocss": "^66.12.0",
|
|
161
|
-
"vite": "^8.3.
|
|
165
|
+
"vite": "^8.3.8",
|
|
162
166
|
"vitest": "^5.1.0"
|
|
163
167
|
},
|
|
164
168
|
"peerDependencies": {
|
|
165
|
-
"astro": "^7.4.
|
|
166
|
-
},
|
|
167
|
-
"overrides": {
|
|
168
|
-
"satteri": "0.11.6"
|
|
169
|
+
"astro": "^7.4.10"
|
|
169
170
|
}
|
|
170
171
|
}
|
package/src/env.d.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -61,7 +61,6 @@ import {
|
|
|
61
61
|
} from "./internal/satteri-config";
|
|
62
62
|
import { searchStylePlugin } from "./internal/search-style";
|
|
63
63
|
import { createViteAliases } from "./internal/vite-aliases";
|
|
64
|
-
import { webMcpMutationsPlugin } from "./internal/webmcp-mutations";
|
|
65
64
|
import { featureFlagsHast, featureFlagsMdast } from "./plugins/feature-flags";
|
|
66
65
|
import { imageCaptions } from "./plugins/image-captions";
|
|
67
66
|
import { imageDimensions } from "./plugins/image-dimensions";
|
|
@@ -176,7 +175,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
176
175
|
},
|
|
177
176
|
vite: {
|
|
178
177
|
optimizeDeps: {
|
|
179
|
-
exclude: ["@
|
|
178
|
+
exclude: ["@w3c/webmcp-polyfill"],
|
|
180
179
|
// Discover lazy page features before the first visit:
|
|
181
180
|
// Vite re-optimization would otherwise reload the
|
|
182
181
|
// entire document midway through a Swup navigation.
|
|
@@ -224,7 +223,6 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
224
223
|
},
|
|
225
224
|
swupDebugPlugin(command === "dev"),
|
|
226
225
|
searchStylePlugin(command === "dev", config.base),
|
|
227
|
-
webMcpMutationsPlugin(),
|
|
228
226
|
...(command === "build"
|
|
229
227
|
? [markdownPropagationPlugin(), compactStylesPlugin(srcDir)]
|
|
230
228
|
: []),
|
|
@@ -9,20 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { AstroIntegrationLogger } from "astro";
|
|
11
11
|
|
|
12
|
+
import { escape as escapeHtml } from "html-escaper";
|
|
12
13
|
import { defineMdastPlugin, type MdastNode, type MdastVisitorContext } from "satteri";
|
|
13
14
|
import temmlLib, { type Options as TemmlOptions } from "temml";
|
|
14
15
|
|
|
15
16
|
const emptyOptions = {};
|
|
16
17
|
|
|
17
|
-
function escapeHtml(value: string): string {
|
|
18
|
-
return value
|
|
19
|
-
.replaceAll("&", "&")
|
|
20
|
-
.replaceAll("<", "<")
|
|
21
|
-
.replaceAll(">", ">")
|
|
22
|
-
.replaceAll('"', """)
|
|
23
|
-
.replaceAll("'", "'");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
type MathNode = Extract<MdastNode, { type: "math" | "inlineMath" }>;
|
|
27
19
|
|
|
28
20
|
function renderTemmlError(value: string, error: unknown, options: TemmlOptions): string {
|
|
@@ -73,9 +73,19 @@ export function installSessionBackground(): void {
|
|
|
73
73
|
},
|
|
74
74
|
);
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* 装饰背景不参与 LCP,也不该和首屏脚本/字体抢带宽。
|
|
78
|
+
* DOM 就绪后先让关键资源占用连接,再在空闲时请求;
|
|
79
|
+
* `timeout` 兜底防止页面持续繁忙时背景永远缺席。
|
|
80
|
+
*/
|
|
81
|
+
const schedule = () => {
|
|
82
|
+
const idle = globalThis.requestIdleCallback;
|
|
83
|
+
if (typeof idle === "function") idle(start, { timeout: 1000 });
|
|
84
|
+
else setTimeout(start, 200);
|
|
85
|
+
};
|
|
76
86
|
if (document.readyState === "loading")
|
|
77
|
-
document.addEventListener("DOMContentLoaded",
|
|
78
|
-
else
|
|
87
|
+
document.addEventListener("DOMContentLoaded", schedule, { once: true });
|
|
88
|
+
else schedule();
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
installSessionBackground();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import escapeStringRegexp from "escape-string-regexp";
|
|
2
|
+
import { escape as escapeHtml } from "html-escaper";
|
|
1
3
|
import {
|
|
2
4
|
CJK,
|
|
3
5
|
type CjkDocument,
|
|
@@ -37,19 +39,12 @@ export interface NativeSearch {
|
|
|
37
39
|
mergeIndex: (url: string, options?: Record<string, unknown>) => Promise<void>;
|
|
38
40
|
}
|
|
39
41
|
const logger = createClientLogger("search/cjk");
|
|
40
|
-
const escapeHtml = (text: string) =>
|
|
41
|
-
text.replace(
|
|
42
|
-
/[&<>"']/gu,
|
|
43
|
-
(char) =>
|
|
44
|
-
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
42
|
function excerpt(content: string, terms: string[]) {
|
|
48
43
|
const position = Math.max(0, normalizeSearch(content).indexOf(terms[0] ?? ""));
|
|
49
44
|
const start = Math.max(0, position - 45);
|
|
50
45
|
const plain = `${start ? "…" : ""}${content.slice(start, start + 180)}${content.length > start + 180 ? "…" : ""}`;
|
|
51
46
|
const pattern = terms
|
|
52
|
-
.map((term) => term
|
|
47
|
+
.map((term) => escapeStringRegexp(term))
|
|
53
48
|
.sort((a, b) => b.length - a.length)
|
|
54
49
|
.join("|");
|
|
55
50
|
const regex = new RegExp(pattern, "giu");
|
package/src/scripts/webmcp.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 工具描述跟随站点语言(site.yml 的 lang):中文站显示中文说明,
|
|
11
11
|
* 英文站显示英文说明,保证 AI 代理与评审(如 webmcp.com)能读懂。
|
|
12
12
|
*
|
|
13
|
-
* 浏览器无原生 modelContext
|
|
13
|
+
* 浏览器无原生 modelContext 时,由官方 @w3c/webmcp-polyfill
|
|
14
14
|
* 提供兜底实现;两者都没有则静默跳过注册,不影响普通访问者。
|
|
15
15
|
*
|
|
16
16
|
* Spec: https://github.com/webmachinelearning/webmcp
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
// 浏览器无原生 document.modelContext 时由 polyfill 兜底安装;
|
|
19
19
|
// 有原生实现或已存在 WebMCP-aware 扩展时,polyfill 检测到后自动 no-op。
|
|
20
20
|
// ESM entry exports installWebMCP; importing it alone does not install the API.
|
|
21
|
-
import { installWebMCP } from "@
|
|
21
|
+
import { installWebMCP } from "@w3c/webmcp-polyfill";
|
|
22
|
+
import type { WebMCP } from "webmcp-types";
|
|
22
23
|
import { createPostLookup } from "../utils/post-lookup";
|
|
23
24
|
import { sitePath } from "../utils/site-path";
|
|
24
25
|
import { createClientLogger } from "./logger";
|
|
@@ -38,27 +39,8 @@ declare global {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
name: string;
|
|
44
|
-
title?: string;
|
|
45
|
-
description: string;
|
|
46
|
-
inputSchema?: object;
|
|
47
|
-
annotations?: {
|
|
48
|
-
readOnlyHint?: boolean;
|
|
49
|
-
untrustedContentHint?: boolean;
|
|
50
|
-
};
|
|
51
|
-
execute: (
|
|
52
|
-
input: Record<string, unknown>,
|
|
53
|
-
options?: { signal?: AbortSignal },
|
|
54
|
-
) => Promise<unknown>;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ModelContext {
|
|
58
|
-
registerTool: (tool: WebMCPTool, options?: { signal?: AbortSignal }) => Promise<undefined>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type DocumentWithModelContext = { modelContext?: ModelContext };
|
|
42
|
+
/** 工具定义直接使用官方 webmcp-types 的 ModelContextTool,不自写最小接口。 */
|
|
43
|
+
type WebMCPTool = WebMCP.ModelContextTool;
|
|
62
44
|
|
|
63
45
|
// ---------------------------------------------------------------------------
|
|
64
46
|
// 语言选择(跟随站点 site.yml 的 lang)
|
|
@@ -763,7 +745,10 @@ function buildTools(): WebMCPTool[] {
|
|
|
763
745
|
const execute = tool.execute;
|
|
764
746
|
return {
|
|
765
747
|
...tool,
|
|
766
|
-
execute: async (
|
|
748
|
+
execute: async (
|
|
749
|
+
input: Record<string, unknown>,
|
|
750
|
+
options?: WebMCP.ToolExecuteCallbackOptions,
|
|
751
|
+
) => {
|
|
767
752
|
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
768
753
|
return err("BAD_INPUT", pick("参数必须是对象", "Input must be an object"));
|
|
769
754
|
const properties =
|
|
@@ -773,7 +758,10 @@ function buildTools(): WebMCPTool[] {
|
|
|
773
758
|
return err("BAD_INPUT", pick("包含未知参数", "Unknown input property"));
|
|
774
759
|
if (options?.signal?.aborted) throw options.signal.reason;
|
|
775
760
|
try {
|
|
776
|
-
const result = await execute(
|
|
761
|
+
const result = await execute(
|
|
762
|
+
input,
|
|
763
|
+
options ?? { signal: new AbortController().signal },
|
|
764
|
+
);
|
|
777
765
|
options?.signal?.throwIfAborted();
|
|
778
766
|
return result;
|
|
779
767
|
} catch (error) {
|
|
@@ -799,8 +787,7 @@ function buildTools(): WebMCPTool[] {
|
|
|
799
787
|
async function registerTools(
|
|
800
788
|
signal: AbortSignal,
|
|
801
789
|
): Promise<{ registered: string[]; reason?: string }> {
|
|
802
|
-
const
|
|
803
|
-
const ctx = doc.modelContext;
|
|
790
|
+
const ctx = document.modelContext;
|
|
804
791
|
if (!ctx || typeof ctx.registerTool !== "function") {
|
|
805
792
|
logger.debug("modelContext unavailable; registration skipped");
|
|
806
793
|
return { registered: [], reason: "unsupported" };
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import type { Plugin } from "vite";
|
|
3
|
-
import { affectsDeclarativeTools } from "../scripts/webmcp-mutations";
|
|
4
|
-
|
|
5
|
-
const observer = "const observer = new MutationObserver(sync);";
|
|
6
|
-
|
|
7
|
-
/** Upstream 5.1.0 rescans every element even for unrelated clock/background mutations. */
|
|
8
|
-
export function optimizeWebMcpMutations(code: string, version: string): string | undefined {
|
|
9
|
-
if (
|
|
10
|
-
version !== "5.1.0" ||
|
|
11
|
-
!code.includes("function installDeclarativeForms(document, context)") ||
|
|
12
|
-
code.split(observer).length !== 2
|
|
13
|
-
)
|
|
14
|
-
return;
|
|
15
|
-
return code.replace(
|
|
16
|
-
observer,
|
|
17
|
-
`const relevant = ${affectsDeclarativeTools.toString()};\nconst observer = new MutationObserver((records) => { if (relevant(records)) sync(); });`,
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function webMcpMutationsPlugin(): Plugin {
|
|
22
|
-
const entry = Bun.resolveSync(
|
|
23
|
-
"@mcp-b/webmcp-polyfill",
|
|
24
|
-
Bun.fileURLToPath(new URL(".", import.meta.url)),
|
|
25
|
-
);
|
|
26
|
-
const version = (
|
|
27
|
-
JSON.parse(
|
|
28
|
-
readFileSync(
|
|
29
|
-
Bun.fileURLToPath(new URL("../package.json", Bun.pathToFileURL(entry))),
|
|
30
|
-
"utf8",
|
|
31
|
-
),
|
|
32
|
-
) as { version: string }
|
|
33
|
-
).version;
|
|
34
|
-
return {
|
|
35
|
-
name: "stalux:webmcp-mutations",
|
|
36
|
-
enforce: "pre",
|
|
37
|
-
transform(code, id) {
|
|
38
|
-
if (!id.replaceAll("\\", "/").endsWith("/@mcp-b/webmcp-polyfill/dist/index.js")) return;
|
|
39
|
-
const optimized = optimizeWebMcpMutations(code, version);
|
|
40
|
-
if (optimized) return { code: optimized, map: null };
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/** A clock tick or background fade cannot change declarative tool definitions. */
|
|
2
|
-
export function affectsDeclarativeTools(records: MutationRecord[]): boolean {
|
|
3
|
-
const selector = "form[toolname],[form],label,slot";
|
|
4
|
-
const containsTools = (node: Node): boolean => {
|
|
5
|
-
if (!(node instanceof Element)) return false;
|
|
6
|
-
if (Element.prototype.matches.call(node, selector)) return true;
|
|
7
|
-
if (Element.prototype.querySelector.call(node, selector)) return true;
|
|
8
|
-
// An already-created open shadow tree can be inserted along with its host.
|
|
9
|
-
for (const element of [node, ...Element.prototype.querySelectorAll.call(node, "*")]) {
|
|
10
|
-
if (Reflect.get(Element.prototype, "shadowRoot", element)) return true;
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
13
|
-
};
|
|
14
|
-
return records.some((record) => {
|
|
15
|
-
if (
|
|
16
|
-
record.type === "attributes" &&
|
|
17
|
-
(record.attributeName === "form" || record.attributeName === "toolname")
|
|
18
|
-
)
|
|
19
|
-
return true;
|
|
20
|
-
const element =
|
|
21
|
-
record.target instanceof Element ? record.target : record.target.parentElement;
|
|
22
|
-
if (element && Element.prototype.closest.call(element, selector)) return true;
|
|
23
|
-
if (record.type === "childList")
|
|
24
|
-
return [...record.addedNodes, ...record.removedNodes].some(containsTools);
|
|
25
|
-
return false;
|
|
26
|
-
});
|
|
27
|
-
}
|