mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-29 20:05:25 +08:00
feat: improve documentation sidebar quick links (#119)
* feat: improve documentation sidebar quick links * test: update sidebar navigation expectations --------- Co-authored-by: dadachann <[email protected]>
This commit is contained in:
@@ -25,31 +25,32 @@
|
||||
},
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "Home",
|
||||
"href": "https://langbot.app/en"
|
||||
},
|
||||
{
|
||||
"label": "Cloud",
|
||||
"href": "https://space.langbot.app/cloud?language=en"
|
||||
"href": "https://space.langbot.app/cloud?language=en",
|
||||
"icon": "cloud"
|
||||
},
|
||||
{
|
||||
"label": "Extensions",
|
||||
"href": "https://space.langbot.app/market?language=en"
|
||||
"href": "https://space.langbot.app/market?language=en",
|
||||
"icon": "blocks"
|
||||
},
|
||||
{
|
||||
"label": "Blog",
|
||||
"href": "https://langbot.app/en/blog"
|
||||
"href": "https://langbot.app/en/blog",
|
||||
"icon": "newspaper"
|
||||
},
|
||||
{
|
||||
"label": "Roadmap",
|
||||
"href": "https://langbot.app/en/roadmap"
|
||||
"href": "https://langbot.app/en/roadmap",
|
||||
"icon": "map"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
"type": "button",
|
||||
"label": "GitHub",
|
||||
"href": "https://github.com/langbot-app/LangBot"
|
||||
"href": "https://github.com/langbot-app/LangBot",
|
||||
"icon": "github"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -780,31 +781,32 @@
|
||||
],
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "Home",
|
||||
"href": "https://langbot.app/en"
|
||||
},
|
||||
{
|
||||
"label": "Cloud",
|
||||
"href": "https://space.langbot.app/cloud?language=en"
|
||||
"href": "https://space.langbot.app/cloud?language=en",
|
||||
"icon": "cloud"
|
||||
},
|
||||
{
|
||||
"label": "Extensions",
|
||||
"href": "https://space.langbot.app/market?language=en"
|
||||
"href": "https://space.langbot.app/market?language=en",
|
||||
"icon": "blocks"
|
||||
},
|
||||
{
|
||||
"label": "Blog",
|
||||
"href": "https://langbot.app/en/blog"
|
||||
"href": "https://langbot.app/en/blog",
|
||||
"icon": "newspaper"
|
||||
},
|
||||
{
|
||||
"label": "Roadmap",
|
||||
"href": "https://langbot.app/en/roadmap"
|
||||
"href": "https://langbot.app/en/roadmap",
|
||||
"icon": "map"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
"type": "button",
|
||||
"label": "GitHub",
|
||||
"href": "https://github.com/langbot-app/LangBot"
|
||||
"href": "https://github.com/langbot-app/LangBot",
|
||||
"icon": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1091,31 +1093,32 @@
|
||||
],
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "首页",
|
||||
"href": "https://langbot.app/zh"
|
||||
},
|
||||
{
|
||||
"label": "云服务",
|
||||
"href": "https://space.langbot.app/cloud?language=zh"
|
||||
"href": "https://space.langbot.app/cloud?language=zh",
|
||||
"icon": "cloud"
|
||||
},
|
||||
{
|
||||
"label": "扩展",
|
||||
"href": "https://space.langbot.app/market?language=zh"
|
||||
"href": "https://space.langbot.app/market?language=zh",
|
||||
"icon": "blocks"
|
||||
},
|
||||
{
|
||||
"label": "博客",
|
||||
"href": "https://langbot.app/zh/blog"
|
||||
"href": "https://langbot.app/zh/blog",
|
||||
"icon": "newspaper"
|
||||
},
|
||||
{
|
||||
"label": "路线图",
|
||||
"href": "https://langbot.app/zh/roadmap"
|
||||
"href": "https://langbot.app/zh/roadmap",
|
||||
"icon": "map"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
"type": "button",
|
||||
"label": "GitHub",
|
||||
"href": "https://github.com/langbot-app/LangBot"
|
||||
"href": "https://github.com/langbot-app/LangBot",
|
||||
"icon": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1377,31 +1380,32 @@
|
||||
],
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "ホーム",
|
||||
"href": "https://langbot.app/ja"
|
||||
},
|
||||
{
|
||||
"label": "クラウド",
|
||||
"href": "https://space.langbot.app/cloud?language=ja"
|
||||
"href": "https://space.langbot.app/cloud?language=ja",
|
||||
"icon": "cloud"
|
||||
},
|
||||
{
|
||||
"label": "拡張機能",
|
||||
"href": "https://space.langbot.app/market?language=ja"
|
||||
"href": "https://space.langbot.app/market?language=ja",
|
||||
"icon": "blocks"
|
||||
},
|
||||
{
|
||||
"label": "ブログ",
|
||||
"href": "https://langbot.app/ja/blog"
|
||||
"href": "https://langbot.app/ja/blog",
|
||||
"icon": "newspaper"
|
||||
},
|
||||
{
|
||||
"label": "ロードマップ",
|
||||
"href": "https://langbot.app/ja/roadmap"
|
||||
"href": "https://langbot.app/ja/roadmap",
|
||||
"icon": "map"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
"type": "button",
|
||||
"label": "GitHub",
|
||||
"href": "https://github.com/langbot-app/LangBot"
|
||||
"href": "https://github.com/langbot-app/LangBot",
|
||||
"icon": "github"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-11
@@ -9,7 +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 { Blocks, Bot, Cloud, ExternalLink, Map as MapIcon, Newspaper } from "lucide-react";
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -204,21 +204,73 @@ function collectRouteLocales() {
|
||||
|
||||
const routeLocales = collectRouteLocales();
|
||||
|
||||
function GithubMark(props: import("react").ComponentProps<"svg">) {
|
||||
return <svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||
<path d="M12 .7a11.5 11.5 0 0 0-3.64 22.4c.58.1.79-.25.79-.56v-2.02c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.78 1.2 1.78 1.2 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.74-1.55-2.57-.29-5.27-1.28-5.27-5.69 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.47.11-3.05 0 0 .97-.31 3.16 1.18a10.96 10.96 0 0 1 5.75 0c2.2-1.49 3.16-1.18 3.16-1.18.63 1.58.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.42-2.71 5.39-5.29 5.68.42.36.79 1.07.79 2.16v3.2c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .7Z" />
|
||||
</svg>;
|
||||
}
|
||||
|
||||
const QUICK_LINK_ICONS = {
|
||||
blocks: Blocks,
|
||||
cloud: Cloud,
|
||||
github: GithubMark,
|
||||
map: MapIcon,
|
||||
newspaper: Newspaper,
|
||||
} as const;
|
||||
|
||||
type QuickLinkIcon = keyof typeof QUICK_LINK_ICONS;
|
||||
type ConfiguredNavbarLink = {
|
||||
href: string;
|
||||
icon?: QuickLinkIcon;
|
||||
label?: string;
|
||||
};
|
||||
type ConfiguredNavbar = {
|
||||
links?: ConfiguredNavbarLink[];
|
||||
primary?: ConfiguredNavbarLink & { type?: string };
|
||||
};
|
||||
|
||||
function quickLinkIcon(name: QuickLinkIcon | undefined) {
|
||||
if (!name) return undefined;
|
||||
const Icon = QUICK_LINK_ICONS[name];
|
||||
return <Icon aria-hidden="true" className="size-4 shrink-0" />;
|
||||
}
|
||||
|
||||
function quickLinkText(text: string) {
|
||||
return <span className="flex min-w-0 flex-1 items-center justify-between gap-2">
|
||||
<span>{text}</span>
|
||||
<ExternalLink aria-hidden="true" className="size-3 shrink-0 text-fd-muted-foreground" />
|
||||
</span>;
|
||||
}
|
||||
|
||||
function localizedNavbar(lang: string | undefined) {
|
||||
const locale = LOCALES.includes(lang as (typeof LOCALES)[number]) ? lang as (typeof LOCALES)[number] : "en";
|
||||
const language = mintlifyDocs.navigation.languages?.find(
|
||||
(entry) => entry.language === MINTLIFY_LOCALE[locale],
|
||||
);
|
||||
const navbar = language?.navbar ?? mintlifyDocs.navbar;
|
||||
const navbar = (language?.navbar ?? mintlifyDocs.navbar) as ConfiguredNavbar | undefined;
|
||||
const linkItem = (link: ConfiguredNavbarLink) => ({
|
||||
text: quickLinkText(link.label ?? link.href),
|
||||
url: link.href,
|
||||
icon: quickLinkIcon(link.icon),
|
||||
external: true,
|
||||
});
|
||||
return [
|
||||
...(navbar?.links ?? []).map((link) => ({ text: link.label ?? link.href, url: link.href })),
|
||||
...(navbar?.primary ? [{
|
||||
type: "button" as const,
|
||||
text: navbar.primary.label ?? navbar.primary.href,
|
||||
url: navbar.primary.href,
|
||||
}] : []),
|
||||
...(navbar?.links ?? []).map(linkItem),
|
||||
...(navbar?.primary ? [{ ...linkItem(navbar.primary), type: "button" as const }] : []),
|
||||
];
|
||||
}
|
||||
|
||||
function docsBrand() {
|
||||
return <span className="inline-flex items-center gap-2">
|
||||
<img
|
||||
src="/langbot-logo.png"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="size-6 shrink-0 rounded-md object-contain"
|
||||
/>
|
||||
<span>LangBot Docs</span>
|
||||
</span>;
|
||||
}
|
||||
const enOpenAPI = createOpenAPI({ input: ["openapi/service-api-en.json"] });
|
||||
const zhOpenAPI = createOpenAPI({ input: ["openapi/service-api-zh.json"] });
|
||||
const jaOpenAPI = createOpenAPI({ input: ["openapi/service-api-ja.json"] });
|
||||
@@ -306,9 +358,16 @@ export default defineConfig({
|
||||
},
|
||||
i18n,
|
||||
translations,
|
||||
defaultLayoutProps: ({ lang }) => ({
|
||||
links: localizedNavbar(lang),
|
||||
}),
|
||||
defaultLayoutProps: ({ lang }) => {
|
||||
const locale = LOCALES.includes(lang as (typeof LOCALES)[number]) ? lang as (typeof LOCALES)[number] : "en";
|
||||
return {
|
||||
links: localizedNavbar(locale),
|
||||
nav: {
|
||||
title: docsBrand(),
|
||||
url: `/${locale}/insight/guide`,
|
||||
},
|
||||
};
|
||||
},
|
||||
meta: {
|
||||
page: (page) => {
|
||||
const locale = page.locale as (typeof LOCALES)[number];
|
||||
|
||||
@@ -241,18 +241,24 @@ function extractHeadLinks(html, rel) {
|
||||
|
||||
test("rendered navbar and documentation tree use each locale's docs.json labels", async () => {
|
||||
const expected = {
|
||||
en: ["Home", "https://langbot.app/en", "Roadmap", "https://langbot.app/en/roadmap", "Guides", "Quick Start", "Installation", "Developers", "Articles", "API Reference"],
|
||||
zh: ["首页", "https://langbot.app/zh", "路线图", "https://langbot.app/zh/roadmap", "指南", "快速开始", "安装部署", "开发者", "文章", "API 参考"],
|
||||
ja: ["ホーム", "https://langbot.app/ja", "ロードマップ", "https://langbot.app/ja/roadmap", "ガイド", "クイックスタート", "インストール", "開発者", "記事", "API リファレンス"],
|
||||
en: ["Roadmap", "https://langbot.app/en/roadmap", "Guides", "Quick Start", "Installation", "Developers", "Articles", "API Reference"],
|
||||
zh: ["路线图", "https://langbot.app/zh/roadmap", "指南", "快速开始", "安装部署", "开发者", "文章", "API 参考"],
|
||||
ja: ["ロードマップ", "https://langbot.app/ja/roadmap", "ガイド", "クイックスタート", "インストール", "開発者", "記事", "API リファレンス"],
|
||||
};
|
||||
const removedHomeLinks = {
|
||||
en: "https://langbot.app/en",
|
||||
zh: "https://langbot.app/zh",
|
||||
ja: "https://langbot.app/ja",
|
||||
};
|
||||
for (const locale of locales) {
|
||||
const html = await readFile(path.join(publicRoot, locale, "insight/guide/index.html"), "utf8");
|
||||
for (const value of expected[locale]) assert.ok(html.includes(value), `${locale} navbar missing ${value}`);
|
||||
assert.ok(!html.includes(`href="${removedHomeLinks[locale]}"`), `${locale} navbar still includes Home`);
|
||||
for (const languageName of ["English", "简体中文", "日本語"]) {
|
||||
assert.ok(html.includes(languageName), `${locale} language selector missing ${languageName}`);
|
||||
}
|
||||
for (const other of locales.filter((item) => item !== locale)) {
|
||||
assert.ok(!html.includes(expected[other][1]), `${locale} navbar includes ${other} home link`);
|
||||
assert.ok(!html.includes(expected[other][1]), `${locale} navbar includes ${other} roadmap link`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+18
-3
@@ -39,15 +39,15 @@ test("navbar is localized and links to locale-aware product pages", async () =>
|
||||
);
|
||||
const expected = {
|
||||
en: {
|
||||
labels: ["Home", "Cloud", "Extensions", "Blog", "Roadmap"],
|
||||
labels: ["Cloud", "Extensions", "Blog", "Roadmap"],
|
||||
roadmap: "https://langbot.app/en/roadmap",
|
||||
},
|
||||
cn: {
|
||||
labels: ["首页", "云服务", "扩展", "博客", "路线图"],
|
||||
labels: ["云服务", "扩展", "博客", "路线图"],
|
||||
roadmap: "https://langbot.app/zh/roadmap",
|
||||
},
|
||||
jp: {
|
||||
labels: ["ホーム", "クラウド", "拡張機能", "ブログ", "ロードマップ"],
|
||||
labels: ["クラウド", "拡張機能", "ブログ", "ロードマップ"],
|
||||
roadmap: "https://langbot.app/ja/roadmap",
|
||||
},
|
||||
};
|
||||
@@ -58,14 +58,29 @@ test("navbar is localized and links to locale-aware product pages", async () =>
|
||||
navbar.links.map((link) => link.label),
|
||||
expected[language].labels,
|
||||
);
|
||||
assert.deepEqual(
|
||||
navbar.links.map((link) => link.icon),
|
||||
["cloud", "blocks", "newspaper", "map"],
|
||||
);
|
||||
assert.equal(navbar.links.at(-1).href, expected[language].roadmap);
|
||||
assert.equal(navbar.primary.type, "button");
|
||||
assert.equal(navbar.primary.label, "GitHub");
|
||||
assert.equal(navbar.primary.icon, "github");
|
||||
assert.equal(
|
||||
navbar.primary.href,
|
||||
"https://github.com/langbot-app/LangBot",
|
||||
);
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
docs.navbar.links.map((link) => link.label),
|
||||
expected.en.labels,
|
||||
);
|
||||
assert.deepEqual(
|
||||
docs.navbar.links.map((link) => link.icon),
|
||||
["cloud", "blocks", "newspaper", "map"],
|
||||
);
|
||||
assert.equal(docs.navbar.primary.icon, "github");
|
||||
});
|
||||
|
||||
test("custom robots policy is served from the Mintlify project root", async () => {
|
||||
|
||||
Reference in New Issue
Block a user