mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-03 07:21:13 +08:00
feat(docs): reorganize documentation structure and update navigation links
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { HomeLayout } from 'fumadocs-ui/layouts/home';
|
||||
import { baseOptions } from '@/lib/layout.shared';
|
||||
|
||||
export default function Layout({ children }: LayoutProps<'/'>) {
|
||||
return <HomeLayout {...baseOptions()}>{children}</HomeLayout>;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import Link from 'next/link';
|
||||
import { ArrowUpRight, BookOpen, Rocket } from 'lucide-react';
|
||||
import { appName, gitConfig } from '@/lib/shared';
|
||||
|
||||
const githubUrl = `https://github.com/${gitConfig.user}/${gitConfig.repo}`;
|
||||
const demoUrl = 'https://infinite-canvas-cpco.onrender.com/';
|
||||
const starHistoryUrl = `https://www.star-history.com/?repos=${gitConfig.user}%2F${gitConfig.repo}&type=date`;
|
||||
const starHistoryChart = `https://api.star-history.com/chart?repos=${gitConfig.user}/${gitConfig.repo}&type=date&transparent=true`;
|
||||
const darkStarHistoryChart = `${starHistoryChart}&theme=dark`;
|
||||
|
||||
const previewImages = [
|
||||
{
|
||||
src: 'https://i.ibb.co/TDFvGWDT/image.png',
|
||||
title: '画布编排',
|
||||
},
|
||||
{
|
||||
src: 'https://i.ibb.co/zVwJq3YS/image.png',
|
||||
title: '图片生成',
|
||||
},
|
||||
{
|
||||
src: 'https://i.ibb.co/PvY3qhhK/image.png',
|
||||
title: '参考图编辑',
|
||||
},
|
||||
{
|
||||
src: 'https://i.ibb.co/7D04LwN/image.png',
|
||||
title: '节点工作流',
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="mx-auto flex w-full max-w-6xl flex-1 flex-col px-5 pb-16 pt-8 md:px-10 md:pt-14">
|
||||
<section className="grid min-h-[520px] items-center gap-10 border-b border-zinc-200 pb-12 dark:border-zinc-800 lg:grid-cols-[0.88fr_1.12fr]">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 text-xs font-medium text-zinc-500 dark:text-zinc-400">
|
||||
<Rocket className="size-3.5 text-emerald-600 dark:text-emerald-400" />
|
||||
开源 AI 图片创作工作台
|
||||
</div>
|
||||
<h1 className="mt-6 max-w-3xl text-4xl font-semibold leading-tight text-zinc-950 dark:text-zinc-50 md:text-6xl [font-family:var(--font-display)]">
|
||||
{appName}
|
||||
<span className="block text-zinc-500 dark:text-zinc-400">文档中心</span>
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-base leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
面向图片创作的无限画布,把画布编排、AI 生成、参考图编辑、提示词库和素材沉淀放在同一个工作流里。
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Link
|
||||
href="/docs/overview/quick-start"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-full bg-zinc-950 px-5 py-3 text-sm font-medium text-white transition hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-950 dark:hover:bg-zinc-200"
|
||||
>
|
||||
<BookOpen className="size-4" />
|
||||
快速开始
|
||||
</Link>
|
||||
<a
|
||||
href={githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-full border border-zinc-300 px-5 py-3 text-sm font-medium text-zinc-900 transition hover:border-zinc-900 hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-100 dark:hover:border-zinc-500 dark:hover:bg-zinc-900"
|
||||
>
|
||||
<img src="/github.svg" alt="" className="size-4" />
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href={demoUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-full border border-zinc-300 px-5 py-3 text-sm font-medium text-zinc-900 transition hover:border-zinc-900 hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-100 dark:hover:border-zinc-500 dark:hover:bg-zinc-900"
|
||||
>
|
||||
在线体验
|
||||
<ArrowUpRight className="size-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-2xl lg:w-[108%] lg:max-w-none">
|
||||
<img
|
||||
src={previewImages[3].src}
|
||||
alt="无限画布效果图"
|
||||
className="aspect-[16/10] w-full rounded-xl object-cover"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-14">
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-zinc-950 dark:text-zinc-50 md:text-3xl">
|
||||
效果展示
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
href="/docs/overview/features"
|
||||
className="inline-flex w-fit items-center gap-1.5 text-sm font-medium text-zinc-800 transition hover:text-zinc-950 dark:text-zinc-200 dark:hover:text-white"
|
||||
>
|
||||
功能介绍
|
||||
<ArrowUpRight className="size-4" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{previewImages.map((item) => (
|
||||
<img
|
||||
key={item.src}
|
||||
src={item.src}
|
||||
alt={`${item.title}效果图`}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="aspect-[16/10] w-full rounded-2xl object-cover"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mx-auto mt-16 w-full max-w-4xl text-center">
|
||||
<h2 className="text-2xl font-semibold text-zinc-950 dark:text-zinc-50 md:text-3xl">
|
||||
开发贡献者
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
感谢所有为本项目做出贡献的开发者
|
||||
</p>
|
||||
<div className="mt-7 flex justify-center">
|
||||
<a
|
||||
href={`${githubUrl}/graphs/contributors`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="inline-flex max-w-full"
|
||||
>
|
||||
<img
|
||||
src={`https://contrib.rocks/image?repo=${gitConfig.user}/${gitConfig.repo}`}
|
||||
alt="开发贡献者头像"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="max-w-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mx-auto mt-16 w-full max-w-5xl text-center">
|
||||
<h2 className="text-2xl font-semibold text-zinc-950 dark:text-zinc-50 md:text-3xl">
|
||||
Star History
|
||||
</h2>
|
||||
<div className="mt-7 flex justify-center">
|
||||
<a
|
||||
href={starHistoryUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="block w-full max-w-4xl"
|
||||
>
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet={darkStarHistoryChart}
|
||||
/>
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcSet={starHistoryChart}
|
||||
/>
|
||||
<img
|
||||
src={starHistoryChart}
|
||||
alt="Star History Chart"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="mx-auto w-full"
|
||||
/>
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { source } from '@/lib/source';
|
||||
import { createFromSource } from 'fumadocs-core/search/server';
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export const { staticGET: GET } = createFromSource(source, {
|
||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
||||
language: 'english',
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { DocPageContent, getDocPageMetadata } from '@/lib/doc-page';
|
||||
import { source } from '@/lib/source';
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default async function Page(props: PageProps<'/docs/[...slug]'>) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return <DocPageContent page={page} />;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: PageProps<'/docs/[...slug]'>): Promise<Metadata> {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return getDocPageMetadata(page);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { source } from '@/lib/source';
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||
import { baseOptions } from '@/lib/layout.shared';
|
||||
import { DocsTopTabs } from '@/components/docs-top-tabs';
|
||||
|
||||
export default function Layout({ children }: LayoutProps<'/docs'>) {
|
||||
return (
|
||||
<DocsLayout {...baseOptions()} tree={source.getPageTree()} tabs={false}>
|
||||
<DocsTopTabs />
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
|
||||
import { Markdown } from 'fumadocs-core/content/md';
|
||||
import { getTableOfContents } from 'fumadocs-core/content/toc';
|
||||
import { remarkHeading } from 'fumadocs-core/mdx-plugins';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import type { Metadata } from 'next';
|
||||
import { getMDXComponents } from '@/components/mdx';
|
||||
|
||||
const title = '无限画布文档';
|
||||
const description = '功能说明、操作手册、部署方式、开发文档、商务合作与赞助支持';
|
||||
|
||||
async function readDocsIndex() {
|
||||
return readFile(join(process.cwd(), 'index.md'), 'utf8');
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const content = await readDocsIndex();
|
||||
const toc = getTableOfContents(content);
|
||||
|
||||
return (
|
||||
<DocsPage toc={toc}>
|
||||
<DocsTitle>{title}</DocsTitle>
|
||||
<DocsDescription>{description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<Markdown components={getMDXComponents()} remarkPlugins={[remarkHeading]}>
|
||||
{content}
|
||||
</Markdown>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
|
||||
import { Markdown } from 'fumadocs-core/content/md';
|
||||
import { getTableOfContents } from 'fumadocs-core/content/toc';
|
||||
import { remarkHeading } from 'fumadocs-core/mdx-plugins';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import type { Metadata } from 'next';
|
||||
import { getMDXComponents } from '@/components/mdx';
|
||||
|
||||
const title = '更新日志';
|
||||
const description = '项目版本变更记录';
|
||||
|
||||
async function readChangelog() {
|
||||
return readFile(join(process.cwd(), '..', 'CHANGELOG.md'), 'utf8');
|
||||
}
|
||||
|
||||
export default async function ChangelogPage() {
|
||||
const changelog = await readChangelog();
|
||||
const toc = getTableOfContents(changelog);
|
||||
|
||||
return (
|
||||
<DocsPage toc={toc}>
|
||||
<DocsTitle>{title}</DocsTitle>
|
||||
<DocsDescription>{description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<Markdown components={getMDXComponents()} remarkPlugins={[remarkHeading]}>
|
||||
{changelog}
|
||||
</Markdown>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'fumadocs-ui/css/neutral.css';
|
||||
@import 'fumadocs-ui/css/preset.css';
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
html > body[data-scroll-locked] {
|
||||
margin-right: 0px !important;
|
||||
--removed-body-scroll-bar-size: 0px !important;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M32 8L58 54H46L32 29L18 54H6L32 8Z" fill="currentColor"/>
|
||||
<path d="M32 40L40 54H24L32 40Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,29 @@
|
||||
import { Noto_Sans_SC, Sora } from 'next/font/google';
|
||||
import { Provider } from '@/components/provider';
|
||||
import './global.css';
|
||||
|
||||
const bodyFont = Noto_Sans_SC({
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '500', '600'],
|
||||
variable: '--font-body',
|
||||
});
|
||||
|
||||
const displayFont = Sora({
|
||||
subsets: ['latin'],
|
||||
weight: ['500', '600', '700'],
|
||||
variable: '--font-display',
|
||||
});
|
||||
|
||||
export default function Layout({ children }: LayoutProps<'/'>) {
|
||||
return (
|
||||
<html
|
||||
lang="zh-CN"
|
||||
className={`${bodyFont.className} ${bodyFont.variable} ${displayFont.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body className="flex flex-col min-h-screen">
|
||||
<Provider>{children}</Provider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getLLMText, source } from '@/lib/source';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET() {
|
||||
const docsIndex = await readFile(join(process.cwd(), 'index.md'), 'utf8');
|
||||
const scan = source.getPages().map(getLLMText);
|
||||
const scanned = await Promise.all(scan);
|
||||
|
||||
return new Response([docsIndex, ...scanned].join('\n\n'));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getLLMText, getPageMarkdownUrl, source } from '@/lib/source';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/docs/[[...slug]]'>) {
|
||||
const { slug } = await params;
|
||||
// remove the appended "content.md"
|
||||
const page = source.getPage(slug?.slice(0, -1));
|
||||
if (!page) notFound();
|
||||
|
||||
return new Response(await getLLMText(page), {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.getPages().map((page) => ({
|
||||
slug: getPageMarkdownUrl(page).segments,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { source } from '@/lib/source';
|
||||
import { llms } from 'fumadocs-core/source';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET() {
|
||||
const docsIndex = await readFile(join(process.cwd(), 'index.md'), 'utf8');
|
||||
return new Response([docsIndex, llms(source).index()].join('\n\n'));
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getPageImage, source } from '@/lib/source';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { generate as DefaultImage } from 'fumadocs-ui/og';
|
||||
import { appName } from '@/lib/shared';
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) {
|
||||
const { slug } = await params;
|
||||
const page = source.getPage(slug.slice(0, -1));
|
||||
if (!page) notFound();
|
||||
|
||||
return new ImageResponse(
|
||||
<DefaultImage title={page.data.title} description={page.data.description} site={appName} />,
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.getPages().map((page) => ({
|
||||
lang: page.locale,
|
||||
slug: getPageImage(page).segments,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user