mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-28 19:35:27 +08:00
fix: preserve nested navigation logos
This commit is contained in:
Generated
+4
-3
@@ -14,6 +14,7 @@
|
||||
"fumadocs-ui": "npm:@fumadocs/base-ui@^16.15.6",
|
||||
"fumapress": "^1.2.0",
|
||||
"hono": "^4.9.9",
|
||||
"lucide-react": "^1.41.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"shiki": "^3.20.0",
|
||||
@@ -4143,9 +4144,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.40.0.tgz",
|
||||
"integrity": "sha512-MaG+8WnOXkDWz9XeElj7TnQ890tTZUB0a36i03aRRCKGWE6e7jJpmdCvtxxuxcjjdqyN6m4sL6qlHVuSUDtYgg==",
|
||||
"version": "1.41.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.41.0.tgz",
|
||||
"integrity": "sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"fumadocs-ui": "npm:@fumadocs/base-ui@^16.15.6",
|
||||
"fumapress": "^1.2.0",
|
||||
"hono": "^4.9.9",
|
||||
"lucide-react": "^1.41.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"shiki": "^3.20.0",
|
||||
|
||||
+37
-1
@@ -9,6 +9,7 @@ import { update } from "fumadocs-core/source";
|
||||
import { uiTranslations } from "fumadocs-ui/i18n";
|
||||
import { defineDocs } from "fumadocs-mdx/macro";
|
||||
import { createOpenAPI, type OpenAPIServer } from "fumadocs-openapi/server";
|
||||
import { Bot } from "lucide-react";
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -23,6 +24,9 @@ function nodeName(node: PageTreeNode) {
|
||||
}
|
||||
|
||||
function renderNavigationIcon(icon: PageTreeNode["icon"]) {
|
||||
if (icon === "robot") {
|
||||
return <Bot aria-hidden="true" className="size-4 shrink-0" />;
|
||||
}
|
||||
if (typeof icon !== "string" || !icon.startsWith("/")) return icon;
|
||||
return (
|
||||
<img
|
||||
@@ -48,6 +52,35 @@ function renderNavigationIcons(node: PageTreeNode): PageTreeNode {
|
||||
return { ...node, icon: renderNavigationIcon(node.icon) };
|
||||
}
|
||||
|
||||
|
||||
type ConfiguredNavigationGroup = {
|
||||
group: string;
|
||||
icon?: string;
|
||||
pages?: unknown[];
|
||||
};
|
||||
|
||||
function restoreConfiguredFolderIcons(
|
||||
nodes: PageTreeNode[],
|
||||
configuredPages: unknown[],
|
||||
): PageTreeNode[] {
|
||||
const configuredGroups = new Map<string, ConfiguredNavigationGroup>();
|
||||
for (const entry of configuredPages) {
|
||||
if (!entry || typeof entry !== "object" || !("group" in entry)) continue;
|
||||
const group = entry as ConfiguredNavigationGroup;
|
||||
configuredGroups.set(group.group, group);
|
||||
}
|
||||
return nodes.map((node) => {
|
||||
if (node.type !== "folder") return node;
|
||||
const configured = configuredGroups.get(nodeName(node));
|
||||
if (!configured) return node;
|
||||
return {
|
||||
...node,
|
||||
icon: configured.icon ?? node.icon,
|
||||
children: restoreConfiguredFolderIcons(node.children, configured.pages ?? []),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const openApiTagOrder = Object.fromEntries(LOCALES.map((locale) => {
|
||||
const spec = JSON.parse(readFileSync(`openapi/service-api-${locale}.json`, "utf8"));
|
||||
const tags: string[] = [];
|
||||
@@ -115,7 +148,10 @@ function sectionizeMintlifyTree(
|
||||
const sourceGroup = sourceGroups.get(group.group);
|
||||
if (!sourceGroup) continue;
|
||||
if (sourceGroup.index) children.push(sourceGroup.index);
|
||||
children.push(...sourceGroup.children);
|
||||
children.push(...restoreConfiguredFolderIcons(
|
||||
sourceGroup.children,
|
||||
"pages" in group ? group.pages ?? [] : [],
|
||||
));
|
||||
}
|
||||
const tab: Folder = { ...node, root: true, children };
|
||||
return tab;
|
||||
|
||||
@@ -119,6 +119,40 @@ test("platform navigation renders logo assets instead of icon path text", async
|
||||
}
|
||||
});
|
||||
|
||||
test("nested bot groups preserve the logos declared in docs.json", async () => {
|
||||
const html = await readFile(
|
||||
path.join(publicRoot, "zh/usage/platforms/discord/index.html"),
|
||||
"utf8",
|
||||
);
|
||||
const asideStart = html.indexOf('<aside id="nd-sidebar"');
|
||||
const asideEnd = html.indexOf("</aside>", asideStart);
|
||||
const sidebar = html.slice(asideStart, asideEnd);
|
||||
for (const [title, logo] of [
|
||||
["企业微信", "wecom.svg"],
|
||||
["微信", "wechat.svg"],
|
||||
["QQ 官方机器人", "qq.svg"],
|
||||
["QQ (OneBot v11)", "qq.svg"],
|
||||
]) {
|
||||
const escapedTitle = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const escapedLogo = logo.replace(".", "\\.");
|
||||
assert.match(
|
||||
sidebar,
|
||||
new RegExp(`<img[^>]+src="/images/platforms/${escapedLogo}"[^>]*>\\s*${escapedTitle}<svg`),
|
||||
`zh sidebar is missing the ${logo} logo for ${title}`,
|
||||
);
|
||||
}
|
||||
assert.doesNotMatch(
|
||||
sidebar,
|
||||
/>robot(?:<!-- -->)?Satori/,
|
||||
"zh sidebar exposes the named robot icon as text",
|
||||
);
|
||||
assert.match(
|
||||
sidebar,
|
||||
/<svg[^>]+class="[^"]*lucide-bot[^"]*"[^>]*>[\s\S]*?<\/svg>Satori/,
|
||||
"zh sidebar is missing the Satori robot icon",
|
||||
);
|
||||
});
|
||||
|
||||
test("all locales emit the OpenAPI surface with endpoint semantics", async () => {
|
||||
const representatives = {
|
||||
en: ["system/get-system-information", "Get system information", "GET", "/api/v1/system/info"],
|
||||
|
||||
Reference in New Issue
Block a user