openmanual 0.8.2 → 0.10.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 CHANGED
@@ -13,6 +13,7 @@ const LogoSchema = z.union([z.string(), z.object({
13
13
  light: z.string(),
14
14
  dark: z.string()
15
15
  })]);
16
+ const FaviconSchema = z.string();
16
17
  const NavbarSchema = z.object({
17
18
  logo: LogoSchema.optional(),
18
19
  github: z.url().optional(),
@@ -48,6 +49,7 @@ const OpenManualConfigSchema = z.object({
48
49
  siteUrl: z.url().optional(),
49
50
  locale: z.string().optional(),
50
51
  contentPolicy: z.enum(["strict", "all"]).optional(),
52
+ favicon: FaviconSchema.optional(),
51
53
  navbar: NavbarSchema.optional(),
52
54
  footer: FooterSchema.optional(),
53
55
  sidebar: z.array(SidebarGroupSchema).optional(),
@@ -140,12 +142,21 @@ function mergeDefaults(config) {
140
142
  };
141
143
  }
142
144
 
145
+ //#endregion
146
+ //#region src/core/generator/callout-component.ts
147
+ function generateCalloutComponent() {
148
+ return `'use client';
149
+ export { Callout, CalloutTitle, CalloutDescription } from 'openmanual/components/callout';
150
+ `;
151
+ }
152
+
143
153
  //#endregion
144
154
  //#region src/core/generator/global-css.ts
145
155
  function generateGlobalCss(ctx) {
146
156
  const { config } = ctx;
147
157
  const primaryHue = config.theme?.primaryHue ?? 213;
148
158
  return `@import 'tailwindcss';
159
+ @source './node_modules/openmanual/dist/components/**/*.js';
149
160
  @import 'fumadocs-ui/style.css';
150
161
  @custom-variant dark (&:is(.dark, .dark *));
151
162
 
@@ -158,6 +169,29 @@ function generateGlobalCss(ctx) {
158
169
  --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */
159
170
  --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */
160
171
  --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */
172
+
173
+ /* Callout 类型色 */
174
+ --callout-info-bg: hsl(210, 35%, 94%);
175
+ --callout-info-border: hsl(212, 40%, 80%);
176
+ --callout-info-text: hsl(213, 45%, 35%);
177
+ --callout-warning-bg: hsl(38, 60%, 93%);
178
+ --callout-warning-border: hsl(36, 55%, 78%);
179
+ --callout-warning-text: hsl(28, 55%, 35%);
180
+ --callout-danger-bg: hsl(0, 50%, 94%);
181
+ --callout-danger-border: hsl(0, 45%, 82%);
182
+ --callout-danger-text: hsl(0, 50%, 38%);
183
+ --callout-check-bg: hsl(150, 35%, 93%);
184
+ --callout-check-border: hsl(152, 35%, 78%);
185
+ --callout-check-text: hsl(155, 40%, 32%);
186
+ --callout-tip-bg: hsl(150, 35%, 93%);
187
+ --callout-tip-border: hsl(152, 35%, 78%);
188
+ --callout-tip-text: hsl(155, 40%, 32%);
189
+ --callout-note-bg: hsl(215, 20%, 94%);
190
+ --callout-note-border: hsl(215, 22%, 82%);
191
+ --callout-note-text: hsl(215, 25%, 40%);
192
+ --callout-key-bg: hsl(30, 55%, 93%);
193
+ --callout-key-border: hsl(28, 50%, 78%);
194
+ --callout-key-text: hsl(25, 50%, 35%);
161
195
  }
162
196
  ${config.theme?.darkMode ?? true ? `
163
197
  .dark {
@@ -181,6 +215,29 @@ ${config.theme?.darkMode ?? true ? `
181
215
  --color-fd-accent-foreground: hsl(35, 12%, 88%);
182
216
  --color-fd-ring: hsl(30, 30%, 50%);
183
217
  --color-fd-overlay: hsla(25, 20%, 5%, 0.5);
218
+
219
+ /* Callout 类型色 */
220
+ --callout-info-bg: hsl(213, 25%, 16%);
221
+ --callout-info-border: hsl(213, 30%, 30%);
222
+ --callout-info-text: hsl(213, 40%, 72%);
223
+ --callout-warning-bg: hsl(32, 35%, 17%);
224
+ --callout-warning-border: hsl(30, 35%, 30%);
225
+ --callout-warning-text: hsl(35, 45%, 72%);
226
+ --callout-danger-bg: hsl(5, 25%, 17%);
227
+ --callout-danger-border: hsl(5, 30%, 30%);
228
+ --callout-danger-text: hsl(5, 40%, 72%);
229
+ --callout-check-bg: hsl(155, 22%, 16%);
230
+ --callout-check-border: hsl(155, 25%, 28%);
231
+ --callout-check-text: hsl(155, 35%, 68%);
232
+ --callout-tip-bg: hsl(155, 22%, 16%);
233
+ --callout-tip-border: hsl(155, 25%, 28%);
234
+ --callout-tip-text: hsl(155, 35%, 68%);
235
+ --callout-note-bg: hsl(215, 15%, 16%);
236
+ --callout-note-border: hsl(215, 18%, 28%);
237
+ --callout-note-text: hsl(215, 25%, 68%);
238
+ --callout-key-bg: hsl(30, 28%, 17%);
239
+ --callout-key-border: hsl(28, 25%, 30%);
240
+ --callout-key-text: hsl(25, 35%, 68%);
184
241
  }
185
242
 
186
243
  .dark body {
@@ -196,6 +253,21 @@ figure.shiki {
196
253
  figure.shiki > div {
197
254
  max-height: none;
198
255
  }
256
+
257
+ /* Mermaid 全屏操作栏按钮 hover */
258
+ .mermaid-toolbar-btn:hover {
259
+ background-color: var(--hover-bg) !important;
260
+ color: var(--hover-color) !important;
261
+ }
262
+
263
+ .mermaid-toolbar-btn:hover svg {
264
+ color: inherit;
265
+ }
266
+
267
+ /* Callout:去除 shadow */
268
+ [style*="--callout-color"] {
269
+ box-shadow: none;
270
+ }
199
271
  `;
200
272
  }
201
273
 
@@ -292,7 +364,7 @@ export default withMDX(config);
292
364
  //#endregion
293
365
  //#region src/core/generator/package-json.ts
294
366
  function getOpenManualVersion() {
295
- return "0.8.2";
367
+ return "0.10.0";
296
368
  }
297
369
  function generatePackageJson(ctx) {
298
370
  const openmanualVersion = getOpenManualVersion();
@@ -313,6 +385,7 @@ function generatePackageJson(ctx) {
313
385
  "fumadocs-core": "^16.7.7",
314
386
  "fumadocs-mdx": "^14.2.11",
315
387
  "fumadocs-ui": "^16.7.7",
388
+ "lucide-react": "^1.7.0",
316
389
  mermaid: "^11.4.0",
317
390
  next: "^16.2.1",
318
391
  "next-themes": "^0.4.6",
@@ -353,7 +426,8 @@ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
353
426
  import { Files, File, Folder } from 'fumadocs-ui/components/files';
354
427
  import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
355
428
  import { TypeTable } from 'fumadocs-ui/components/type-table';
356
- import { Mermaid } from '@/components/mermaid';${pageActionsEnabled ? "\nimport { PageActions } from '@/components/page-actions';" : ""}
429
+ import { Mermaid } from '@/components/mermaid';
430
+ import { Callout, CalloutTitle, CalloutDescription } from '@/components/callout';${pageActionsEnabled ? "\nimport { PageActions } from '@/components/page-actions';" : ""}
357
431
  ${allowedSlugsSnippet}
358
432
  export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
359
433
  const { slug } = await params;
@@ -384,7 +458,7 @@ ${pageActionsEnabled ? ` <div className="flex items-start justify-between g
384
458
  <DocsDescription>{page.data.description}</DocsDescription>
385
459
  )}`}
386
460
  <DocsBody data-content-area>
387
- <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />
461
+ <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid, Callout, CalloutTitle, CalloutDescription }} />
388
462
  </DocsBody>
389
463
  </DocsPage>
390
464
  );
@@ -438,7 +512,13 @@ import { Provider } from 'openmanual/components/provider';
438
512
  import type { ReactNode } from 'react';
439
513
 
440
514
  export function AppProvider({ children }: { children: ReactNode }) {
441
- return <Provider searchEnabled={${ctx.config.search?.enabled !== false}}>{children}</Provider>;
515
+ return (
516
+ <Provider
517
+ searchEnabled={${ctx.config.search?.enabled !== false}}
518
+ >
519
+ {children}
520
+ </Provider>
521
+ );
442
522
  }
443
523
  `;
444
524
  }
@@ -472,6 +552,17 @@ export async function GET(
472
552
  `;
473
553
  }
474
554
 
555
+ //#endregion
556
+ //#region src/core/generator/search-route.ts
557
+ function generateSearchRoute() {
558
+ return `import { source } from '@/lib/source';
559
+ import { createFromSource } from 'fumadocs-core/search/server';
560
+
561
+ export const revalidate = false;
562
+ export const { staticGET: GET } = createFromSource(source);
563
+ `;
564
+ }
565
+
475
566
  //#endregion
476
567
  //#region src/core/generator/source-config.ts
477
568
  function generateSourceConfig(_ctx) {
@@ -613,6 +704,10 @@ async function generateAll(ctx) {
613
704
  path: "lib/layout.tsx",
614
705
  content: generateLayout(ctx)
615
706
  },
707
+ {
708
+ path: "components/callout.tsx",
709
+ content: generateCalloutComponent()
710
+ },
616
711
  {
617
712
  path: "components/mermaid.tsx",
618
713
  content: generateMermaidComponent()
@@ -624,10 +719,16 @@ async function generateAll(ctx) {
624
719
  ...ctx.dev ? [{
625
720
  path: "app/api/raw/[...path]/route.ts",
626
721
  content: generateRawContentRoute()
627
- }] : [],
722
+ }, {
723
+ path: "app/api/search/route.ts",
724
+ content: generateSearchRoute()
725
+ }] : [{
726
+ path: "app/api/search/route.ts",
727
+ content: generateSearchRoute()
728
+ }],
628
729
  {
629
730
  path: "app/layout.tsx",
630
- content: generateRootLayout()
731
+ content: generateRootLayout(ctx)
631
732
  },
632
733
  {
633
734
  path: "app/provider.tsx",
@@ -656,11 +757,21 @@ async function generateAll(ctx) {
656
757
  }
657
758
  await generateMetaFiles(ctx);
658
759
  }
659
- function generateRootLayout() {
760
+ function generateRootLayout(ctx) {
761
+ const { config } = ctx;
762
+ const favicon = config.favicon;
660
763
  return `import { AppLayout } from 'openmanual/components/app-layout';
661
764
  import { AppProvider } from './provider';
662
765
  import type { ReactNode } from 'react';
663
- import '../global.css';
766
+ ${favicon ? `import type { Metadata } from 'next';
767
+
768
+ export const metadata: Metadata = {
769
+ icons: {
770
+ icon: '${favicon}',
771
+ },
772
+ };
773
+
774
+ ` : ""}import '../global.css';
664
775
 
665
776
  export default function RootLayout({ children }: { children: ReactNode }) {
666
777
  return (
@@ -686,20 +797,34 @@ function generateDocsLayout(ctx) {
686
797
  const footerLine = footerText ? `\n footer: { children: '${footerText.replace(/'/g, "\\'")}' },` : "";
687
798
  const sidebar = config.sidebar;
688
799
  const hasSidebar = sidebar && sidebar.length > 0;
800
+ const iconNames = /* @__PURE__ */ new Set();
801
+ if (hasSidebar) for (const g of sidebar ?? []) {
802
+ if (g.icon) iconNames.add(g.icon);
803
+ for (const p of g.pages) if (p.icon) iconNames.add(p.icon);
804
+ }
805
+ const hasIcons = iconNames.size > 0;
806
+ const iconNameList = [...iconNames];
689
807
  const sidebarConfigSnippet = hasSidebar ? `\nconst sidebarConfig = ${JSON.stringify((sidebar ?? []).map((g) => ({
690
808
  group: g.group,
809
+ icon: g.icon,
691
810
  collapsed: g.collapsed,
692
- pages: g.pages.map((p) => ({ slug: p.slug }))
811
+ pages: g.pages.map((p) => ({
812
+ slug: p.slug,
813
+ icon: p.icon
814
+ }))
693
815
  })), null, 2)} as const;
816
+ ` : "";
817
+ const lucideImportLine = hasIcons ? `\nimport { ${iconNameList.join(", ")} } from 'lucide-react';` : "";
818
+ const iconMapSnippet = hasIcons ? `\nconst iconMap = {${iconNameList.map((name) => `\n ${name}: <${name} />,`).join("")}\n} as const;
694
819
  ` : "";
695
820
  return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
696
821
  import { baseOptions } from '@/lib/layout';
697
822
  import { source } from '@/lib/source';
698
- import type { ReactNode } from 'react';${hasSidebar ? "\nimport { restructureTree } from 'openmanual/utils/restructure-tree';" : ""}
699
- ${sidebarConfigSnippet}
823
+ import type { ReactNode } from 'react';${hasSidebar ? "\nimport { restructureTree } from 'openmanual/utils/restructure-tree';" : ""}${lucideImportLine}
824
+ ${sidebarConfigSnippet}${iconMapSnippet}
700
825
  const docsOptions = {
701
826
  ...baseOptions(),
702
- ${hasSidebar ? "tree: restructureTree(source.getPageTree(), sidebarConfig)," : "tree: source.getPageTree(),"}${githubLine}${linksLine}${footerLine}
827
+ ${hasSidebar ? hasIcons ? "tree: restructureTree(source.getPageTree(), sidebarConfig, iconMap)," : "tree: restructureTree(source.getPageTree(), sidebarConfig)," : "tree: source.getPageTree(),"}${githubLine}${linksLine}${footerLine}
703
828
  };
704
829
 
705
830
  export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
@@ -986,14 +1111,27 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
986
1111
  const tempDir = await ensureTempDir(cwd);
987
1112
  const appDir = getAppDir(cwd);
988
1113
  const contentDir = resolve(cwd, config.contentDir ?? "content");
989
- await generateAll({
1114
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1115
+ const openmanualRoot = process.env.OPENMANUAL_ROOT || resolve(__dirname, "..");
1116
+ const ctx = {
990
1117
  config,
991
1118
  projectDir: cwd,
992
1119
  appDir,
993
1120
  contentDir: config.contentDir ?? "content",
994
1121
  dev: true,
995
- ...process.env.OPENMANUAL_ROOT ? { openmanualRoot: process.env.OPENMANUAL_ROOT } : {}
996
- });
1122
+ openmanualRoot
1123
+ };
1124
+ if (process.env.OPENMANUAL_ROOT) await spawnInitialGenerate(openmanualRoot, cwd);
1125
+ else {
1126
+ await generateAll(ctx);
1127
+ await createSymlink(contentDir, resolve(appDir, "content"));
1128
+ const publicDir = resolve(cwd, "public");
1129
+ try {
1130
+ const { stat } = await import("node:fs/promises");
1131
+ await stat(publicDir);
1132
+ await createSymlink(publicDir, resolve(appDir, "public"));
1133
+ } catch {}
1134
+ }
997
1135
  try {
998
1136
  const unknownLangs = await checkCodeLangs(contentDir);
999
1137
  if (unknownLangs.length > 0) {
@@ -1002,13 +1140,6 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
1002
1140
  logger.warn("建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值");
1003
1141
  }
1004
1142
  } catch {}
1005
- await createSymlink(contentDir, resolve(appDir, "content"));
1006
- const publicDir = resolve(cwd, "public");
1007
- try {
1008
- const { stat } = await import("node:fs/promises");
1009
- await stat(publicDir);
1010
- await createSymlink(publicDir, resolve(appDir, "public"));
1011
- } catch {}
1012
1143
  logger.step("安装依赖...");
1013
1144
  await installDeps(appDir);
1014
1145
  logger.success("开发服务器启动中...");
@@ -1080,6 +1211,24 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
1080
1211
  process.exit(1);
1081
1212
  }
1082
1213
  });
1214
+ function spawnInitialGenerate(openmanualRoot, cwd) {
1215
+ const child = spawn("node", [
1216
+ resolve(openmanualRoot, "dist/bin.js"),
1217
+ "_regenerate",
1218
+ "--cwd",
1219
+ cwd
1220
+ ], {
1221
+ stdio: "inherit",
1222
+ env: { ...process.env }
1223
+ });
1224
+ return new Promise((promiseResolve, promiseReject) => {
1225
+ child.on("exit", (code) => {
1226
+ if (code === 0) promiseResolve();
1227
+ else promiseReject(/* @__PURE__ */ new Error(`初始生成失败 (exit code: ${code})`));
1228
+ });
1229
+ child.on("error", promiseReject);
1230
+ });
1231
+ }
1083
1232
  function spawnRegenerate(openmanualRoot, cwd, nextChild) {
1084
1233
  if (nextChild.exitCode !== null) {
1085
1234
  logger.warn("Next.js 进程已退出,跳过重新生成");
@@ -1192,7 +1341,7 @@ const regenerateCommand = new Command("_regenerate").description("内部命令
1192
1341
  //#endregion
1193
1342
  //#region src/cli/bin.ts
1194
1343
  function getVersion() {
1195
- return "0.8.2";
1344
+ return "0.10.0";
1196
1345
  }
1197
1346
  const program = new Command();
1198
1347
  const commandName = basename(process.argv[1] ?? "openmanual");