react-docs-module 0.1.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/README.md +279 -0
- package/ai-chat.tsx +222 -0
- package/chat-api.ts +90 -0
- package/cn.ts +15 -0
- package/config.ts +29 -0
- package/dist/ai-chat.d.ts +12 -0
- package/dist/ai-chat.js +72 -0
- package/dist/ai-chat.js.map +1 -0
- package/dist/chat-api.d.ts +16 -0
- package/dist/chat-api.js +62 -0
- package/dist/chat-api.js.map +1 -0
- package/dist/cn.d.ts +4 -0
- package/dist/cn.js +14 -0
- package/dist/cn.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -0
- package/dist/doc-pagination.d.ts +13 -0
- package/dist/doc-pagination.js +8 -0
- package/dist/doc-pagination.js.map +1 -0
- package/dist/docs-index.d.ts +7 -0
- package/dist/docs-index.js +11 -0
- package/dist/docs-index.js.map +1 -0
- package/dist/docs-page.d.ts +15 -0
- package/dist/docs-page.js +38 -0
- package/dist/docs-page.js.map +1 -0
- package/dist/docs-sidebar.d.ts +18 -0
- package/dist/docs-sidebar.d.ts.map +1 -0
- package/dist/docs-sidebar.js +27 -0
- package/dist/docs-sidebar.js.map +1 -0
- package/dist/documentation-layout.d.ts +15 -0
- package/dist/documentation-layout.js +20 -0
- package/dist/documentation-layout.js.map +1 -0
- package/dist/heading.d.ts +10 -0
- package/dist/heading.js +16 -0
- package/dist/heading.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/mdx/callouts.d.ts +8 -0
- package/dist/mdx/callouts.js +8 -0
- package/dist/mdx/callouts.js.map +1 -0
- package/dist/mdx/code-block.d.ts +8 -0
- package/dist/mdx/code-block.js +29 -0
- package/dist/mdx/code-block.js.map +1 -0
- package/dist/mdx/components.d.ts +13 -0
- package/dist/mdx/components.js +21 -0
- package/dist/mdx/components.js.map +1 -0
- package/dist/mdx.d.ts +20 -0
- package/dist/mdx.js +109 -0
- package/dist/mdx.js.map +1 -0
- package/dist/search-index.d.ts +10 -0
- package/dist/search-index.js +38 -0
- package/dist/search-index.js.map +1 -0
- package/dist/search.d.ts +6 -0
- package/dist/search.js +142 -0
- package/dist/search.js.map +1 -0
- package/dist/table-of-contents-provider.d.ts +4 -0
- package/dist/table-of-contents-provider.js +30 -0
- package/dist/table-of-contents-provider.js.map +1 -0
- package/dist/table-of-contents.d.ts +11 -0
- package/dist/table-of-contents.js +9 -0
- package/dist/table-of-contents.js.map +1 -0
- package/dist/theme-context.d.ts +20 -0
- package/dist/theme-context.js +28 -0
- package/dist/theme-context.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/ui/button.d.ts +12 -0
- package/dist/ui/button.js +34 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/dialog.d.ts +17 -0
- package/dist/ui/dialog.js +22 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/input.d.ts +4 -0
- package/dist/ui/input.js +9 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/util.d.ts +59 -0
- package/dist/util.js +96 -0
- package/dist/util.js.map +1 -0
- package/doc-pagination.tsx +67 -0
- package/docs-index.tsx +17 -0
- package/docs-page.tsx +68 -0
- package/docs-sidebar.tsx +165 -0
- package/documentation-layout.tsx +99 -0
- package/heading.tsx +63 -0
- package/index.ts +28 -0
- package/mdx/callouts.tsx +29 -0
- package/mdx/code-block.tsx +89 -0
- package/mdx/components.tsx +55 -0
- package/mdx.ts +138 -0
- package/package.json +99 -0
- package/search-index.ts +52 -0
- package/search.tsx +273 -0
- package/table-of-contents-provider.tsx +43 -0
- package/table-of-contents.tsx +44 -0
- package/theme-context.tsx +57 -0
- package/ui/button.tsx +56 -0
- package/ui/dialog.tsx +108 -0
- package/ui/input.tsx +22 -0
- package/util.ts +169 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
import { DocsSidebar } from "./docs-sidebar";
|
2
|
+
import { TableOfContents } from "./table-of-contents";
|
3
|
+
import { Search } from "./search";
|
4
|
+
import { DocPagination } from "./doc-pagination";
|
5
|
+
import { ReactDocsConfig } from "./config";
|
6
|
+
import { DocsThemeProvider } from "./theme-context";
|
7
|
+
import { getDocsSidebar } from "./util";
|
8
|
+
|
9
|
+
interface Props {
|
10
|
+
config: ReactDocsConfig;
|
11
|
+
navigation: any;
|
12
|
+
children: React.ReactNode;
|
13
|
+
currentPath: string;
|
14
|
+
headings: {
|
15
|
+
id: string;
|
16
|
+
text: string;
|
17
|
+
level: number;
|
18
|
+
}[];
|
19
|
+
}
|
20
|
+
|
21
|
+
export function DocumentationLayout({
|
22
|
+
config,
|
23
|
+
navigation,
|
24
|
+
children,
|
25
|
+
currentPath,
|
26
|
+
headings,
|
27
|
+
}: Props) {
|
28
|
+
// Get docs configuration for theming
|
29
|
+
const { config: docsConfig } = getDocsSidebar(config);
|
30
|
+
|
31
|
+
// Find current page and get prev/next
|
32
|
+
const allPages = navigation.flatMap((section: any) => section.pages);
|
33
|
+
const currentPageIndex = allPages.findIndex(
|
34
|
+
(page: any) => config.basePath + `/${page.slug}` === currentPath
|
35
|
+
);
|
36
|
+
const prevPage =
|
37
|
+
currentPageIndex > 0 ? allPages[currentPageIndex - 1] : undefined;
|
38
|
+
const nextPage =
|
39
|
+
currentPageIndex < allPages.length - 1
|
40
|
+
? allPages[currentPageIndex + 1]
|
41
|
+
: undefined;
|
42
|
+
|
43
|
+
return (
|
44
|
+
<DocsThemeProvider config={docsConfig}>
|
45
|
+
<div className="max-w-screen-xl mx-auto px-4">
|
46
|
+
<div className="flex gap-8">
|
47
|
+
{/* Left Sidebar */}
|
48
|
+
<div className="w-64 flex-shrink-0 hidden md:block">
|
49
|
+
<div className="sticky top-0">
|
50
|
+
<div className="py-4">
|
51
|
+
<DocsSidebar
|
52
|
+
config={config}
|
53
|
+
navigation={navigation}
|
54
|
+
currentPath={currentPath}
|
55
|
+
/>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
<div className="flex-1 min-w-0">
|
61
|
+
{/* Sticky Search Container */}
|
62
|
+
<div className="sticky top-0 z-50 bg-gray-900/95 border-b border-gray-800">
|
63
|
+
<div className="py-4">
|
64
|
+
<div className="flex items-center gap-1">
|
65
|
+
<div className="md:hidden">
|
66
|
+
<DocsSidebar
|
67
|
+
config={config}
|
68
|
+
navigation={navigation}
|
69
|
+
currentPath={currentPath}
|
70
|
+
/>
|
71
|
+
</div>
|
72
|
+
<div className="flex-1 md:max-w-2xl md:mx-auto">
|
73
|
+
<Search config={config} />
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
|
79
|
+
<main className="py-8 max-w-2xl mx-auto">
|
80
|
+
<div className="relative z-0">
|
81
|
+
{children}
|
82
|
+
<DocPagination config={config} prev={prevPage} next={nextPage} />
|
83
|
+
</div>
|
84
|
+
</main>
|
85
|
+
</div>
|
86
|
+
|
87
|
+
{/* Right Sidebar */}
|
88
|
+
<div className="w-64 flex-shrink-0 hidden lg:block">
|
89
|
+
<div className="sticky top-0">
|
90
|
+
<div className="py-4">
|
91
|
+
<TableOfContents headings={headings} />
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
</div>
|
97
|
+
</DocsThemeProvider>
|
98
|
+
);
|
99
|
+
}
|
package/heading.tsx
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
"use client";
|
2
|
+
import { useState } from "react";
|
3
|
+
import { cn } from "./cn";
|
4
|
+
import React from "react";
|
5
|
+
|
6
|
+
interface HeadingProps {
|
7
|
+
level: 1 | 2 | 3;
|
8
|
+
id: string;
|
9
|
+
children: React.ReactNode;
|
10
|
+
className?: string;
|
11
|
+
}
|
12
|
+
|
13
|
+
export function Heading({ level, id, children, className }: HeadingProps) {
|
14
|
+
const [copied, setCopied] = useState(false);
|
15
|
+
const Tag = `h${level}` as keyof React.JSX.IntrinsicElements;
|
16
|
+
|
17
|
+
const copyLink = () => {
|
18
|
+
const url = `${window.location.origin}${window.location.pathname}#${id}`;
|
19
|
+
navigator.clipboard.writeText(url);
|
20
|
+
setCopied(true);
|
21
|
+
setTimeout(() => setCopied(false), 2000);
|
22
|
+
};
|
23
|
+
|
24
|
+
return (
|
25
|
+
<Tag
|
26
|
+
id={id}
|
27
|
+
className={cn(
|
28
|
+
"group relative flex items-center scroll-mt-24",
|
29
|
+
level === 1 && "text-3xl font-medium mb-6 mt-6",
|
30
|
+
level === 2 && "text-2xl font-medium mb-4 mt-8",
|
31
|
+
level === 3 && "text-lg font-medium mb-4 mt-6",
|
32
|
+
className
|
33
|
+
)}
|
34
|
+
>
|
35
|
+
<a href={`#${id}`} className="no-underline">
|
36
|
+
{children}
|
37
|
+
</a>
|
38
|
+
<button
|
39
|
+
onClick={copyLink}
|
40
|
+
className="invisible ml-2 group-hover:visible"
|
41
|
+
aria-label="Copy link to heading"
|
42
|
+
>
|
43
|
+
{copied ? (
|
44
|
+
<span className="text-green-500 text-sm">✓</span>
|
45
|
+
) : (
|
46
|
+
<svg
|
47
|
+
className="w-5 h-5 text-gray-400 hover:text-white"
|
48
|
+
fill="none"
|
49
|
+
stroke="currentColor"
|
50
|
+
viewBox="0 0 24 24"
|
51
|
+
>
|
52
|
+
<path
|
53
|
+
strokeLinecap="round"
|
54
|
+
strokeLinejoin="round"
|
55
|
+
strokeWidth={2}
|
56
|
+
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
57
|
+
/>
|
58
|
+
</svg>
|
59
|
+
)}
|
60
|
+
</button>
|
61
|
+
</Tag>
|
62
|
+
);
|
63
|
+
}
|
package/index.ts
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
export type { ReactDocsConfig, ReactDocsConfigOptions } from "./config";
|
2
|
+
export { createReactDocsConfig } from "./config";
|
3
|
+
|
4
|
+
export { getDocsSidebar } from "./util";
|
5
|
+
export type { DocsJsonConfig } from "./util";
|
6
|
+
export { getAllDocs, getAllDocsContent, getDocBySlug } from "./mdx";
|
7
|
+
|
8
|
+
// Search functionality
|
9
|
+
export { buildSearchIndex } from "./search-index";
|
10
|
+
export { Search } from "./search";
|
11
|
+
|
12
|
+
// Chat API
|
13
|
+
export { streamDocsChatResponse } from "./chat-api";
|
14
|
+
export type { ModelProvider, Message, FinishResult } from "./chat-api";
|
15
|
+
|
16
|
+
// Page components and functions
|
17
|
+
export { generateDocsMetadata, generateDocsStaticParams, DocsPage } from "./docs-page";
|
18
|
+
export { DocsIndex } from "./docs-index";
|
19
|
+
|
20
|
+
// Layout components
|
21
|
+
export { DocumentationLayout } from "./documentation-layout";
|
22
|
+
export { DocsSidebar } from "./docs-sidebar";
|
23
|
+
export { TableOfContents } from "./table-of-contents";
|
24
|
+
export { DocPagination } from "./doc-pagination";
|
25
|
+
|
26
|
+
// Theme context
|
27
|
+
export { DocsThemeProvider, useDocsTheme, useDocsColors } from "./theme-context";
|
28
|
+
export type { ThemeColors, DocsTheme } from "./theme-context";
|
package/mdx/callouts.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
export function Info({ children }: { children: React.ReactNode }) {
|
4
|
+
return (
|
5
|
+
<div className="bg-blue-900/30 border border-blue-500/20 rounded-lg p-3 my-3">
|
6
|
+
<div className="flex gap-2 items-center text-blue-400 mb-1.5">
|
7
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
8
|
+
<path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm1-3V7H7v6h2zM8 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
|
9
|
+
</svg>
|
10
|
+
<span className="font-medium">Info</span>
|
11
|
+
</div>
|
12
|
+
<div className="text-gray-300 [&>p]:m-0">{children}</div>
|
13
|
+
</div>
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
export function Warning({ children }: { children: React.ReactNode }) {
|
18
|
+
return (
|
19
|
+
<div className="bg-yellow-900/30 border border-yellow-500/20 rounded-lg p-3 my-3">
|
20
|
+
<div className="flex gap-2 items-center text-yellow-400 mb-1.5">
|
21
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
22
|
+
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
23
|
+
</svg>
|
24
|
+
<span className="font-medium">Warning</span>
|
25
|
+
</div>
|
26
|
+
<div className="text-gray-300 [&>p]:m-0">{children}</div>
|
27
|
+
</div>
|
28
|
+
);
|
29
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
"use client";
|
2
|
+
|
3
|
+
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
|
4
|
+
import { useState } from "react";
|
5
|
+
import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter";
|
6
|
+
const SyntaxHighlighter = Prism as any as React.FC<SyntaxHighlighterProps>;
|
7
|
+
|
8
|
+
interface CodeBlockProps {
|
9
|
+
children: string;
|
10
|
+
className?: string;
|
11
|
+
inline?: boolean;
|
12
|
+
}
|
13
|
+
|
14
|
+
export function CodeBlock({ children, className, inline }: CodeBlockProps) {
|
15
|
+
const [copied, setCopied] = useState(false);
|
16
|
+
|
17
|
+
if (inline) {
|
18
|
+
return (
|
19
|
+
<code className="bg-gray-800/50 px-1.5 py-0.5 rounded text-sm font-mono">
|
20
|
+
{children}
|
21
|
+
</code>
|
22
|
+
);
|
23
|
+
}
|
24
|
+
|
25
|
+
const language = className?.replace("language-", "") || "text";
|
26
|
+
|
27
|
+
const copyCode = () => {
|
28
|
+
navigator.clipboard.writeText(String(children));
|
29
|
+
setCopied(true);
|
30
|
+
setTimeout(() => setCopied(false), 2000);
|
31
|
+
};
|
32
|
+
|
33
|
+
return (
|
34
|
+
<div
|
35
|
+
className="relative bg-gray-800/50 rounded-lg overflow-x-auto w-full my-4 group"
|
36
|
+
style={{ maxWidth: "100%" }}
|
37
|
+
>
|
38
|
+
<button
|
39
|
+
onClick={copyCode}
|
40
|
+
className="absolute right-2 top-2 p-2 rounded-md bg-gray-700/50 invisible group-hover:visible hover:bg-gray-700 transition-colors"
|
41
|
+
aria-label="Copy code"
|
42
|
+
>
|
43
|
+
{copied ? (
|
44
|
+
<svg
|
45
|
+
width="16"
|
46
|
+
height="16"
|
47
|
+
viewBox="0 0 24 24"
|
48
|
+
fill="none"
|
49
|
+
stroke="currentColor"
|
50
|
+
strokeWidth="2"
|
51
|
+
className="text-green-500"
|
52
|
+
>
|
53
|
+
<path d="M20 6L9 17l-5-5" />
|
54
|
+
</svg>
|
55
|
+
) : (
|
56
|
+
<svg
|
57
|
+
width="16"
|
58
|
+
height="16"
|
59
|
+
viewBox="0 0 24 24"
|
60
|
+
fill="none"
|
61
|
+
stroke="currentColor"
|
62
|
+
strokeWidth="2"
|
63
|
+
className="text-gray-400"
|
64
|
+
>
|
65
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
66
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
67
|
+
</svg>
|
68
|
+
)}
|
69
|
+
</button>
|
70
|
+
<SyntaxHighlighter
|
71
|
+
language={language}
|
72
|
+
style={vscDarkPlus}
|
73
|
+
customStyle={{
|
74
|
+
margin: 0,
|
75
|
+
background: "transparent",
|
76
|
+
fontSize: "1.1rem",
|
77
|
+
lineHeight: "1.4",
|
78
|
+
padding: "1.5rem",
|
79
|
+
borderRadius: "0.5rem",
|
80
|
+
maxWidth: "100%",
|
81
|
+
overflowX: "auto",
|
82
|
+
}}
|
83
|
+
wrapLongLines={false}
|
84
|
+
>
|
85
|
+
{children}
|
86
|
+
</SyntaxHighlighter>
|
87
|
+
</div>
|
88
|
+
);
|
89
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { Heading } from "../heading";
|
2
|
+
import { Info, Warning } from "./callouts";
|
3
|
+
import { CodeBlock } from "./code-block";
|
4
|
+
import { slugify } from "../cn";
|
5
|
+
import { HTMLAttributes } from "react";
|
6
|
+
import Image from "next/image";
|
7
|
+
|
8
|
+
export const mdxComponents = {
|
9
|
+
Info,
|
10
|
+
Warning,
|
11
|
+
h1: ({ children, ...props }: HTMLAttributes<HTMLHeadingElement>) => (
|
12
|
+
<Heading level={1} id={slugify(String(children))} {...props}>
|
13
|
+
{children}
|
14
|
+
</Heading>
|
15
|
+
),
|
16
|
+
h2: ({ children, ...props }: HTMLAttributes<HTMLHeadingElement>) => (
|
17
|
+
<Heading level={2} id={slugify(String(children))} {...props}>
|
18
|
+
{children}
|
19
|
+
</Heading>
|
20
|
+
),
|
21
|
+
h3: ({ children, ...props }: HTMLAttributes<HTMLHeadingElement>) => (
|
22
|
+
<Heading level={3} id={slugify(String(children))} {...props}>
|
23
|
+
{children}
|
24
|
+
</Heading>
|
25
|
+
),
|
26
|
+
pre: (props: any) => <CodeBlock {...props.children.props} />,
|
27
|
+
code: (props: HTMLAttributes<HTMLElement>) => (
|
28
|
+
<code
|
29
|
+
{...props}
|
30
|
+
className={`${props.className || ""
|
31
|
+
} bg-gray-800/50 px-1.5 py-0.5 rounded text-sm font-mono`}
|
32
|
+
/>
|
33
|
+
),
|
34
|
+
img: ({
|
35
|
+
src,
|
36
|
+
alt,
|
37
|
+
width: _width,
|
38
|
+
height: _height,
|
39
|
+
...props
|
40
|
+
}: React.ImgHTMLAttributes<HTMLImageElement>) => {
|
41
|
+
if (!src) return null;
|
42
|
+
return (
|
43
|
+
<Image
|
44
|
+
src={src as string}
|
45
|
+
alt={alt || ""}
|
46
|
+
width={0}
|
47
|
+
height={0}
|
48
|
+
sizes="100vw"
|
49
|
+
style={{ width: "100%", height: "auto" }}
|
50
|
+
className="max-w-[400px] aspect-auto mx-auto rounded-lg"
|
51
|
+
{...props}
|
52
|
+
/>
|
53
|
+
);
|
54
|
+
},
|
55
|
+
};
|
package/mdx.ts
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
import fs from "fs";
|
2
|
+
import path from "path";
|
3
|
+
import matter from "gray-matter";
|
4
|
+
import { MDXRemote } from "next-mdx-remote/rsc";
|
5
|
+
import { mdxComponents } from "./mdx/components";
|
6
|
+
import { MDXComponents } from "mdx/types";
|
7
|
+
import { ReactDocsConfig } from "./config";
|
8
|
+
|
9
|
+
interface Heading {
|
10
|
+
id: string;
|
11
|
+
text: string;
|
12
|
+
level: number;
|
13
|
+
}
|
14
|
+
|
15
|
+
function slugify(str: string) {
|
16
|
+
return String(str)
|
17
|
+
.toLowerCase()
|
18
|
+
.trim()
|
19
|
+
.replace(/[^\w\s-]/g, '')
|
20
|
+
.replace(/[\s_-]+/g, '-')
|
21
|
+
.replace(/^-+|-+$/g, '');
|
22
|
+
}
|
23
|
+
|
24
|
+
function extractHeadings(source: string): Heading[] {
|
25
|
+
const headings: Heading[] = [];
|
26
|
+
const lines = source.split("\n");
|
27
|
+
let inCodeBlock = false;
|
28
|
+
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
30
|
+
const line = lines[i];
|
31
|
+
|
32
|
+
// Check for code block delimiters
|
33
|
+
if (line.trim().startsWith("```")) {
|
34
|
+
inCodeBlock = !inCodeBlock;
|
35
|
+
continue;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Skip if we're inside a code block
|
39
|
+
if (inCodeBlock) continue;
|
40
|
+
|
41
|
+
// Match headings, but only if they're not inside a code block
|
42
|
+
const headingMatch = line.match(/^(#{1,3})\s+(.+)$/);
|
43
|
+
if (headingMatch) {
|
44
|
+
const level = headingMatch[1].length;
|
45
|
+
const text = headingMatch[2];
|
46
|
+
const id = slugify(text);
|
47
|
+
|
48
|
+
headings.push({ level, text, id });
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
return headings;
|
53
|
+
}
|
54
|
+
|
55
|
+
export async function getDocBySlug(docsConfig: ReactDocsConfig, slug: string) {
|
56
|
+
const directory = path.join(process.cwd(), docsConfig.contentPath);
|
57
|
+
const fullPath = path.join(directory, `${slug}.mdx`);
|
58
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
59
|
+
const { data: frontmatter, content: source } = matter(fileContents);
|
60
|
+
const headings = extractHeadings(source);
|
61
|
+
|
62
|
+
return {
|
63
|
+
frontmatter,
|
64
|
+
content: await MDXRemote({
|
65
|
+
source,
|
66
|
+
components: mdxComponents as MDXComponents,
|
67
|
+
}),
|
68
|
+
headings,
|
69
|
+
};
|
70
|
+
}
|
71
|
+
|
72
|
+
export function getAllDocs(docsConfig: ReactDocsConfig) {
|
73
|
+
const docs: { slug: string; frontmatter: any }[] = [];
|
74
|
+
|
75
|
+
function traverseDirectory(currentPath: string, baseDir: string) {
|
76
|
+
const files = fs.readdirSync(currentPath);
|
77
|
+
|
78
|
+
for (const file of files) {
|
79
|
+
const fullPath = path.join(currentPath, file);
|
80
|
+
const stat = fs.statSync(fullPath);
|
81
|
+
|
82
|
+
if (stat.isDirectory()) {
|
83
|
+
traverseDirectory(fullPath, baseDir);
|
84
|
+
} else if (file.endsWith(".mdx")) {
|
85
|
+
const relativePath = path.relative(baseDir, fullPath);
|
86
|
+
const slug = relativePath.replace(/\.mdx$/, "");
|
87
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
88
|
+
const { data: frontmatter } = matter(fileContents);
|
89
|
+
|
90
|
+
docs.push({
|
91
|
+
slug,
|
92
|
+
frontmatter,
|
93
|
+
});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
const docsDirectory = path.join(process.cwd(), docsConfig.contentPath);
|
99
|
+
traverseDirectory(docsDirectory, docsDirectory);
|
100
|
+
return docs;
|
101
|
+
}
|
102
|
+
|
103
|
+
// Cache for docs content
|
104
|
+
const docsContentCache = new Map<string, string>();
|
105
|
+
|
106
|
+
export function getAllDocsContent(docsConfig: ReactDocsConfig): string {
|
107
|
+
const cacheKey = docsConfig.contentPath;
|
108
|
+
|
109
|
+
if (docsContentCache.has(cacheKey)) {
|
110
|
+
return docsContentCache.get(cacheKey)!;
|
111
|
+
}
|
112
|
+
|
113
|
+
const content: string[] = [];
|
114
|
+
|
115
|
+
function traverseDirectory(currentPath: string) {
|
116
|
+
const files = fs.readdirSync(currentPath);
|
117
|
+
|
118
|
+
for (const file of files) {
|
119
|
+
const fullPath = path.join(currentPath, file);
|
120
|
+
const stat = fs.statSync(fullPath);
|
121
|
+
|
122
|
+
if (stat.isDirectory()) {
|
123
|
+
traverseDirectory(fullPath);
|
124
|
+
} else if (file.endsWith(".mdx")) {
|
125
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
126
|
+
const { content: mdxContent } = matter(fileContents);
|
127
|
+
content.push(mdxContent);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
const docsDirectory = path.join(process.cwd(), docsConfig.contentPath);
|
133
|
+
traverseDirectory(docsDirectory);
|
134
|
+
|
135
|
+
const joinedContent = content.join('\n\n---\n\n');
|
136
|
+
docsContentCache.set(cacheKey, joinedContent);
|
137
|
+
return joinedContent;
|
138
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
{
|
2
|
+
"name": "react-docs-module",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"description": "Embeddable documentation page with search and AI chat",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"types": "dist/index.d.ts",
|
7
|
+
"type": "module",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"development": "./index.ts",
|
11
|
+
"types": "./dist/index.d.ts",
|
12
|
+
"import": "./dist/index.js"
|
13
|
+
},
|
14
|
+
"./config": {
|
15
|
+
"development": "./config.ts",
|
16
|
+
"types": "./dist/config.d.ts",
|
17
|
+
"import": "./dist/config.js"
|
18
|
+
},
|
19
|
+
"./search": {
|
20
|
+
"development": "./search.tsx",
|
21
|
+
"types": "./dist/search.d.ts",
|
22
|
+
"import": "./dist/search.js"
|
23
|
+
},
|
24
|
+
"./chat-api": {
|
25
|
+
"development": "./chat-api.ts",
|
26
|
+
"types": "./dist/chat-api.d.ts",
|
27
|
+
"import": "./dist/chat-api.js"
|
28
|
+
},
|
29
|
+
"./mdx": {
|
30
|
+
"development": "./mdx.ts",
|
31
|
+
"types": "./dist/mdx.d.ts",
|
32
|
+
"import": "./dist/mdx.js"
|
33
|
+
},
|
34
|
+
"./utils": {
|
35
|
+
"development": "./util.ts",
|
36
|
+
"types": "./dist/util.d.ts",
|
37
|
+
"import": "./dist/util.js"
|
38
|
+
}
|
39
|
+
},
|
40
|
+
"files": [
|
41
|
+
"dist",
|
42
|
+
"*.ts",
|
43
|
+
"*.tsx",
|
44
|
+
"mdx/",
|
45
|
+
"ui/",
|
46
|
+
"README.md"
|
47
|
+
],
|
48
|
+
"peerDependencies": {
|
49
|
+
"react": "^19.1.0",
|
50
|
+
"react-dom": "^19.1.0",
|
51
|
+
"@ai-sdk/anthropic": "^2.0.0",
|
52
|
+
"@ai-sdk/groq": "^1.2.9",
|
53
|
+
"@ai-sdk/openai": "^2.0.0",
|
54
|
+
"@radix-ui/react-dialog": "^1.1.4",
|
55
|
+
"@radix-ui/react-slot": "^1.1.0",
|
56
|
+
"ai": "^4.3.19",
|
57
|
+
"class-variance-authority": "^0.7.0",
|
58
|
+
"clsx": "^2.1.1",
|
59
|
+
"gray-matter": "^4.0.3",
|
60
|
+
"lucide-react": "^0.456.0",
|
61
|
+
"next-mdx-remote": "^5.0.0",
|
62
|
+
"react-markdown": "^9.1.0",
|
63
|
+
"react-syntax-highlighter": "^15.6.1",
|
64
|
+
"tailwind-merge": "^2.5.4",
|
65
|
+
"tailwindcss-animate": "^1.0.7"
|
66
|
+
},
|
67
|
+
"devDependencies": {
|
68
|
+
"@types/react": "^19.1.1",
|
69
|
+
"@types/react-dom": "^19.1.2",
|
70
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
71
|
+
"@types/node": "^22.10.2",
|
72
|
+
"typescript": "^5.6.3"
|
73
|
+
},
|
74
|
+
"keywords": [
|
75
|
+
"documentation",
|
76
|
+
"mdx",
|
77
|
+
"search",
|
78
|
+
"ai-chat",
|
79
|
+
"react",
|
80
|
+
"typescript",
|
81
|
+
"mintlify"
|
82
|
+
],
|
83
|
+
"author": "Ivan Chebykin",
|
84
|
+
"license": "MIT",
|
85
|
+
"readme": "README.md",
|
86
|
+
"homepage": "https://github.com/sourcewizard-ai/react-docs-module",
|
87
|
+
"repository": {
|
88
|
+
"type": "git",
|
89
|
+
"url": "https://github.com/sourcewizard-ai/react-docs-module.git"
|
90
|
+
},
|
91
|
+
"bugs": {
|
92
|
+
"url": "https://github.com/sourcewizard-ai/react-docs-module/issues"
|
93
|
+
},
|
94
|
+
"scripts": {
|
95
|
+
"build": "tsc --build",
|
96
|
+
"clean": "rm -rf dist",
|
97
|
+
"typecheck": "tsc --noEmit --skipLibCheck"
|
98
|
+
}
|
99
|
+
}
|
package/search-index.ts
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
import fs from "fs";
|
2
|
+
import path from "path";
|
3
|
+
import matter from "gray-matter";
|
4
|
+
import { ReactDocsConfig } from "./config";
|
5
|
+
|
6
|
+
export interface SearchResult {
|
7
|
+
title: string;
|
8
|
+
content: string;
|
9
|
+
url: string;
|
10
|
+
snippet?: string;
|
11
|
+
score?: number;
|
12
|
+
}
|
13
|
+
|
14
|
+
export function buildSearchIndex(config: ReactDocsConfig): SearchResult[] {
|
15
|
+
const docsDirectory = path.join(process.cwd(), config.contentPath);
|
16
|
+
const searchIndex: SearchResult[] = [];
|
17
|
+
|
18
|
+
function processDirectory(dir: string, baseUrl: string = "") {
|
19
|
+
const items = fs.readdirSync(dir);
|
20
|
+
|
21
|
+
items.forEach((item) => {
|
22
|
+
const fullPath = path.join(dir, item);
|
23
|
+
const stat = fs.statSync(fullPath);
|
24
|
+
|
25
|
+
if (stat.isDirectory()) {
|
26
|
+
processDirectory(fullPath, `${baseUrl}${item}/`);
|
27
|
+
} else if (item.endsWith(".mdx")) {
|
28
|
+
const fileContents = fs.readFileSync(fullPath, "utf8");
|
29
|
+
const { data: frontmatter, content } = matter(fileContents);
|
30
|
+
const slug = item.replace(/\.mdx$/, "");
|
31
|
+
const url = config.basePath + `/${baseUrl}${slug}`;
|
32
|
+
|
33
|
+
// Clean up the content by removing markdown syntax
|
34
|
+
const cleanContent = content
|
35
|
+
.replace(/```[\s\S]*?```/g, "") // Remove code blocks
|
36
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove markdown links but keep text
|
37
|
+
.replace(/[#*`_]/g, "") // Remove markdown syntax
|
38
|
+
.replace(/\n+/g, " ") // Replace newlines with spaces
|
39
|
+
.trim();
|
40
|
+
|
41
|
+
searchIndex.push({
|
42
|
+
title: frontmatter.title || slug,
|
43
|
+
content: cleanContent,
|
44
|
+
url,
|
45
|
+
});
|
46
|
+
}
|
47
|
+
});
|
48
|
+
}
|
49
|
+
|
50
|
+
processDirectory(docsDirectory);
|
51
|
+
return searchIndex;
|
52
|
+
}
|