mirror of
https://github.com/zen-browser/docs.git
synced 2025-07-07 08:55:33 +02:00
Migrated project from Quartz to Next.js with Fumadocs
This commit is contained in:
parent
d0e8fca4a6
commit
ff60b8afc1
383 changed files with 8990 additions and 152443 deletions
66
src/app/(docs)/[[...slug]]/page.tsx
Normal file
66
src/app/(docs)/[[...slug]]/page.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { source } from "@/lib/source";
|
||||
import {
|
||||
DocsPage,
|
||||
DocsBody,
|
||||
DocsDescription,
|
||||
DocsTitle,
|
||||
} from "fumadocs-ui/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import defaultMdxComponents, { createRelativeLink } from "fumadocs-ui/mdx";
|
||||
import { ImageZoom } from "fumadocs-ui/components/image-zoom";
|
||||
import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock";
|
||||
import { metadataImage } from "@/lib/metadata";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const MDXContent = page.data.body;
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<MDXContent
|
||||
components={{
|
||||
...defaultMdxComponents,
|
||||
a: createRelativeLink(source, page),
|
||||
img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (
|
||||
<ImageZoom {...props} />
|
||||
),
|
||||
pre: (props) => (
|
||||
<CodeBlock {...props}>
|
||||
<Pre>{props.children}</Pre>
|
||||
</CodeBlock>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams();
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return metadataImage.withImage(page.slugs, {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
metadataBase:
|
||||
process.env.NODE_ENV === "production"
|
||||
? new URL("https://docs.zen-browser.app")
|
||||
: undefined,
|
||||
});
|
||||
}
|
12
src/app/(docs)/layout.tsx
Normal file
12
src/app/(docs)/layout.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
||||
import type { ReactNode } from "react";
|
||||
import { baseOptions } from "@/app/layout.config";
|
||||
import { source } from "@/lib/source";
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout tree={source.pageTree} {...baseOptions}>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
27
src/app/(docs)/not-found.tsx
Normal file
27
src/app/(docs)/not-found.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { buttonVariants } from "fumadocs-ui/components/api";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex items-center w-full h-screen">
|
||||
<div className="w-full space-y-6 text-center">
|
||||
<div className="space-y-3">
|
||||
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl transition-transform">
|
||||
404
|
||||
</h1>
|
||||
<p className="text-gray-500">
|
||||
Looks like you've ventured into the unknown digital realm.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
className={buttonVariants({
|
||||
color: "outline",
|
||||
})}
|
||||
href="/"
|
||||
>
|
||||
Return to Docs
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
7
src/app/api/search/route.ts
Normal file
7
src/app/api/search/route.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { source } from "@/lib/source";
|
||||
import { createFromSource } from "fumadocs-core/search/server";
|
||||
|
||||
// it should be cached forever
|
||||
export const revalidate = false;
|
||||
|
||||
export const { staticGET: GET } = createFromSource(source);
|
BIN
src/app/apple-icon.png
Normal file
BIN
src/app/apple-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
97
src/app/docs-og/[...slug]/route.tsx
Normal file
97
src/app/docs-og/[...slug]/route.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { metadataImage } from "@/lib/metadata";
|
||||
import { ImageResponse } from "next/og";
|
||||
|
||||
export const GET = metadataImage.createAPI(async (page) => {
|
||||
const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
background: "#2E2E2E",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
color: "#F2F0E3",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
paddingLeft: "80px",
|
||||
paddingRight: "80px",
|
||||
}}
|
||||
>
|
||||
<div style={{ position: "absolute", left: "80px", top: "60px" }}>
|
||||
zen docs
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: "bolder",
|
||||
fontSize: "52px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
{page.data.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
color: "#A9A79B",
|
||||
fontSize: "36px",
|
||||
}}
|
||||
>
|
||||
{page.data.description}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "-225px",
|
||||
right: "-225px",
|
||||
height: "450px",
|
||||
width: "450px",
|
||||
}}
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M32 44.3077C38.7974 44.3077 44.3077 38.7974 44.3077 32C44.3077 25.2027 38.7974 19.6923 32 19.6923C25.2027 19.6923 19.6923 25.2027 19.6923 32C19.6923 38.7974 25.2027 44.3077 32 44.3077ZM41.8462 32C41.8462 37.4379 37.4379 41.8462 32 41.8462C26.5621 41.8462 22.1538 37.4379 22.1538 32C22.1538 26.5621 26.5621 22.1538 32 22.1538C37.4379 22.1538 41.8462 26.5621 41.8462 32Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M53.3333 32C53.3333 43.7821 43.7821 53.3333 32 53.3333C20.2179 53.3333 10.6667 43.7821 10.6667 32C10.6667 20.2179 20.2179 10.6667 32 10.6667C43.7821 10.6667 53.3333 20.2179 53.3333 32ZM32 49.2308C41.5163 49.2308 49.2308 41.5163 49.2308 32C49.2308 22.4837 41.5163 14.7692 32 14.7692C22.4837 14.7692 14.7692 22.4837 14.7692 32C14.7692 41.5163 22.4837 49.2308 32 49.2308Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M64 32C64 49.6731 49.6731 64 32 64C14.3269 64 0 49.6731 0 32C0 14.3269 14.3269 0 32 0C49.6731 0 64 14.3269 64 32ZM32 58.2564C46.501 58.2564 58.2564 46.501 58.2564 32C58.2564 17.499 46.501 5.74359 32 5.74359C17.499 5.74359 5.74359 17.499 5.74359 32C5.74359 46.501 17.499 58.2564 32 58.2564Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export function generateStaticParams() {
|
||||
return metadataImage.generateParams();
|
||||
}
|
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
60
src/app/global.css
Normal file
60
src/app/global.css
Normal file
|
@ -0,0 +1,60 @@
|
|||
@import 'tailwindcss';
|
||||
@import 'fumadocs-ui/css/neutral.css';
|
||||
@import 'fumadocs-ui/css/preset.css';
|
||||
|
||||
@source '../../node_modules/fumadocs-ui/dist/**/*.js';
|
||||
|
||||
:root {
|
||||
--color-fd-background: #F2F0E3;
|
||||
--color-fd-foreground: #2E2E2E;
|
||||
--color-fd-primary: #F76F53;
|
||||
--color-fd-accent: #E6E4D7;
|
||||
--color-fd-accent-foreground: #2E2E2E;
|
||||
--color-fd-card: #F7F6EE;
|
||||
--color-fd-card-foreground: #2E2E2E;
|
||||
--color-fd-muted: #2e2e2e0b;
|
||||
--color-fd-muted-foreground: #2e2e2ec5;
|
||||
--color-fd-secondary: #E6E4D7;
|
||||
--color-fd-secondary-foreground: #2E2E2E;
|
||||
--color-fd-popover: #F2F0E3;
|
||||
--color-fd-popover-foreground: #2E2E2E;
|
||||
--color-fd-border: #EAE9E2;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-fd-background: #1F1F1F;
|
||||
--color-fd-foreground: #D1CFC0;
|
||||
--color-fd-primary: #F76F53;
|
||||
--color-fd-accent: #363636;
|
||||
--color-fd-accent-foreground: #D1CFC0;
|
||||
--color-fd-card: #161616;
|
||||
--color-fd-card-foreground: #D1CFC0;
|
||||
--color-fd-muted: #d1cfc00b;
|
||||
--color-fd-muted-foreground: #D1CFC0c5;
|
||||
--color-fd-secondary: #363636;
|
||||
--color-fd-secondary-foreground: #2E2E2E;
|
||||
--color-fd-popover: #1F1F1F;
|
||||
--color-fd-popover-foreground: #D1CFC0;
|
||||
--color-fd-border: #2E2E2E;
|
||||
}
|
||||
|
||||
aside {
|
||||
background-color: var(--color-fd-background) !important;
|
||||
}
|
||||
|
||||
aside a[data-active="true"] {
|
||||
color: var(--color-fd-foreground) !important;
|
||||
background-color: var(--color-fd-accent) !important;
|
||||
}
|
||||
|
||||
.prose :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
g.main:has(> g.plot) > rect {
|
||||
display: none;
|
||||
}
|
||||
|
||||
video {
|
||||
border-radius: 40px;
|
||||
}
|
3
src/app/icon0.svg
Normal file
3
src/app/icon0.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="32" height="32"><svg color="#F76F53" width="32" height="32" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M32 44.3077C38.7974 44.3077 44.3077 38.7974 44.3077 32C44.3077 25.2027 38.7974 19.6923 32 19.6923C25.2027 19.6923 19.6923 25.2027 19.6923 32C19.6923 38.7974 25.2027 44.3077 32 44.3077ZM41.8462 32C41.8462 37.4379 37.4379 41.8462 32 41.8462C26.5621 41.8462 22.1538 37.4379 22.1538 32C22.1538 26.5621 26.5621 22.1538 32 22.1538C37.4379 22.1538 41.8462 26.5621 41.8462 32Z" fill="currentColor"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M53.3333 32C53.3333 43.7821 43.7821 53.3333 32 53.3333C20.2179 53.3333 10.6667 43.7821 10.6667 32C10.6667 20.2179 20.2179 10.6667 32 10.6667C43.7821 10.6667 53.3333 20.2179 53.3333 32ZM32 49.2308C41.5163 49.2308 49.2308 41.5163 49.2308 32C49.2308 22.4837 41.5163 14.7692 32 14.7692C22.4837 14.7692 14.7692 22.4837 14.7692 32C14.7692 41.5163 22.4837 49.2308 32 49.2308Z" fill="currentColor"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M64 32C64 49.6731 49.6731 64 32 64C14.3269 64 0 49.6731 0 32C0 14.3269 14.3269 0 32 0C49.6731 0 64 14.3269 64 32ZM32 58.2564C46.501 58.2564 58.2564 46.501 58.2564 32C58.2564 17.499 46.501 5.74359 32 5.74359C17.499 5.74359 5.74359 17.499 5.74359 32C5.74359 46.501 17.499 58.2564 32 58.2564Z" fill="currentColor"></path> </svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
|
||||
@media (prefers-color-scheme: dark) { :root { filter: none; } }
|
||||
</style></svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/app/icon1.png
Normal file
BIN
src/app/icon1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
27
src/app/layout.config.tsx
Normal file
27
src/app/layout.config.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
|
||||
import Image from "next/image";
|
||||
|
||||
/**
|
||||
* Shared layout configurations
|
||||
*
|
||||
* you can customise layouts individually from:
|
||||
* Home Layout: app/(home)/layout.tsx
|
||||
* Docs Layout: app/docs/layout.tsx
|
||||
*/
|
||||
export const baseOptions: BaseLayoutProps = {
|
||||
nav: {
|
||||
title: (
|
||||
<>
|
||||
<Image
|
||||
width="24"
|
||||
height="24"
|
||||
src="/icon.svg"
|
||||
aria-label="Logo"
|
||||
alt="Logo"
|
||||
/>
|
||||
zen docs
|
||||
</>
|
||||
),
|
||||
},
|
||||
githubUrl: "https://github.com/zen-browser/docs",
|
||||
};
|
26
src/app/layout.tsx
Normal file
26
src/app/layout.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import "./global.css";
|
||||
import { RootProvider } from "fumadocs-ui/provider";
|
||||
import { Inter } from "next/font/google";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={inter.className} suppressHydrationWarning>
|
||||
<body className="flex flex-col min-h-screen">
|
||||
<RootProvider
|
||||
search={{
|
||||
options: {
|
||||
type: "static",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
43
src/app/llms.txt/route.ts
Normal file
43
src/app/llms.txt/route.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import * as fs from "node:fs/promises";
|
||||
import fg from "fast-glob";
|
||||
import matter from "gray-matter";
|
||||
import { remark } from "remark";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import remarkMdx from "remark-mdx";
|
||||
import { remarkInclude } from "fumadocs-mdx/config";
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
const processor = remark()
|
||||
.use(remarkMdx)
|
||||
// https://fumadocs.vercel.app/docs/mdx/include
|
||||
.use(remarkInclude)
|
||||
// gfm styles
|
||||
.use(remarkGfm)
|
||||
// .use(your remark plugins)
|
||||
.use(remarkStringify); // to string
|
||||
|
||||
export async function GET() {
|
||||
// all scanned content
|
||||
const files = await fg(["./content/docs/**/*.mdx"]);
|
||||
|
||||
const scan = files.map(async (file) => {
|
||||
const fileContent = await fs.readFile(file);
|
||||
const { content, data } = matter(fileContent.toString());
|
||||
|
||||
const processed = await processor.process({
|
||||
path: file,
|
||||
value: content,
|
||||
});
|
||||
|
||||
return `file: ${file}
|
||||
meta: ${JSON.stringify(data, null, 2)}
|
||||
|
||||
${processed}`;
|
||||
});
|
||||
|
||||
const scanned = await Promise.all(scan);
|
||||
|
||||
return new Response(scanned.join("\n\n"));
|
||||
}
|
21
src/app/manifest.json
Normal file
21
src/app/manifest.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "Zen Docs",
|
||||
"short_name": "Zen Docs",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#F2F0E3",
|
||||
"background_color": "#F2F0E3",
|
||||
"display": "standalone"
|
||||
}
|
4
src/app/robots.txt
Normal file
4
src/app/robots.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
User-Agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://docs.zen-browser.app/sitemap.xml
|
60
src/app/sitemap.ts
Normal file
60
src/app/sitemap.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
import { source } from "@/lib/source";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
interface TreeNode {
|
||||
$id: string;
|
||||
type?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
index?: TreeNode;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
function generateSitemap(
|
||||
root: TreeNode,
|
||||
baseUrl: string = "https://docs.zen-browser.app"
|
||||
): MetadataRoute.Sitemap {
|
||||
const sitemap: MetadataRoute.Sitemap = [];
|
||||
|
||||
function traverse(node: TreeNode): void {
|
||||
if (!node) return;
|
||||
|
||||
// If the node is a page and has a URL, add it to the sitemap.
|
||||
if (node.type === "page" && node.url) {
|
||||
// Concatenate baseUrl with the node URL.
|
||||
const fullUrl = baseUrl + (node.url === "/" ? "" : node.url);
|
||||
sitemap.push({
|
||||
url: fullUrl,
|
||||
// Use 'monthly' for the homepage and 'monthly' for other pages (customize as needed)
|
||||
changeFrequency: node.url === "/" ? "monthly" : "monthly",
|
||||
// Set a higher priority for the homepage
|
||||
priority: node.url === "/" ? 1 : 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
// If the node is a folder and has an index page, add that index page to the sitemap.
|
||||
if (node.type === "folder" && node.index && node.index.url) {
|
||||
const fullUrl = baseUrl + (node.index.url === "/" ? "" : node.index.url);
|
||||
sitemap.push({
|
||||
url: fullUrl,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: node.index.url === "/" ? "monthly" : "monthly",
|
||||
priority: node.index.url === "/" ? 1 : 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively process children if any.
|
||||
if (node.children && node.children.length) {
|
||||
node.children.forEach((child) => traverse(child));
|
||||
}
|
||||
}
|
||||
|
||||
traverse(root);
|
||||
return sitemap;
|
||||
}
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return generateSitemap(source.pageTree as TreeNode);
|
||||
}
|
7
src/lib/metadata.ts
Normal file
7
src/lib/metadata.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { createMetadataImage } from "fumadocs-core/server";
|
||||
import { source } from "@/lib/source";
|
||||
|
||||
export const metadataImage = createMetadataImage({
|
||||
imageRoute: "/docs-og",
|
||||
source,
|
||||
});
|
9
src/lib/source.ts
Normal file
9
src/lib/source.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { docs } from "@/.source";
|
||||
import { loader } from "fumadocs-core/source";
|
||||
|
||||
// `loader()` also assign a URL to your pages
|
||||
// See https://fumadocs.vercel.app/docs/headless/source-api for more info
|
||||
export const source = loader({
|
||||
baseUrl: "/",
|
||||
source: docs.toFumadocsSource(),
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue