openmanual 0.15.2 → 0.16.0
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/dist/bin.js +123 -35
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -15,6 +15,18 @@ const LogoSchema = z.union([z.string(), z.object({
|
|
|
15
15
|
light: z.string(),
|
|
16
16
|
dark: z.string()
|
|
17
17
|
})]);
|
|
18
|
+
/** Logo 显示位置 */
|
|
19
|
+
const LogoPositionSchema = z.enum(["sidebar", "header"]);
|
|
20
|
+
/**
|
|
21
|
+
* 顶级 Logo 配置(支持字符串简写和对象形式)
|
|
22
|
+
* - 字符串简写: "/logo.svg" → { light, dark: 同值, position: 'sidebar' }
|
|
23
|
+
* - 对象形式: { light, dark, position? }
|
|
24
|
+
*/
|
|
25
|
+
const TopLevelLogoSchema = z.union([z.string(), z.object({
|
|
26
|
+
light: z.string(),
|
|
27
|
+
dark: z.string(),
|
|
28
|
+
position: LogoPositionSchema.optional()
|
|
29
|
+
})]);
|
|
18
30
|
const FaviconSchema = z.string();
|
|
19
31
|
const NavbarSchema = z.object({
|
|
20
32
|
logo: LogoSchema.optional(),
|
|
@@ -96,6 +108,7 @@ const OpenManualConfigSchema = z.object({
|
|
|
96
108
|
locale: z.string().optional(),
|
|
97
109
|
contentPolicy: z.enum(["strict", "all"]).optional(),
|
|
98
110
|
favicon: FaviconSchema.optional(),
|
|
111
|
+
logo: TopLevelLogoSchema.optional(),
|
|
99
112
|
navbar: NavbarSchema.optional(),
|
|
100
113
|
header: TopBarSchema.optional(),
|
|
101
114
|
footer: FooterSchema.optional(),
|
|
@@ -145,6 +158,51 @@ function isSeparateTabMode(config) {
|
|
|
145
158
|
function isHeaderEnabled(config) {
|
|
146
159
|
return config.header !== void 0;
|
|
147
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* 将顶级 logo 配置标准化为 { light, dark } 形式
|
|
163
|
+
*/
|
|
164
|
+
function normalizeTopLevelLogo(logo) {
|
|
165
|
+
if (typeof logo === "string") return {
|
|
166
|
+
light: logo,
|
|
167
|
+
dark: logo,
|
|
168
|
+
position: "sidebar"
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
light: logo.light,
|
|
172
|
+
dark: logo.dark,
|
|
173
|
+
position: logo.position ?? "sidebar"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 解析有效的 Logo 配置(统一优先级链)
|
|
178
|
+
*
|
|
179
|
+
* 优先级:
|
|
180
|
+
* 1. config.logo(新顶级配置)
|
|
181
|
+
* 2. config.navbar.logo(旧 sidebar logo)
|
|
182
|
+
* 3. config.header.logo(旧 header logo)
|
|
183
|
+
* 4. undefined(调用方回退到 config.name)
|
|
184
|
+
*/
|
|
185
|
+
function resolveEffectiveLogo(config) {
|
|
186
|
+
if (config.logo) {
|
|
187
|
+
const { position, ...source } = normalizeTopLevelLogo(config.logo);
|
|
188
|
+
return {
|
|
189
|
+
source,
|
|
190
|
+
position
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (config.navbar?.logo) return {
|
|
194
|
+
source: config.navbar.logo,
|
|
195
|
+
position: "sidebar"
|
|
196
|
+
};
|
|
197
|
+
if (config.header?.logo) return {
|
|
198
|
+
source: config.header.logo,
|
|
199
|
+
position: "header"
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
source: void 0,
|
|
203
|
+
position: "sidebar"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
148
206
|
|
|
149
207
|
//#endregion
|
|
150
208
|
//#region src/core/config/loader.ts
|
|
@@ -183,17 +241,31 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
183
241
|
return mergeDefaults(result.data);
|
|
184
242
|
}
|
|
185
243
|
function mergeDefaults(config) {
|
|
244
|
+
const topLevelLogo = config.logo ? normalizeTopLevelLogo(config.logo) : null;
|
|
245
|
+
const topLevelLogoSource = topLevelLogo ? topLevelLogo.light === topLevelLogo.dark ? topLevelLogo.light : {
|
|
246
|
+
light: topLevelLogo.light,
|
|
247
|
+
dark: topLevelLogo.dark
|
|
248
|
+
} : null;
|
|
186
249
|
return {
|
|
187
250
|
...config,
|
|
188
251
|
contentPolicy: config.contentPolicy ?? "strict",
|
|
189
252
|
contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? "content",
|
|
190
253
|
outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? "dist",
|
|
191
254
|
locale: config.locale ?? DEFAULT_CONFIG.locale ?? "zh",
|
|
255
|
+
logo: topLevelLogo ? typeof config.logo === "string" ? config.logo : {
|
|
256
|
+
light: topLevelLogo.light,
|
|
257
|
+
dark: topLevelLogo.dark,
|
|
258
|
+
position: topLevelLogo.position
|
|
259
|
+
} : void 0,
|
|
192
260
|
navbar: {
|
|
193
261
|
...DEFAULT_CONFIG.navbar,
|
|
194
262
|
...config.navbar,
|
|
195
|
-
logo: config.navbar?.logo ?? config.name
|
|
263
|
+
logo: config.navbar?.logo ?? (topLevelLogo && topLevelLogo.position === "sidebar" ? topLevelLogoSource ?? config.name : config.name)
|
|
196
264
|
},
|
|
265
|
+
header: config.header ? {
|
|
266
|
+
...config.header,
|
|
267
|
+
logo: config.header.logo ?? (topLevelLogo && topLevelLogo.position === "header" ? topLevelLogoSource ?? void 0 : void 0)
|
|
268
|
+
} : void 0,
|
|
197
269
|
footer: {
|
|
198
270
|
...DEFAULT_CONFIG.footer,
|
|
199
271
|
...config.footer,
|
|
@@ -486,11 +558,6 @@ ${config.search?.position === "header" ? `
|
|
|
486
558
|
display: none;
|
|
487
559
|
}
|
|
488
560
|
` : ""}
|
|
489
|
-
|
|
490
|
-
/* 隐藏侧边栏顶部导航区域(logo/标题 + 折叠按钮容器) */
|
|
491
|
-
#nd-sidebar > div:first-child {
|
|
492
|
-
display: none;
|
|
493
|
-
}
|
|
494
561
|
`;
|
|
495
562
|
}
|
|
496
563
|
|
|
@@ -604,7 +671,7 @@ function generateLibSource(ctx) {
|
|
|
604
671
|
if (isOpenApiEnabled(ctx.config)) {
|
|
605
672
|
const separateTab = isSeparateTabMode(ctx.config);
|
|
606
673
|
const groupBy = ctx.config.openapi?.groupBy ?? "tag";
|
|
607
|
-
if (isI18n) return `import { docs } from '
|
|
674
|
+
if (isI18n) return `import { docs } from '@/.source/server';
|
|
608
675
|
import { loader, multiple } from 'fumadocs-core/source';
|
|
609
676
|
import { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';
|
|
610
677
|
import { openapi } from '@/lib/openapi';
|
|
@@ -631,7 +698,7 @@ export const source = loader(
|
|
|
631
698
|
},
|
|
632
699
|
);
|
|
633
700
|
`;
|
|
634
|
-
return `import { docs } from '
|
|
701
|
+
return `import { docs } from '@/.source/server';
|
|
635
702
|
import { loader, multiple } from 'fumadocs-core/source';
|
|
636
703
|
import { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';
|
|
637
704
|
import { openapi } from '@/lib/openapi';
|
|
@@ -651,7 +718,7 @@ ${!separateTab ? ` meta: true,\n groupBy: '${groupBy}',` : ""}
|
|
|
651
718
|
);
|
|
652
719
|
`;
|
|
653
720
|
}
|
|
654
|
-
if (isI18n) return `import { docs } from '
|
|
721
|
+
if (isI18n) return `import { docs } from '@/.source/server';
|
|
655
722
|
import { loader } from 'fumadocs-core/source';
|
|
656
723
|
import { i18n } from '@/lib/i18n';
|
|
657
724
|
|
|
@@ -661,7 +728,7 @@ export const source = loader({
|
|
|
661
728
|
i18n,
|
|
662
729
|
});
|
|
663
730
|
`;
|
|
664
|
-
return `import { docs } from '
|
|
731
|
+
return `import { docs } from '@/.source/server';
|
|
665
732
|
import { loader } from 'fumadocs-core/source';
|
|
666
733
|
|
|
667
734
|
export const source = loader({
|
|
@@ -733,11 +800,6 @@ const config = {
|
|
|
733
800
|
images: {
|
|
734
801
|
unoptimized: true,
|
|
735
802
|
},${rewritesBlock}
|
|
736
|
-
turbopack: {
|
|
737
|
-
resolveAlias: {
|
|
738
|
-
'collections/*': './.source/*',
|
|
739
|
-
},
|
|
740
|
-
},
|
|
741
803
|
};
|
|
742
804
|
|
|
743
805
|
export default withMDX(config);
|
|
@@ -794,7 +856,7 @@ export const APIPage = createAPIPage(openapi, {
|
|
|
794
856
|
//#endregion
|
|
795
857
|
//#region src/core/generator/package-json.ts
|
|
796
858
|
function getOpenManualVersion() {
|
|
797
|
-
return "0.
|
|
859
|
+
return "0.16.0";
|
|
798
860
|
}
|
|
799
861
|
function generatePackageJson(ctx) {
|
|
800
862
|
const openmanualVersion = getOpenManualVersion();
|
|
@@ -874,7 +936,13 @@ import { Callout, CalloutTitle, CalloutDescription } from '@/components/callout'
|
|
|
874
936
|
${allowedSlugsSnippet}
|
|
875
937
|
export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
|
|
876
938
|
const { slug } = await params;
|
|
877
|
-
|
|
939
|
+
let page = source.getPage(slug);
|
|
940
|
+
// Fallback: when slug is empty (root path /) and getPage returns undefined,
|
|
941
|
+
// try ['index'] — fumadocs-core's slugsPlugin may assign ["index"] to index.mdx
|
|
942
|
+
// due to slug de-duplication conflict, causing getPage([]) to miss it.
|
|
943
|
+
if (!page && (!slug || slug.length === 0)) {
|
|
944
|
+
page = source.getPage(['index']);
|
|
945
|
+
}
|
|
878
946
|
${isStrict ? `
|
|
879
947
|
if (!isAllowed(slug)) {
|
|
880
948
|
notFound();
|
|
@@ -922,14 +990,16 @@ export function generateStaticParams() {
|
|
|
922
990
|
let params = source.generateParams();
|
|
923
991
|
params = params.filter((p: { slug: string[] }) => isAllowed(p.slug));
|
|
924
992
|
if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {
|
|
925
|
-
params.
|
|
993
|
+
const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');
|
|
994
|
+
params.unshift({ ...(homepage ?? params[0]), slug: [] });
|
|
926
995
|
}
|
|
927
996
|
return params;
|
|
928
997
|
}` : `
|
|
929
998
|
export function generateStaticParams() {
|
|
930
999
|
const params = source.generateParams();
|
|
931
1000
|
if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {
|
|
932
|
-
params.
|
|
1001
|
+
const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');
|
|
1002
|
+
params.unshift({ ...(homepage ?? params[0]), slug: [] });
|
|
933
1003
|
}
|
|
934
1004
|
return params;
|
|
935
1005
|
}`}
|
|
@@ -960,7 +1030,13 @@ import { Callout, CalloutTitle, CalloutDescription } from '@/components/callout'
|
|
|
960
1030
|
${allowedSlugsSnippet}
|
|
961
1031
|
export default async function Page({ params }: { params: Promise<{ slug?: string[]; lang: string }> }) {
|
|
962
1032
|
const { slug, lang } = await params;
|
|
963
|
-
|
|
1033
|
+
let page = source.getPage(slug, lang);
|
|
1034
|
+
// Fallback: when slug is empty (root path /) and getPage returns undefined,
|
|
1035
|
+
// try ['index'] — fumadocs-core's slugsPlugin may assign ["index"] to index.mdx
|
|
1036
|
+
// due to slug de-duplication conflict, causing getPage([], lang) to miss it.
|
|
1037
|
+
if (!page && (!slug || slug.length === 0)) {
|
|
1038
|
+
page = source.getPage(['index'], lang);
|
|
1039
|
+
}
|
|
964
1040
|
${isStrict ? `
|
|
965
1041
|
if (!isAllowed(slug, lang)) {
|
|
966
1042
|
notFound();
|
|
@@ -1011,9 +1087,10 @@ export function generateStaticParams() {
|
|
|
1011
1087
|
const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];
|
|
1012
1088
|
for (const lang of languages) {
|
|
1013
1089
|
if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1090
|
+
const homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);
|
|
1091
|
+
const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);
|
|
1092
|
+
if (homepage || fallback) {
|
|
1093
|
+
params.unshift({ ...(homepage ?? fallback!), slug: [], lang });
|
|
1017
1094
|
}
|
|
1018
1095
|
}
|
|
1019
1096
|
}
|
|
@@ -1025,9 +1102,10 @@ export function generateStaticParams() {
|
|
|
1025
1102
|
const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];
|
|
1026
1103
|
for (const lang of languages) {
|
|
1027
1104
|
if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1105
|
+
const homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);
|
|
1106
|
+
const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);
|
|
1107
|
+
if (homepage || fallback) {
|
|
1108
|
+
params.unshift({ ...(homepage ?? fallback!), slug: [], lang });
|
|
1031
1109
|
}
|
|
1032
1110
|
}
|
|
1033
1111
|
}
|
|
@@ -1769,10 +1847,13 @@ async function generateAll(ctx) {
|
|
|
1769
1847
|
await mkdir(join(fullPath, ".."), { recursive: true });
|
|
1770
1848
|
await writeFile(fullPath, file.content, "utf-8");
|
|
1771
1849
|
}
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1850
|
+
const rawLogo = ctx.config.logo != null ? typeof ctx.config.logo === "string" ? ctx.config.logo : {
|
|
1851
|
+
light: ctx.config.logo.light,
|
|
1852
|
+
dark: ctx.config.logo.dark
|
|
1853
|
+
} : ctx.config.navbar?.logo;
|
|
1854
|
+
if (rawLogo && typeof rawLogo === "string" && isImagePath(rawLogo)) await ensureLogoFile(ctx, rawLogo, "light");
|
|
1855
|
+
else if (rawLogo && typeof rawLogo === "object") {
|
|
1856
|
+
const { light, dark } = resolveLogoPaths(rawLogo);
|
|
1776
1857
|
if (isImagePath(light)) await ensureLogoFile(ctx, light, "light");
|
|
1777
1858
|
if (isImagePath(dark) && dark !== light) await ensureLogoFile(ctx, dark, "dark");
|
|
1778
1859
|
}
|
|
@@ -1855,6 +1936,11 @@ function generateDocsLayout(ctx) {
|
|
|
1855
1936
|
const isOApi = isOpenApiEnabled(config);
|
|
1856
1937
|
const rootGroups = ctx.rootGroups;
|
|
1857
1938
|
const isHeaderSearch = config.search?.position === "header";
|
|
1939
|
+
const { source: logoSource, position: logoPosition } = resolveEffectiveLogo(config);
|
|
1940
|
+
const hasSidebarLogo = logoSource !== void 0 && logoPosition === "sidebar";
|
|
1941
|
+
const sidebarLogoImport = hasSidebarLogo ? "\nimport { NavLogo } from 'openmanual/components/nav-layout';" : "";
|
|
1942
|
+
const sidebarLogoProps = hasSidebarLogo ? resolveNavLogoProps(logoSource, config.name) : null;
|
|
1943
|
+
const sidebarBannerLine = hasSidebarLogo && sidebarLogoProps && !sidebarLogoProps.includes("type=\"text\"") ? `\n banner: <NavLogo ${sidebarLogoProps} />,` : "";
|
|
1858
1944
|
const linksArray = navLinks.map((l) => ({
|
|
1859
1945
|
text: l.label,
|
|
1860
1946
|
url: l.href,
|
|
@@ -1876,7 +1962,7 @@ function generateDocsLayout(ctx) {
|
|
|
1876
1962
|
if (isI18n) return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
1877
1963
|
import { baseOptions } from '@/lib/layout';
|
|
1878
1964
|
import { source } from '@/lib/source';
|
|
1879
|
-
import type { ReactNode } from 'react';${configDesc ? `\nconst configDescription = '${configDesc.replace(/'/g, "\\'")}' as const;\n` : ""}
|
|
1965
|
+
import type { ReactNode } from 'react';${sidebarLogoImport}${configDesc ? `\nconst configDescription = '${configDesc.replace(/'/g, "\\'")}' as const;\n` : ""}
|
|
1880
1966
|
export default async function DocsLayoutWrapper({
|
|
1881
1967
|
params,
|
|
1882
1968
|
children,
|
|
@@ -1894,7 +1980,8 @@ ${isOApi && separateTab ? ` const _omFirstApi = source.getPages(lang)?.find((p:
|
|
|
1894
1980
|
const docsOptions = {
|
|
1895
1981
|
...baseOptions(lang),
|
|
1896
1982
|
${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${configDesc ? "\n description: siteDescription," : ""}${isHeaderSearch ? "\n searchToggle: { enabled: false }," : ""}
|
|
1897
|
-
sidebar: { collapsible: false
|
|
1983
|
+
sidebar: { collapsible: false,${sidebarBannerLine}
|
|
1984
|
+
},
|
|
1898
1985
|
};
|
|
1899
1986
|
|
|
1900
1987
|
return (
|
|
@@ -1907,14 +1994,15 @@ ${isOApi && separateTab ? ` const _omFirstApi = source.getPages(lang)?.find((p:
|
|
|
1907
1994
|
return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
1908
1995
|
import { baseOptions } from '@/lib/layout';
|
|
1909
1996
|
import { source } from '@/lib/source';
|
|
1910
|
-
import type { ReactNode } from 'react';${isOApi && separateTab ? `
|
|
1997
|
+
import type { ReactNode } from 'react';${sidebarLogoImport}${isOApi && separateTab ? `
|
|
1911
1998
|
const _omFirstApi = source.getPages()?.find((p: any) => p.data?.type === 'openapi');
|
|
1912
1999
|
const _omApiUrl = _omFirstApi?.url ?? '/openapi';
|
|
1913
2000
|
` : ""}
|
|
1914
2001
|
const docsOptions = {
|
|
1915
2002
|
...baseOptions(),
|
|
1916
2003
|
${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${descLine}${isHeaderSearch ? "\n searchToggle: { enabled: false }," : ""}
|
|
1917
|
-
sidebar: { collapsible: false
|
|
2004
|
+
sidebar: { collapsible: false,${sidebarBannerLine}
|
|
2005
|
+
},
|
|
1918
2006
|
};
|
|
1919
2007
|
|
|
1920
2008
|
export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
|
|
@@ -2544,7 +2632,7 @@ const regenerateCommand = new Command("_regenerate").description("内部命令
|
|
|
2544
2632
|
//#endregion
|
|
2545
2633
|
//#region src/cli/bin.ts
|
|
2546
2634
|
function getVersion() {
|
|
2547
|
-
return "0.
|
|
2635
|
+
return "0.16.0";
|
|
2548
2636
|
}
|
|
2549
2637
|
const program = new Command();
|
|
2550
2638
|
const commandName = basename(process.argv[1] ?? "openmanual");
|