Skip to content

Commit 5502fac

Browse files
authored
Merge pull request #181 from codelitdev/fumadocs-upgrade-mar26
Documentation upgrade
2 parents cb8c577 + c9488c4 commit 5502fac

File tree

34 files changed

+3475
-1161
lines changed

34 files changed

+3475
-1161
lines changed

.github/workflows/code-quality.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: Code Quality
2-
on: [push]
2+
on:
3+
push:
4+
branches:
5+
- '**'
36

47
concurrency: ${{ github.workflow }}-${{ github.ref }}
58

@@ -49,4 +52,4 @@ jobs:
4952
pnpm -r build
5053
5154
- name: Run test
52-
run: pnpm test
55+
run: pnpm test

apps/docs/app/(home)/[[...slug]]/page.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,56 @@ import {
55
DocsDescription,
66
DocsTitle,
77
} from "fumadocs-ui/page";
8-
import { notFound } from "next/navigation";
8+
import { notFound, redirect } from "next/navigation";
99
import { createRelativeLink } from "fumadocs-ui/mdx";
1010
import { getMDXComponents } from "@/mdx-components";
11+
import { APIPage } from "@/components/api-page";
12+
import { PageActions } from "@/components/page-actions";
1113

1214
export default async function Page(props: {
1315
params: Promise<{ slug?: string[] }>;
1416
}) {
1517
const params = await props.params;
18+
const slugPath = params.slug?.join("/");
19+
20+
if (slugPath?.endsWith(".mdx")) {
21+
const markdownSlug = slugPath.slice(0, -4);
22+
redirect(`/llms.mdx/${markdownSlug || "index"}`);
23+
}
24+
25+
if (slugPath === "rest-api" || slugPath === "api-reference") {
26+
redirect("/api/uploadMedia");
27+
}
28+
1629
const page = source.getPage(params.slug);
1730
if (!page) notFound();
1831

32+
if (page.data.type === "openapi") {
33+
return (
34+
<DocsPage full>
35+
<DocsTitle>{page.data.title}</DocsTitle>
36+
<DocsDescription>{page.data.description}</DocsDescription>
37+
<DocsBody>
38+
<APIPage {...page.data.getAPIPageProps()} />
39+
</DocsBody>
40+
</DocsPage>
41+
);
42+
}
43+
1944
const MDXContent = page.data.body;
45+
const markdownUrl = `${page.url === "/" ? "/index" : page.url}.mdx`;
46+
const githubBaseUrl =
47+
process.env.NEXT_PUBLIC_DOCS_GITHUB_BASE_URL ||
48+
"https://github.com/codelitdev/medialit/blob/main/apps/docs/content/docs";
49+
const githubUrl = `${githubBaseUrl.replace(/\/$/, "")}/${page.path}`;
2050

2151
return (
2252
<DocsPage toc={page.data.toc} full={page.data.full}>
2353
<DocsTitle>{page.data.title}</DocsTitle>
24-
<DocsDescription>{page.data.description}</DocsDescription>
54+
<DocsDescription className="mb-4">
55+
{page.data.description}
56+
</DocsDescription>
57+
<PageActions markdownUrl={markdownUrl} githubUrl={githubUrl} />
2558
<DocsBody>
2659
<MDXContent
2760
components={getMDXComponents({
@@ -35,7 +68,9 @@ export default async function Page(props: {
3568
}
3669

3770
export async function generateStaticParams() {
38-
return source.generateParams();
71+
const params = source.generateParams();
72+
73+
return [...params, { slug: ["rest-api"] }, { slug: ["api-reference"] }];
3974
}
4075

4176
export async function generateMetadata(props: {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { source } from "@/lib/source";
2+
import { getLLMText } from "@/lib/get-llm-text";
3+
4+
export const revalidate = false;
5+
6+
export async function GET(
7+
req: Request,
8+
_ctx: { params: Promise<Record<string, string | string[]>> },
9+
) {
10+
const pathname = new URL(req.url).pathname;
11+
const relative = pathname.replace(/^\/+/, "").replace(/\.mdx$/, "");
12+
const slug = relative.length > 0 ? relative.split("/") : [];
13+
const normalizedSlug =
14+
slug.length === 1 && slug[0] === "index" ? undefined : slug;
15+
const page = source.getPage(normalizedSlug);
16+
17+
if (!page) {
18+
return new Response("Not Found", { status: 404 });
19+
}
20+
21+
return new Response(await getLLMText(page), {
22+
headers: {
23+
"Content-Type": "text/markdown; charset=utf-8",
24+
},
25+
});
26+
}
27+
28+
export function generateStaticParams() {
29+
return source.generateParams().map((item) => ({
30+
slug: item.slug.length > 0 ? item.slug : ["index"],
31+
}));
32+
}

apps/docs/app/global.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
@import "tailwindcss";
22
@import "fumadocs-ui/css/neutral.css";
33
@import "fumadocs-ui/css/preset.css";
4+
@import "fumadocs-openapi/css/preset.css";

apps/docs/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import "./global.css";
2-
import { RootProvider } from "fumadocs-ui/provider";
2+
import { RootProvider } from "fumadocs-ui/provider/next";
33
import { Inter } from "next/font/google";
44
import type { ReactNode } from "react";
55

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { source } from "@/lib/source";
2+
import { getLLMText } from "@/lib/get-llm-text";
3+
4+
export const revalidate = false;
5+
6+
export async function GET(
7+
_req: Request,
8+
{ params }: { params: Promise<{ slug: string[] }> },
9+
) {
10+
const { slug } = await params;
11+
const normalizedSlug =
12+
slug.length === 1 && slug[0] === "index" ? undefined : slug;
13+
const page = source.getPage(normalizedSlug);
14+
15+
if (!page) {
16+
return new Response("Not Found", { status: 404 });
17+
}
18+
19+
return new Response(await getLLMText(page), {
20+
headers: {
21+
"Content-Type": "text/markdown",
22+
},
23+
});
24+
}
25+
26+
export function generateStaticParams() {
27+
return source.generateParams().map((item) => ({
28+
slug: item.slug.length > 0 ? item.slug : ["index"],
29+
}));
30+
}

apps/docs/app/llms.txt/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { source } from "@/lib/source";
2+
import { llms } from "fumadocs-core/source";
3+
4+
export const revalidate = false;
5+
6+
export function GET() {
7+
return new Response(llms(source).index(), {
8+
headers: {
9+
"Content-Type": "text/plain; charset=utf-8",
10+
},
11+
});
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
import { defineClientConfig } from "fumadocs-openapi/ui/client";
4+
5+
export default defineClientConfig({});

apps/docs/components/api-page.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { openapi } from "@/lib/openapi";
2+
import { createAPIPage } from "fumadocs-openapi/ui";
3+
import client from "./api-page.client";
4+
5+
export const APIPage = createAPIPage(openapi, {
6+
client,
7+
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"use client";
2+
3+
import { useMemo } from "react";
4+
import type { ReactNode } from "react";
5+
import { usePathname } from "fumadocs-core/framework";
6+
import { buttonVariants } from "fumadocs-ui/components/ui/button";
7+
import {
8+
Popover,
9+
PopoverContent,
10+
PopoverTrigger,
11+
} from "fumadocs-ui/components/ui/popover";
12+
import { MarkdownCopyButton } from "fumadocs-ui/layouts/docs/page";
13+
import { ChevronDown, ExternalLinkIcon, TextIcon } from "lucide-react";
14+
15+
type ViewOptionsPopoverProps = {
16+
markdownUrl: string;
17+
githubUrl: string;
18+
className?: string;
19+
};
20+
21+
type ViewOption = {
22+
title: string;
23+
href: string;
24+
icon: ReactNode;
25+
};
26+
27+
const linkClassName =
28+
"text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4";
29+
30+
function iconGitHub() {
31+
return (
32+
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
33+
<title>GitHub</title>
34+
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
35+
</svg>
36+
);
37+
}
38+
39+
function iconOpenAI() {
40+
return (
41+
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
42+
<title>OpenAI</title>
43+
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
44+
</svg>
45+
);
46+
}
47+
48+
function iconClaude() {
49+
return (
50+
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
51+
<title>Anthropic</title>
52+
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
53+
</svg>
54+
);
55+
}
56+
57+
function iconCursor() {
58+
return (
59+
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
60+
<title>Cursor</title>
61+
<path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" />
62+
</svg>
63+
);
64+
}
65+
66+
export function PageActions({
67+
markdownUrl,
68+
githubUrl,
69+
}: {
70+
markdownUrl: string;
71+
githubUrl: string;
72+
}) {
73+
return (
74+
<div className="not-prose mb-6 flex items-center gap-2 border-b pb-6">
75+
<MarkdownCopyButton markdownUrl={markdownUrl} />
76+
<ViewOptionsPopover
77+
markdownUrl={markdownUrl}
78+
githubUrl={githubUrl}
79+
/>
80+
</div>
81+
);
82+
}
83+
84+
export function ViewOptionsPopover({
85+
markdownUrl,
86+
githubUrl,
87+
className,
88+
}: ViewOptionsPopoverProps) {
89+
const pathname = usePathname();
90+
91+
const items = useMemo<ViewOption[]>(() => {
92+
const q = `Read ${typeof window === "undefined" ? pathname : new URL(pathname, window.location.origin)}, I want to ask questions about it.`;
93+
94+
return [
95+
{
96+
title: "Open in GitHub",
97+
href: githubUrl,
98+
icon: iconGitHub(),
99+
},
100+
{
101+
title: "View as Markdown",
102+
href: markdownUrl,
103+
icon: <TextIcon />,
104+
},
105+
{
106+
title: "Open in ChatGPT",
107+
href: `https://chatgpt.com/?${new URLSearchParams({
108+
hints: "search",
109+
q,
110+
})}`,
111+
icon: iconOpenAI(),
112+
},
113+
{
114+
title: "Open in Claude",
115+
href: `https://claude.ai/new?${new URLSearchParams({ q })}`,
116+
icon: iconClaude(),
117+
},
118+
{
119+
title: "Open in Cursor",
120+
href: `https://cursor.com/link/prompt?${new URLSearchParams({
121+
text: q,
122+
})}`,
123+
icon: iconCursor(),
124+
},
125+
];
126+
}, [githubUrl, markdownUrl, pathname]);
127+
128+
return (
129+
<Popover>
130+
<PopoverTrigger
131+
className={[
132+
buttonVariants({ color: "secondary", size: "sm" }),
133+
"gap-2 data-[state=open]:bg-fd-accent data-[state=open]:text-fd-accent-foreground",
134+
className,
135+
]
136+
.filter(Boolean)
137+
.join(" ")}
138+
>
139+
Open
140+
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
141+
</PopoverTrigger>
142+
<PopoverContent className="flex flex-col">
143+
{items.map((item) => (
144+
<a
145+
key={item.href}
146+
href={item.href}
147+
rel="noreferrer noopener"
148+
target="_blank"
149+
className={linkClassName}
150+
>
151+
{item.icon}
152+
{item.title}
153+
<ExternalLinkIcon className="text-fd-muted-foreground size-3.5 ms-auto" />
154+
</a>
155+
))}
156+
</PopoverContent>
157+
</Popover>
158+
);
159+
}

0 commit comments

Comments
 (0)