openmanual 0.13.0 → 0.14.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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
+ import { cn } from "./lib/utils.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { Info, KeyRound, Lightbulb, MessageCircleCheck, MessageCircleWarning, OctagonAlert, TriangleAlert } from "lucide-react";
4
- import { twMerge } from "tailwind-merge";
5
5
  //#region src/components/callout.tsx
6
6
  const iconClass = "size-5 -me-0.5 text-(--callout-color)";
7
7
  function resolveAlias(type) {
@@ -21,7 +21,7 @@ function Callout({ children, title, ...props }) {
21
21
  function CalloutContainer({ type: inputType = "info", icon, children, className, style, ...props }) {
22
22
  const type = resolveAlias(inputType);
23
23
  return /* @__PURE__ */ jsxs("div", {
24
- className: twMerge("flex gap-2 my-4 rounded-xl border p-3 text-sm text-(--callout-color)", className),
24
+ className: cn("flex gap-2 my-4 rounded-xl border p-3 text-sm text-(--callout-color)", className),
25
25
  style: {
26
26
  "--callout-color": `var(--callout-${type}-text)`,
27
27
  backgroundColor: `var(--callout-${type}-bg)`,
@@ -45,14 +45,14 @@ function CalloutContainer({ type: inputType = "info", icon, children, className,
45
45
  }
46
46
  function CalloutTitle({ children, className, ...props }) {
47
47
  return /* @__PURE__ */ jsx("p", {
48
- className: twMerge("font-medium my-0!", className),
48
+ className: cn("font-medium my-0!", className),
49
49
  ...props,
50
50
  children
51
51
  });
52
52
  }
53
53
  function CalloutDescription({ children, className, ...props }) {
54
54
  return /* @__PURE__ */ jsx("div", {
55
- className: twMerge("prose-no-margin empty:hidden", className),
55
+ className: cn("prose-no-margin empty:hidden", className),
56
56
  ...props,
57
57
  children
58
58
  });
@@ -0,0 +1,8 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ //#region src/lib/utils.ts
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+ //#endregion
8
+ export { cn };
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { cn } from "./lib/utils.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import { LocateFixed, RotateCcw, X, ZoomIn, ZoomOut } from "lucide-react";
4
5
  import Panzoom from "@panzoom/panzoom";
@@ -129,7 +130,7 @@ function MermaidContent({ chart }) {
129
130
  ref: overlayRef,
130
131
  role: "dialog",
131
132
  "aria-modal": dialogState !== "closed" ? "true" : void 0,
132
- className: `fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-200 ease-out ${dialogState === "closed" ? "pointer-events-none" : ""}`,
133
+ className: cn("fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-200 ease-out", dialogState === "closed" && "pointer-events-none"),
133
134
  style: {
134
135
  backgroundColor: "var(--color-fd-background)",
135
136
  opacity: dialogState === "closed" || dialogState === "closing" ? 0 : 1,
@@ -0,0 +1,36 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ComponentProps } from "react";
3
+
4
+ //#region src/components/nav-links.d.ts
5
+ /** 单个导航链接项的配置 */
6
+ interface NavLinkItem {
7
+ /** 链接地址 */
8
+ href: string;
9
+ /** 显示文本 */
10
+ label?: string;
11
+ /** lucide-react 图标名称,或图片文件路径(如 /icons/foo.svg) */
12
+ icon?: string;
13
+ /** 是否在新窗口打开(默认 true) */
14
+ external?: boolean;
15
+ }
16
+ interface NavLinksProps extends Omit<ComponentProps<'nav'>, 'children'> {
17
+ /** 链接列表 */
18
+ links: NavLinkItem[];
19
+ /** 容器额外 className */
20
+ className?: string;
21
+ }
22
+ /**
23
+ * 导航链接组 — 渲染顶部横条右侧的图标/文本链接
24
+ *
25
+ * 支持三种模式:
26
+ * - icon + label:图标带文字
27
+ * - 仅 icon:仅图标按钮(带 aria-label 无障碍)
28
+ * - 仅 label:纯文字链接
29
+ */
30
+ declare function NavLinks({
31
+ links,
32
+ className,
33
+ ...props
34
+ }: NavLinksProps): _$react_jsx_runtime0.JSX.Element | null;
35
+ //#endregion
36
+ export { NavLinkItem, NavLinks, NavLinksProps };
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { cn } from "./lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { DynamicIcon } from "lucide-react/dynamic.js";
5
+ //#region src/components/nav-links.tsx
6
+ /** 图片路径检测:判断 icon 值是否为图片文件路径 */
7
+ function isImagePath(value) {
8
+ return /^\//i.test(value) || /^\.\.?\//i.test(value) || /\.(svg|png|jpe?g|gif|webp)$/i.test(value);
9
+ }
10
+ /**
11
+ * 导航链接组 — 渲染顶部横条右侧的图标/文本链接
12
+ *
13
+ * 支持三种模式:
14
+ * - icon + label:图标带文字
15
+ * - 仅 icon:仅图标按钮(带 aria-label 无障碍)
16
+ * - 仅 label:纯文字链接
17
+ */
18
+ function NavLinks({ links, className = "", ...props }) {
19
+ if (links.length === 0) return null;
20
+ return /* @__PURE__ */ jsx("nav", {
21
+ className: cn("flex items-center gap-4", className),
22
+ ...props,
23
+ children: links.map((link) => /* @__PURE__ */ jsx(NavLinkItemRender, { ...link }, link.href))
24
+ });
25
+ }
26
+ function NavLinkItemRender({ href, label, icon, external = true }) {
27
+ const externalAttrs = external !== false ? {
28
+ target: "_blank",
29
+ rel: "noopener noreferrer"
30
+ } : {};
31
+ const hasIcon = !!icon;
32
+ const hasLabel = !!label;
33
+ const isImage = hasIcon && isImagePath(icon);
34
+ if (isImage && hasLabel) return /* @__PURE__ */ jsxs("a", {
35
+ href,
36
+ ...externalAttrs,
37
+ className: "inline-flex items-center gap-1.5 text-md text-fd-muted-foreground hover:text-fd-foreground transition-colors whitespace-nowrap",
38
+ children: [/* @__PURE__ */ jsx("img", {
39
+ src: icon,
40
+ alt: label,
41
+ className: "size-5"
42
+ }), label]
43
+ });
44
+ if (isImage) return /* @__PURE__ */ jsx("a", {
45
+ href,
46
+ ...externalAttrs,
47
+ className: "inline-flex items-center justify-center text-fd-muted-foreground hover:text-fd-foreground transition-colors",
48
+ "aria-label": label || icon,
49
+ children: /* @__PURE__ */ jsx("img", {
50
+ src: icon,
51
+ alt: label || icon,
52
+ className: "size-5"
53
+ })
54
+ });
55
+ if (hasIcon && hasLabel) return /* @__PURE__ */ jsxs("a", {
56
+ href,
57
+ ...externalAttrs,
58
+ className: "inline-flex items-center gap-1.5 text-md text-fd-muted-foreground hover:text-fd-foreground transition-colors whitespace-nowrap",
59
+ children: [/* @__PURE__ */ jsx(DynamicIcon, {
60
+ name: icon,
61
+ className: "size-5"
62
+ }), label]
63
+ });
64
+ if (hasIcon) return /* @__PURE__ */ jsx("a", {
65
+ href,
66
+ ...externalAttrs,
67
+ className: "inline-flex items-center justify-center text-fd-muted-foreground hover:text-fd-foreground transition-colors",
68
+ "aria-label": icon,
69
+ children: /* @__PURE__ */ jsx(DynamicIcon, {
70
+ name: icon,
71
+ className: "size-5"
72
+ })
73
+ });
74
+ return /* @__PURE__ */ jsx("a", {
75
+ href,
76
+ ...externalAttrs,
77
+ className: "text-md text-fd-muted-foreground hover:text-fd-foreground transition-colors whitespace-nowrap",
78
+ children: label
79
+ });
80
+ }
81
+ //#endregion
82
+ export { NavLinks };
@@ -1,10 +1,8 @@
1
1
  "use client";
2
+ import { cn } from "./lib/utils.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useCallback, useEffect, useRef, useState } from "react";
4
5
  //#region src/components/page-actions.tsx
5
- function cn(...classes) {
6
- return classes.filter(Boolean).join(" ");
7
- }
8
6
  function getPageText() {
9
7
  const article = document.querySelector("[data-content-area]") ?? document.querySelector("article");
10
8
  return article ? article.innerText : "";
@@ -3,6 +3,7 @@ import { ReactNode } from "react";
3
3
 
4
4
  //#region src/components/provider.d.ts
5
5
  interface ProviderProps {
6
+ /** 是否启用搜索(存在即启用,与 openmanual.json 的 search 字段语义一致) */
6
7
  searchEnabled?: boolean;
7
8
  children: ReactNode;
8
9
  }
@@ -0,0 +1,12 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/components/top-bar-search-trigger.d.ts
4
+ /**
5
+ * TopBar 中间位置的搜索触发器(Mintlify 风格)
6
+ *
7
+ * 渲染为圆角搜索输入框样式,点击后打开 SearchDialog。
8
+ * 仅在 search.position === 'header' 时使用。
9
+ */
10
+ declare function TopBarSearchTrigger(): _$react_jsx_runtime0.JSX.Element;
11
+ //#endregion
12
+ export { TopBarSearchTrigger };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Search } from "lucide-react";
4
+ import { useI18n } from "fumadocs-ui/contexts/i18n";
5
+ import { useSearchContext } from "fumadocs-ui/contexts/search";
6
+ //#region src/components/top-bar-search-trigger.tsx
7
+ /** 各语言的搜索占位符文本 */
8
+ const PLACEHOLDERS = {
9
+ zh: "搜索文档...",
10
+ en: "Search documents...",
11
+ ja: "ドキュメントを検索...",
12
+ ko: "문서 검색..."
13
+ };
14
+ /**
15
+ * TopBar 中间位置的搜索触发器(Mintlify 风格)
16
+ *
17
+ * 渲染为圆角搜索输入框样式,点击后打开 SearchDialog。
18
+ * 仅在 search.position === 'header' 时使用。
19
+ */
20
+ function TopBarSearchTrigger() {
21
+ const { setOpenSearch } = useSearchContext();
22
+ const { locale } = useI18n();
23
+ const placeholder = PLACEHOLDERS[locale ?? "zh"] ?? PLACEHOLDERS.zh;
24
+ return /* @__PURE__ */ jsxs("button", {
25
+ type: "button",
26
+ onClick: () => setOpenSearch(true),
27
+ className: "\n flex items-center gap-2 w-full max-w-md h-9 px-3\n rounded-lg border border-fd-border bg-fd-muted/50\n text-sm text-fd-muted-foreground\n cursor-pointer transition-colors\n hover:bg-fd-muted hover:border-fd-inputborder\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-primary\n ",
28
+ "aria-label": placeholder,
29
+ children: [
30
+ /* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0" }),
31
+ /* @__PURE__ */ jsx("span", {
32
+ className: "flex-1 text-left truncate",
33
+ children: placeholder
34
+ }),
35
+ /* @__PURE__ */ jsx("kbd", {
36
+ className: "\n inline-flex items-center gap-0.5 h-5 px-1.5 rounded\n border border-fd-border bg-fd-background text-[11px] text-fd-muted-foreground\n font-mono select-none pointer-events-none\n ",
37
+ children: "⌘K"
38
+ })
39
+ ]
40
+ });
41
+ }
42
+ //#endregion
43
+ export { TopBarSearchTrigger };
@@ -0,0 +1,31 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/top-bar.d.ts
5
+ interface TopBarProps {
6
+ /** 高度值,如 '64px' */
7
+ height: string;
8
+ /** 左侧内容(Logo 等) */
9
+ left?: ReactNode;
10
+ /** 中间内容(搜索框等) */
11
+ center?: ReactNode;
12
+ /** 右侧内容(链接等) */
13
+ right?: ReactNode;
14
+ /** 是否 sticky */
15
+ sticky?: boolean;
16
+ /** 背景色 */
17
+ background?: string;
18
+ /** 是否显示底边框 */
19
+ bordered?: boolean;
20
+ }
21
+ declare function TopBar({
22
+ height,
23
+ left,
24
+ center,
25
+ right,
26
+ sticky,
27
+ background,
28
+ bordered
29
+ }: TopBarProps): _$react_jsx_runtime0.JSX.Element;
30
+ //#endregion
31
+ export { TopBar, TopBarProps };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { cn } from "./lib/utils.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/top-bar.tsx
5
+ function TopBar({ height, left, center, right, sticky = true, background, bordered = true }) {
6
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: `:root { --fd-banner-height: ${height}; }` }), /* @__PURE__ */ jsx("div", {
7
+ id: "om-topbar",
8
+ style: {
9
+ height,
10
+ ...sticky ? {
11
+ position: "sticky",
12
+ top: 0,
13
+ zIndex: 40
14
+ } : {},
15
+ ...background ? { background } : {}
16
+ },
17
+ className: cn("w-full shrink-0 bg-fd-background text-fd-foreground", bordered && "border-b border-fd-border"),
18
+ children: /* @__PURE__ */ jsxs("div", {
19
+ className: "flex items-center justify-between mx-auto max-w-[var(--fd-layout-width,97rem)] px-4 h-full",
20
+ children: [
21
+ /* @__PURE__ */ jsx("div", {
22
+ className: "flex items-center gap-3 min-w-0",
23
+ children: left
24
+ }),
25
+ /* @__PURE__ */ jsx("div", {
26
+ className: "flex items-center justify-center flex-1 min-w-0 px-4 max-md:hidden",
27
+ children: center
28
+ }),
29
+ /* @__PURE__ */ jsx("div", {
30
+ className: "flex items-center gap-4 min-w-0",
31
+ children: right
32
+ })
33
+ ]
34
+ })
35
+ })] });
36
+ }
37
+ //#endregion
38
+ export { TopBar };
package/dist/index.d.ts CHANGED
@@ -24,6 +24,22 @@ declare const OpenManualConfigSchema: z.ZodObject<{
24
24
  href: z.ZodString;
25
25
  }, z.core.$strip>>>;
26
26
  }, z.core.$strip>>;
27
+ header: z.ZodOptional<z.ZodObject<{
28
+ height: z.ZodOptional<z.ZodString>;
29
+ logo: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
30
+ light: z.ZodString;
31
+ dark: z.ZodString;
32
+ }, z.core.$strip>]>>;
33
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
34
+ label: z.ZodOptional<z.ZodString>;
35
+ icon: z.ZodOptional<z.ZodString>;
36
+ href: z.ZodString;
37
+ external: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
38
+ }, z.core.$strip>>>;
39
+ sticky: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
+ background: z.ZodOptional<z.ZodString>;
41
+ bordered: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
42
+ }, z.core.$strip>>;
27
43
  footer: z.ZodOptional<z.ZodObject<{
28
44
  text: z.ZodOptional<z.ZodString>;
29
45
  }, z.core.$strip>>;
@@ -42,7 +58,10 @@ declare const OpenManualConfigSchema: z.ZodObject<{
42
58
  darkMode: z.ZodOptional<z.ZodBoolean>;
43
59
  }, z.core.$strip>>;
44
60
  search: z.ZodOptional<z.ZodObject<{
45
- enabled: z.ZodOptional<z.ZodBoolean>;
61
+ position: z.ZodOptional<z.ZodEnum<{
62
+ sidebar: "sidebar";
63
+ header: "header";
64
+ }>>;
46
65
  }, z.core.$strip>>;
47
66
  mdx: z.ZodOptional<z.ZodObject<{
48
67
  latex: z.ZodOptional<z.ZodBoolean>;
@@ -62,6 +81,20 @@ declare const OpenManualConfigSchema: z.ZodObject<{
62
81
  dir: "dir";
63
82
  }>>;
64
83
  }, z.core.$strip>>;
84
+ openapi: z.ZodOptional<z.ZodObject<{
85
+ specPath: z.ZodOptional<z.ZodString>;
86
+ specs: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
87
+ path: z.ZodString;
88
+ group: z.ZodOptional<z.ZodString>;
89
+ }, z.core.$strip>>]>>;
90
+ label: z.ZodOptional<z.ZodString>;
91
+ groupBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
92
+ tag: "tag";
93
+ route: "route";
94
+ none: "none";
95
+ }>>>;
96
+ separateTab: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
97
+ }, z.core.$strip>>;
65
98
  }, z.core.$strip>;
66
99
  type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;
67
100
  //#endregion
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ const ThemeSchema = z.object({
32
32
  primaryHue: z.number().min(0).max(360).optional(),
33
33
  darkMode: z.boolean().optional()
34
34
  });
35
- const SearchSchema = z.object({ enabled: z.boolean().optional() });
35
+ const SearchSchema = z.object({ position: z.enum(["sidebar", "header"]).optional() });
36
36
  const MdxSchema = z.object({ latex: z.boolean().optional() });
37
37
  const PageActionsSchema = z.object({ enabled: z.boolean().optional() });
38
38
  const I18nLocaleSchema = z.object({
@@ -45,6 +45,40 @@ const I18nConfigSchema = z.object({
45
45
  languages: z.array(I18nLocaleSchema).optional(),
46
46
  parser: z.enum(["dot", "dir"]).optional()
47
47
  });
48
+ const OpenApiSpecSchema = z.object({
49
+ path: z.string(),
50
+ group: z.string().optional()
51
+ });
52
+ const OpenApiSchema = z.object({
53
+ specPath: z.string().optional(),
54
+ specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),
55
+ label: z.string().optional(),
56
+ groupBy: z.enum([
57
+ "tag",
58
+ "route",
59
+ "none"
60
+ ]).optional().default("tag"),
61
+ separateTab: z.boolean().optional().default(false)
62
+ });
63
+ /** 顶部横条链接项 */
64
+ const TopBarLinkSchema = z.object({
65
+ label: z.string().optional(),
66
+ icon: z.string().optional(),
67
+ href: z.string(),
68
+ external: z.boolean().optional().default(true)
69
+ }).refine((data) => data.label || data.icon, {
70
+ message: "至少需要提供 label 或 icon 中的一个",
71
+ path: ["label"]
72
+ });
73
+ /** 顶部横条配置 */
74
+ const TopBarSchema = z.object({
75
+ height: z.string().optional(),
76
+ logo: LogoSchema.optional(),
77
+ links: z.array(TopBarLinkSchema).optional(),
78
+ sticky: z.boolean().optional().default(true),
79
+ background: z.string().optional(),
80
+ bordered: z.boolean().optional().default(true)
81
+ });
48
82
  const OpenManualConfigSchema = z.object({
49
83
  name: z.string().min(1),
50
84
  description: z.string().optional(),
@@ -55,13 +89,15 @@ const OpenManualConfigSchema = z.object({
55
89
  contentPolicy: z.enum(["strict", "all"]).optional(),
56
90
  favicon: FaviconSchema.optional(),
57
91
  navbar: NavbarSchema.optional(),
92
+ header: TopBarSchema.optional(),
58
93
  footer: FooterSchema.optional(),
59
94
  sidebar: z.array(SidebarGroupSchema).optional(),
60
95
  theme: ThemeSchema.optional(),
61
96
  search: SearchSchema.optional(),
62
97
  mdx: MdxSchema.optional(),
63
98
  pageActions: PageActionsSchema.optional(),
64
- i18n: I18nConfigSchema.optional()
99
+ i18n: I18nConfigSchema.optional(),
100
+ openapi: OpenApiSchema.optional()
65
101
  });
66
102
 
67
103
  //#endregion
@@ -76,7 +112,6 @@ const DEFAULT_CONFIG = {
76
112
  primaryHue: 213,
77
113
  darkMode: true
78
114
  },
79
- search: { enabled: true },
80
115
  mdx: {},
81
116
  pageActions: { enabled: true }
82
117
  };
@@ -122,10 +157,7 @@ function mergeDefaults(config) {
122
157
  ...DEFAULT_CONFIG.theme,
123
158
  ...config.theme
124
159
  },
125
- search: {
126
- ...DEFAULT_CONFIG.search,
127
- ...config.search
128
- },
160
+ search: config.search ? { position: config.search.position ?? "sidebar" } : void 0,
129
161
  mdx: {
130
162
  ...DEFAULT_CONFIG.mdx,
131
163
  ...config.mdx
@@ -139,6 +171,13 @@ function mergeDefaults(config) {
139
171
  defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? "zh",
140
172
  languages: config.i18n.languages ?? [],
141
173
  parser: config.i18n.parser ?? "dot"
174
+ } : void 0,
175
+ openapi: config.openapi ? {
176
+ specPath: config.openapi.specPath,
177
+ specs: config.openapi.specs,
178
+ label: config.openapi.label ?? "接口文档",
179
+ groupBy: config.openapi.groupBy ?? "tag",
180
+ separateTab: config.openapi.separateTab ?? false
142
181
  } : void 0
143
182
  };
144
183
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const FaviconSchema = z.string();\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n favicon: FaviconSchema.optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n i18n: I18nConfigSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const FaviconSchema = z.string();\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n /**\n * 搜索入口位置\n * - 'sidebar': 放在侧边栏顶部(Fumadocs 风格)\n * - 'header': 放在 header 中间(Mintlify 风格)\n *\n * 默认值: 'sidebar'\n * 不配置 search 字段则不启用搜索(与 header 的「配置即启用」语义一致)\n */\n position: z.enum(['sidebar', 'header']).optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).optional(),\n});\n\nexport const OpenApiSpecSchema = z.object({\n /** OpenAPI 规范文件路径,相对于项目根目录 */\n path: z.string(),\n /** 分组标签(用于在侧边栏中显示的分组名) */\n group: z.string().optional(),\n});\n\nexport const OpenApiSchema = z.object({\n /**\n * OpenAPI 规范文件路径(单个文件),相对于项目根目录。支持 .json / .yaml / .yml\n * @deprecated 推荐使用 specs 字段\n */\n specPath: z.string().optional(),\n /**\n * OpenAPI 规范文件配置,支持字符串(单文件)或对象数组(多文件)\n * - string: 单个 spec 文件路径\n * - array: 多个 spec 文件配置,每个可指定 path 和 group\n */\n specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),\n /** 侧边栏 Tab 显示名称,默认 \"接口文档\"(仅在 separateTab: true 时生效) */\n label: z.string().optional(),\n /**\n * API 端点在侧边栏中的分组策略\n * - 'tag': 按 OpenAPI 规范中的 tags 分组(默认)\n * - 'route': 按路由路径分组\n * - 'none': 不分组,平铺展示\n */\n groupBy: z.enum(['tag', 'route', 'none']).optional().default('tag'),\n /**\n * 是否将 API 文档作为独立的侧边栏 Tab 展示\n * - true: 保持旧行为,API 文档在独立 Tab 中(向后兼容)\n * - false: 将 API 端点混合到文档导航树中(类似 Mintlify 风格) // cspell:ignore Mintlify\n */\n separateTab: z.boolean().optional().default(false),\n});\n\n/** 顶部横条链接项 */\nexport const TopBarLinkSchema = z\n .object({\n /** 链接显示文本(与 icon 至少填一个) */\n label: z.string().optional(),\n /** lucide-react 图标名称(如 \"Github\", \"Twitter\",与 label 至少填一个) */\n icon: z.string().optional(),\n href: z.string(),\n external: z.boolean().optional().default(true),\n })\n .refine((data) => data.label || data.icon, {\n message: '至少需要提供 label 或 icon 中的一个',\n path: ['label'],\n });\n\n/** 顶部横条配置 */\nexport const TopBarSchema = z.object({\n /** 高度,默认 '64px' */\n height: z.string().optional(),\n /** Logo 配置(独立于 navbar.logo) */\n logo: LogoSchema.optional(),\n /** 右侧导航链接 */\n links: z.array(TopBarLinkSchema).optional(),\n /** 是否显示粘性(sticky),默认 true */\n sticky: z.boolean().optional().default(true),\n /** 背景色(CSS 值) */\n background: z.string().optional(),\n /** 底部边框 */\n bordered: z.boolean().optional().default(true),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n favicon: FaviconSchema.optional(),\n navbar: NavbarSchema.optional(),\n header: TopBarSchema.optional(),\n footer: FooterSchema.optional(),\n // @deprecated Use meta.json files in content directories instead\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n i18n: I18nConfigSchema.optional(),\n openapi: OpenApiSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\nexport type OpenApiConfig = z.infer<typeof OpenApiSchema>;\nexport type TopBarConfig = z.infer<typeof TopBarSchema>;\nexport type TopBarLink = z.infer<typeof TopBarLinkSchema>;\n\n// @deprecated Use collectSlugsFromMeta from meta-scanner instead\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n console.warn(\n '[openmanual] The \"sidebar\" field in openmanual.json is deprecated. ' +\n 'Please use meta.json files in your content directories instead.'\n );\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\n}\n\nexport function isOpenApiEnabled(config: OpenManualConfig): boolean {\n if (config.openapi === undefined) return false;\n // 检查 specs(新格式)或 specPath(旧格式,向后兼容)\n const hasSpecs = config.openapi.specs !== undefined;\n const hasSpecPath = typeof config.openapi?.specPath === 'string';\n return hasSpecs || hasSpecPath;\n}\n\n/**\n * 从 OpenAPI 配置中解析出所有 spec 文件路径\n * 兼容 specs(新)和 specPath(旧)两种格式\n */\nexport function resolveOpenApiSpecPaths(config: OpenManualConfig): string[] {\n const openApiCfg = config.openapi;\n if (!openApiCfg) return [];\n\n // 新格式:specs 数组或字符串\n if (openApiCfg.specs !== undefined) {\n if (typeof openApiCfg.specs === 'string') {\n return [openApiCfg.specs];\n }\n return openApiCfg.specs.map((s) => s.path);\n }\n\n // 旧格式:specPath 字符串\n if (typeof openApiCfg.specPath === 'string') {\n return [openApiCfg.specPath];\n }\n\n return [];\n}\n\n/**\n * 判断是否使用独立 Tab 模式(旧行为)\n */\nexport function isSeparateTabMode(config: OpenManualConfig): boolean {\n return config.openapi?.separateTab === true;\n}\n\n/**\n * 判断是否启用了顶部横条(配置了 header 即启用)\n */\nexport function isHeaderEnabled(config: OpenManualConfig): boolean {\n return config.header !== undefined;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n // 「配置即启用」:用户不配 search 字段则保持 undefined(不启用搜索)\n search: config.search ? { position: config.search.position ?? 'sidebar' } : undefined,\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\n openapi: config.openapi\n ? {\n specPath: config.openapi.specPath,\n specs: config.openapi.specs,\n label: config.openapi.label ?? '接口文档',\n groupBy: config.openapi.groupBy ?? 'tag',\n separateTab: config.openapi.separateTab ?? false,\n }\n : undefined,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmanual",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "author": "shenjingnan <sjn.code@gmail.com>",
5
5
  "description": "AI 友好的开源文档系统框架",
6
6
  "type": "module",
@@ -53,6 +53,7 @@
53
53
  "dependencies": {
54
54
  "@panzoom/panzoom": "^4.6.2",
55
55
  "chokidar": "^5.0.0",
56
+ "clsx": "^2.1.1",
56
57
  "commander": "^14.0.3",
57
58
  "fast-glob": "^3.3.3",
58
59
  "fumadocs-core": "^16.7.7",
@@ -1,20 +0,0 @@
1
- import * as PageTree from "fumadocs-core/page-tree";
2
-
3
- //#region src/utils/restructure-tree.d.ts
4
- interface SidebarConfigEntry {
5
- group: string;
6
- icon?: string;
7
- collapsed?: boolean;
8
- pages: readonly {
9
- slug: string;
10
- icon?: string;
11
- }[];
12
- }
13
- interface RestructureOptions {
14
- /** 是否保留树节点原始名称(不覆盖为 sidebar 配置值)。i18n 模式下应设为 true */
15
- preserveNames?: boolean;
16
- }
17
- declare function slugToUrl(slug: string): string;
18
- declare function restructureTree(tree: PageTree.Root, sidebarConfig: readonly SidebarConfigEntry[], iconMap?: Record<string, React.ReactNode>, options?: RestructureOptions): PageTree.Root;
19
- //#endregion
20
- export { RestructureOptions, SidebarConfigEntry, restructureTree, slugToUrl };
@@ -1,70 +0,0 @@
1
- //#region src/utils/restructure-tree.ts
2
- function slugToUrl(slug) {
3
- return slug === "index" ? "/" : `/${slug}`;
4
- }
5
- function restructureTree(tree, sidebarConfig, iconMap, options) {
6
- const consumed = /* @__PURE__ */ new Set();
7
- const newChildren = [];
8
- const children = tree.children ?? [];
9
- for (const group of sidebarConfig) if (group.pages.every((p) => !p.slug.includes("/"))) {
10
- const folderChildren = [];
11
- for (const page of group.pages) {
12
- const url = slugToUrl(page.slug);
13
- const idx = children.findIndex((c, i) => !consumed.has(i) && c.type === "page" && c.url === url);
14
- if (idx >= 0) {
15
- const node = children[idx];
16
- if (node) folderChildren.push(page.icon && iconMap?.[page.icon] ? {
17
- ...node,
18
- icon: iconMap[page.icon]
19
- } : node);
20
- consumed.add(idx);
21
- }
22
- }
23
- if (folderChildren.length > 0) newChildren.push({
24
- type: "folder",
25
- name: group.group,
26
- icon: group.icon && iconMap ? iconMap[group.icon] : void 0,
27
- defaultOpen: !group.collapsed,
28
- children: folderChildren
29
- });
30
- } else {
31
- const dirPrefix = group.pages.find((p) => p.slug.includes("/"))?.slug.split("/")[0];
32
- if (dirPrefix) {
33
- const idx = children.findIndex((child, i) => !consumed.has(i) && child.type === "folder" && child.children?.some((c) => c.type === "page" && c.url?.startsWith(`/${dirPrefix}/`)));
34
- if (idx >= 0) {
35
- consumed.add(idx);
36
- const originalFolder = children[idx];
37
- const childrenWithIcons = (originalFolder.children ?? []).map((child) => {
38
- if (child.type === "page") {
39
- const matchedPage = group.pages.find((p) => {
40
- const url = slugToUrl(p.slug);
41
- return child.url === url;
42
- });
43
- if (matchedPage?.icon && iconMap?.[matchedPage.icon]) return {
44
- ...child,
45
- icon: iconMap[matchedPage.icon]
46
- };
47
- }
48
- return child;
49
- });
50
- newChildren.push({
51
- ...originalFolder,
52
- ...options?.preserveNames ? {} : { name: group.group },
53
- icon: group.icon && iconMap ? iconMap[group.icon] : void 0,
54
- defaultOpen: !group.collapsed,
55
- children: childrenWithIcons
56
- });
57
- }
58
- }
59
- }
60
- for (let i = 0; i < children.length; i++) if (!consumed.has(i)) {
61
- const node = children[i];
62
- if (node) newChildren.push(node);
63
- }
64
- return {
65
- ...tree,
66
- children: newChildren
67
- };
68
- }
69
- //#endregion
70
- export { restructureTree, slugToUrl };