Skip to content

Commit fb405c5

Browse files
Harden docs highlighter and try-it guards
1 parent ca475b7 commit fb405c5

3 files changed

Lines changed: 63 additions & 30 deletions

File tree

docs/API.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ curl -X POST http://localhost:8080/api/admin/accounts/at \
650650

651651
- **`txt`** — 每行一个 Refresh Token:
652652

653-
```
653+
```text
654654
rt_xxxxxx1
655655
rt_xxxxxx2
656656
rt_xxxxxx3
@@ -666,7 +666,7 @@ curl -X POST http://localhost:8080/api/admin/accounts/at \
666666
```
667667

668668
- **`at_txt`** — 每行一个 Access Token(AT-only 模式):
669-
```
669+
```text
670670
eyJhbGciOiJSUzI1NiIs...token1
671671
eyJhbGciOiJSUzI1NiIs...token2
672672
```
@@ -705,7 +705,7 @@ curl -X POST http://localhost:8080/api/admin/accounts/import \
705705

706706
**响应:** SSE 流式进度
707707

708-
```
708+
```text
709709
data: {"type":"progress","current":5,"total":10,"success":3,"duplicate":1,"failed":1}
710710
711711
data: {"type":"complete","current":10,"total":10,"success":8,"duplicate":1,"failed":1}

frontend/src/hooks/useHighlighter.ts

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ function getHighlighter() {
1515
import("shiki/langs/json.mjs"),
1616
import("shiki/langs/shellscript.mjs"),
1717
import("shiki/langs/toml.mjs"),
18-
]).then(
19-
([core, engine, darkPlus, githubLight, json, shellscript, toml]) => {
20-
return core.createHighlighterCore({
21-
themes: [githubLight.default, darkPlus.default],
22-
langs: [json.default, toml.default, shellscript.default],
23-
engine: engine.createJavaScriptRegexEngine(),
24-
});
25-
},
26-
);
18+
])
19+
.then(
20+
([core, engine, darkPlus, githubLight, json, shellscript, toml]) => {
21+
return core.createHighlighterCore({
22+
themes: [githubLight.default, darkPlus.default],
23+
langs: [json.default, toml.default, shellscript.default],
24+
engine: engine.createJavaScriptRegexEngine(),
25+
});
26+
},
27+
)
28+
.catch((error) => {
29+
highlighterPromise = null;
30+
throw error;
31+
});
2732
}
2833
return highlighterPromise;
2934
}
@@ -75,31 +80,42 @@ export function useHighlightedHtml(code: string, lang?: string) {
7580
};
7681
}
7782

78-
getHighlighter().then((hl) => {
79-
if (cancelled) return;
80-
try {
81-
const result = hl.codeToHtml(code, {
82-
lang: resolvedLang,
83-
theme: isDark ? "dark-plus" : "github-light-default",
84-
});
85-
const cacheKey = `${isDark ? "dark" : "light"}:${resolvedLang}:${code}`;
86-
const nextHtml = isDark ? result : tuneLightTheme(result);
87-
if (htmlCache.size >= MAX_CACHE_SIZE) {
88-
const firstKey = htmlCache.keys().next().value;
89-
if (firstKey) htmlCache.delete(firstKey);
83+
getHighlighter()
84+
.then((hl) => {
85+
if (cancelled) return;
86+
try {
87+
const result = hl.codeToHtml(code, {
88+
lang: resolvedLang,
89+
theme: isDark ? "dark-plus" : "github-light-default",
90+
});
91+
const cacheKey = `${isDark ? "dark" : "light"}:${resolvedLang}:${code}`;
92+
const nextHtml = isDark ? result : tuneLightTheme(result);
93+
if (htmlCache.size >= MAX_CACHE_SIZE) {
94+
const firstKey = htmlCache.keys().next().value;
95+
if (firstKey) htmlCache.delete(firstKey);
96+
}
97+
htmlCache.set(cacheKey, nextHtml);
98+
setHtml(nextHtml);
99+
} catch (error) {
100+
console.warn("highlight failed", {
101+
resolvedLang,
102+
isDark,
103+
codeLength: code.length,
104+
error,
105+
});
106+
setHtml("");
90107
}
91-
htmlCache.set(cacheKey, nextHtml);
92-
setHtml(nextHtml);
93-
} catch (error) {
108+
})
109+
.catch((error) => {
110+
if (cancelled) return;
94111
console.warn("highlight failed", {
95112
resolvedLang,
96113
isDark,
97114
codeLength: code.length,
98115
error,
99116
});
100117
setHtml("");
101-
}
102-
});
118+
});
103119

104120
return () => {
105121
cancelled = true;

frontend/src/pages/docs/EndpointDoc.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,22 @@ export const EndpointDoc = memo(function EndpointDoc({
453453
const activeBody =
454454
responseExamples.find((r) => r.code === activeStatus)?.body ?? "";
455455
const [tryOpen, setTryOpen] = useState(false);
456+
const supportsJsonBody =
457+
method === "GET" ||
458+
method === "DELETE" ||
459+
!defaultBody ||
460+
(() => {
461+
try {
462+
JSON.parse(defaultBody);
463+
return true;
464+
} catch {
465+
return false;
466+
}
467+
})();
468+
const supportsTryIt =
469+
!path.includes(":") &&
470+
path !== "/api/admin/accounts/import" &&
471+
supportsJsonBody;
456472

457473
return (
458474
<Card id={id} className="mb-4 scroll-mt-20 py-0">
@@ -472,14 +488,15 @@ export const EndpointDoc = memo(function EndpointDoc({
472488
<Button
473489
size="sm"
474490
onClick={() => setTryOpen(true)}
491+
disabled={!supportsTryIt}
475492
className="h-8 gap-1.5 bg-emerald-600 text-white shrink-0 hover:bg-emerald-600/90 dark:bg-emerald-500/90 dark:hover:bg-emerald-500"
476493
>
477494
<Play className="size-3.5" />
478495
{t("apiRef.tryIt.button")}
479496
</Button>
480497
</div>
481498

482-
{tryOpen && (
499+
{supportsTryIt && tryOpen && (
483500
<TryItDialog
484501
open={tryOpen}
485502
onClose={() => setTryOpen(false)}

0 commit comments

Comments
 (0)