sync
This commit is contained in:
parent
20af1f5e79
commit
fe3b2e8f90
51 changed files with 33307 additions and 15 deletions
55
packages/www/src/components/mdx.tsx
Normal file
55
packages/www/src/components/mdx.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { Children, isValidElement, type ComponentProps, type ReactNode } from "react"
|
||||
import { Callout } from "fumadocs-ui/components/callout"
|
||||
import { Card, Cards } from "fumadocs-ui/components/card"
|
||||
import { Tab as FumadocsTab, Tabs as FumadocsTabs } from "fumadocs-ui/components/tabs"
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx"
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
|
||||
function Tip(props: { children: ReactNode }) {
|
||||
return <Callout type="idea">{props.children}</Callout>
|
||||
}
|
||||
|
||||
function Note(props: { children: ReactNode }) {
|
||||
return <Callout>{props.children}</Callout>
|
||||
}
|
||||
|
||||
function Warning(props: { children: ReactNode }) {
|
||||
return <Callout type="warn">{props.children}</Callout>
|
||||
}
|
||||
|
||||
function Tab(props: { title?: string; value?: string; children: ReactNode }) {
|
||||
return <FumadocsTab value={props.value ?? props.title}>{props.children}</FumadocsTab>
|
||||
}
|
||||
|
||||
function Tabs(props: { children: ReactNode }) {
|
||||
const items = Children.toArray(props.children).flatMap((child) => {
|
||||
if (!isValidElement<ComponentProps<typeof Tab>>(child)) return []
|
||||
const value = child.props.value ?? child.props.title
|
||||
return value ? [value] : []
|
||||
})
|
||||
return <FumadocsTabs items={items}>{props.children}</FumadocsTabs>
|
||||
}
|
||||
|
||||
function CardGroup(props: { children: ReactNode; cols?: number }) {
|
||||
return <Cards>{props.children}</Cards>
|
||||
}
|
||||
|
||||
export function getMdxComponents(components?: MDXComponents) {
|
||||
return {
|
||||
...defaultMdxComponents,
|
||||
Tip,
|
||||
Note,
|
||||
Warning,
|
||||
Tabs,
|
||||
Tab,
|
||||
CardGroup,
|
||||
Card,
|
||||
...components,
|
||||
} satisfies MDXComponents
|
||||
}
|
||||
|
||||
export const useMDXComponents = getMdxComponents
|
||||
|
||||
declare global {
|
||||
type MDXProvidedComponents = ReturnType<typeof getMdxComponents>
|
||||
}
|
||||
22
packages/www/src/lib/layout.tsx
Normal file
22
packages/www/src/lib/layout.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"
|
||||
|
||||
export function baseOptions(): BaseLayoutProps {
|
||||
return {
|
||||
nav: {
|
||||
title: "OpenCode",
|
||||
url: "/",
|
||||
},
|
||||
links: [
|
||||
{
|
||||
text: "Docs",
|
||||
url: "/docs",
|
||||
active: "nested-url",
|
||||
},
|
||||
{
|
||||
text: "GitHub",
|
||||
url: "https://github.com/anomalyco/opencode",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
7
packages/www/src/lib/source.ts
Normal file
7
packages/www/src/lib/source.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { docs } from "collections/server"
|
||||
import { loader } from "fumadocs-core/source"
|
||||
|
||||
export const source = loader({
|
||||
baseUrl: "/docs",
|
||||
source: docs.toFumadocsSource(),
|
||||
})
|
||||
104
packages/www/src/routeTree.gen.ts
Normal file
104
packages/www/src/routeTree.gen.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
||||
import { Route as ApiSearchRouteImport } from './routes/api/search'
|
||||
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DocsSplatRoute = DocsSplatRouteImport.update({
|
||||
id: '/docs/$',
|
||||
path: '/docs/$',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ApiSearchRoute = ApiSearchRouteImport.update({
|
||||
id: '/api/search',
|
||||
path: '/api/search',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/api/search': typeof ApiSearchRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/api/search': typeof ApiSearchRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/api/search': typeof ApiSearchRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/api/search' | '/docs/$'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/api/search' | '/docs/$'
|
||||
id: '__root__' | '/' | '/api/search' | '/docs/$'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ApiSearchRoute: typeof ApiSearchRoute
|
||||
DocsSplatRoute: typeof DocsSplatRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/docs/$': {
|
||||
id: '/docs/$'
|
||||
path: '/docs/$'
|
||||
fullPath: '/docs/$'
|
||||
preLoaderRoute: typeof DocsSplatRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/api/search': {
|
||||
id: '/api/search'
|
||||
path: '/api/search'
|
||||
fullPath: '/api/search'
|
||||
preLoaderRoute: typeof ApiSearchRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
ApiSearchRoute: ApiSearchRoute,
|
||||
DocsSplatRoute: DocsSplatRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
16
packages/www/src/router.tsx
Normal file
16
packages/www/src/router.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { createRouter } from "@tanstack/react-router"
|
||||
import { routeTree } from "./routeTree.gen"
|
||||
|
||||
export function getRouter() {
|
||||
return createRouter({
|
||||
routeTree,
|
||||
scrollRestoration: true,
|
||||
defaultPreload: "intent",
|
||||
})
|
||||
}
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: ReturnType<typeof getRouter>
|
||||
}
|
||||
}
|
||||
34
packages/www/src/routes/__root.tsx
Normal file
34
packages/www/src/routes/__root.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"
|
||||
import { RootProvider } from "fumadocs-ui/provider/tanstack"
|
||||
import type { ReactNode } from "react"
|
||||
import styles from "../styles.css?url"
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ charSet: "utf-8" },
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||
{ title: "OpenCode" },
|
||||
{
|
||||
name: "description",
|
||||
content: "The open source AI coding agent.",
|
||||
},
|
||||
],
|
||||
links: [{ rel: "stylesheet", href: styles }],
|
||||
}),
|
||||
shellComponent: RootDocument,
|
||||
})
|
||||
|
||||
function RootDocument(props: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
<RootProvider>{props.children}</RootProvider>
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
15
packages/www/src/routes/api/search.ts
Normal file
15
packages/www/src/routes/api/search.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { createFromSource } from "fumadocs-core/search/server"
|
||||
import { source } from "@/lib/source"
|
||||
|
||||
const search = createFromSource(source, {
|
||||
language: "english",
|
||||
})
|
||||
|
||||
export const Route = createFileRoute("/api/search")({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: ({ request }) => search.GET(request),
|
||||
},
|
||||
},
|
||||
})
|
||||
62
packages/www/src/routes/docs/$.tsx
Normal file
62
packages/www/src/routes/docs/$.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { createFileRoute, notFound } from "@tanstack/react-router"
|
||||
import { createServerFn } from "@tanstack/react-start"
|
||||
import browserCollections from "collections/browser"
|
||||
import { useFumadocsLoader } from "fumadocs-core/source/client"
|
||||
import { DocsLayout } from "fumadocs-ui/layouts/docs"
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page"
|
||||
import { Suspense } from "react"
|
||||
import { getMdxComponents } from "@/components/mdx"
|
||||
import { baseOptions } from "@/lib/layout"
|
||||
import { source } from "@/lib/source"
|
||||
|
||||
export const Route = createFileRoute("/docs/$")({
|
||||
loader: async ({ params }) => {
|
||||
const slugs = params._splat?.split("/").filter(Boolean) ?? []
|
||||
const data = await loadPage({ data: slugs })
|
||||
await clientLoader.preload(data.path)
|
||||
return data
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{ title: `${loaderData?.title ?? "Docs"} | OpenCode` },
|
||||
{ name: "description", content: loaderData?.description ?? "OpenCode documentation." },
|
||||
],
|
||||
}),
|
||||
component: Documentation,
|
||||
})
|
||||
|
||||
const loadPage = createServerFn({ method: "GET" })
|
||||
.validator((slugs: string[]) => slugs)
|
||||
.handler(async ({ data: slugs }) => {
|
||||
const page = source.getPage(slugs)
|
||||
if (!page) throw notFound()
|
||||
return {
|
||||
path: page.path,
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
tree: await source.serializePageTree(source.getPageTree()),
|
||||
}
|
||||
})
|
||||
|
||||
const clientLoader = browserCollections.docs.createClientLoader({
|
||||
component({ toc, frontmatter, default: Content }) {
|
||||
return (
|
||||
<DocsPage toc={toc}>
|
||||
<DocsTitle>{frontmatter.title}</DocsTitle>
|
||||
<DocsDescription>{frontmatter.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<Content components={getMdxComponents()} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
function Documentation() {
|
||||
const data = useFumadocsLoader(Route.useLoaderData())
|
||||
return (
|
||||
<DocsLayout {...baseOptions()} tree={data.tree} tabMode="top">
|
||||
<Suspense>{clientLoader.useContent(data.path)}</Suspense>
|
||||
</DocsLayout>
|
||||
)
|
||||
}
|
||||
18
packages/www/src/routes/index.tsx
Normal file
18
packages/www/src/routes/index.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { createFileRoute } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: Home,
|
||||
})
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<main className="home">
|
||||
<p className="home-kicker">OpenCode</p>
|
||||
<h1>The open source AI coding agent</h1>
|
||||
<p className="home-copy">The new opencode.ai SSR site is running. The V2 documentation is available now.</p>
|
||||
<a className="home-link" href="/docs">
|
||||
Read the docs
|
||||
</a>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
79
packages/www/src/styles.css
Normal file
79
packages/www/src/styles.css
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
@import "tailwindcss";
|
||||
@import "fumadocs-ui/css/neutral.css";
|
||||
@import "fumadocs-ui/css/preset.css";
|
||||
|
||||
:root {
|
||||
--site-background: #f7f7f5;
|
||||
--site-foreground: #171717;
|
||||
--site-border: #d8d8d2;
|
||||
--site-accent: #3b7dd8;
|
||||
--fd-layout-width: 90rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
align-content: center;
|
||||
justify-items: start;
|
||||
gap: 1.5rem;
|
||||
padding: clamp(1.5rem, 7vw, 7rem);
|
||||
color: var(--site-foreground);
|
||||
background: var(--site-background);
|
||||
}
|
||||
|
||||
.home-kicker,
|
||||
.home-copy,
|
||||
.home h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.home-kicker {
|
||||
font:
|
||||
600 0.75rem/1.2 ui-monospace,
|
||||
monospace;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.home h1 {
|
||||
max-width: 13ch;
|
||||
font:
|
||||
500 clamp(3rem, 9vw, 8rem) / 0.92 ui-monospace,
|
||||
monospace;
|
||||
letter-spacing: -0.07em;
|
||||
}
|
||||
|
||||
.home-copy {
|
||||
max-width: 42rem;
|
||||
font:
|
||||
400 clamp(1rem, 2vw, 1.25rem) / 1.5 ui-monospace,
|
||||
monospace;
|
||||
}
|
||||
|
||||
.home-link {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 1rem;
|
||||
color: var(--site-background);
|
||||
background: var(--site-foreground);
|
||||
font:
|
||||
500 0.875rem/1 ui-monospace,
|
||||
monospace;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.home-link:hover {
|
||||
background: var(--site-accent);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue