diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index c86671d..0000000 --- a/docs/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# docs - -This is a Waku application generated with [Create Fumadocs](https://github.com/fuma-nama/fumadocs). - -Run development server: - -```bash -npm run dev -# or -pnpm dev -# or -yarn dev -``` diff --git a/docs/biome.json b/docs/biome.json deleted file mode 100644 index 1381c95..0000000 --- a/docs/biome.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "ignoreUnknown": true, - "includes": [ - "**", - "!node_modules", - "!.source" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2 - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - }, - "domains": { - "react": "recommended" - } - }, - "assist": { - "actions": { - "source": { - "organizeImports": "on" - } - } - } -} \ No newline at end of file diff --git a/docs/content/02-structure.mdx b/docs/content/02-structure.mdx new file mode 100644 index 0000000..e69de29 diff --git a/docs/content/03-faq.mdx b/docs/content/03-faq.mdx new file mode 100644 index 0000000..605427c --- /dev/null +++ b/docs/content/03-faq.mdx @@ -0,0 +1,162 @@ +--- +title: FAQ +description: Common questions about Blume — how it compares to other documentation tools, and why a Markdown formatter might collapse your callout directives. +sidebar: + label: FAQ +--- + +Answers to questions that come up often. Missing one? [Open an issue](https://github.com/haydenbleasel/blume/issues) or ask the in-page assistant. + +## How is Blume different from Mintlify, Fumadocs, and others? + +Most documentation tools sit at one of two extremes. **Managed platforms** like Mintlify give you a polished result fast, but the build and hosting are their service — you author inside their system and deploy to their infrastructure. **Component libraries and starters** like Fumadocs, Nextra, or Docusaurus are open-source and flexible, but they hand you an application (a Next.js or React project) that you scaffold, wire up, and maintain before and after you write a word. + +Blume takes a third path: **the framework is the template.** You point it at a folder of Markdown and it generates and drives the whole site — navigation, search, theming, Open Graph images, SEO, and AI endpoints — with no app to own. It's fully open-source and self-hostable, so there's no managed service and no vendor lock-in, but there's also no boilerplate to maintain. + +| | Blume | Mintlify | Fumadocs / Nextra / Docusaurus | +| --- | --- | --- | --- | +| **Model** | Zero-config framework; content only | Hosted platform | Library + app you scaffold | +| **Source** | Open-source (MIT) | Closed core | Open-source | +| **Hosting** | Anywhere — static or a server function | Their managed infrastructure | Anywhere; you build and deploy | +| **You maintain** | Your Markdown | Your Markdown + platform config | Your Markdown + the app around it | +| **Rendering** | Astro; core theme ships zero client JS | Their runtime | React/Next.js runtime | +| **AI features** | `llms.txt`, raw Markdown, Ask AI, MCP — built in, no hosted service | Built in (hosted) | Bring your own | + +A few consequences worth calling out: + +- **You own the output.** `blume build` produces a plain site you host on Vercel, Netlify, Cloudflare, S3, or your own box. Nothing phones home. +- **No lock-in, two ways out.** Your content is portable Markdown, and `blume eject` turns the project into a standalone Astro app that still uses the `blume` package when you want full control. +- **Fast by default.** The core theme is React-free and renders static HTML, so pages score well on Core Web Vitals without tuning. You opt into server features (Ask AI, MCP) only when you need them. +- **Type-safe configuration.** `blume.config.ts` and every `meta.ts` are real TypeScript validated by a schema — not loosely-typed YAML. + +:::note +This isn't "better than everything" — managed platforms and full frameworks are the right call when you want a hosted product or maximum control over the app. Blume is for teams who want a production-grade docs site without owning either the platform or the plumbing. +::: + +See [Why Blume exists](/docs) for the longer version. + +## Is Blume free and open-source? + +Yes — Blume is MIT-licensed and free. You install the `blume` package, keep your content in your own repository, and host the build wherever you like. There's no paid tier, no per-seat pricing, and no account to sign up for. The source lives on [GitHub](https://github.com/haydenbleasel/blume). + +## Do I need to know Astro, React, or Tailwind? + +No. A folder of Markdown is a complete site — navigation, search, and theming are inferred or set with a handful of tokens. You only reach for the underlying stack when you want to customize: [interactive islands](/docs/content/islands) (React), [component overrides](/docs/configuration/customization), or [theme tokens](/docs/configuration/theming) (Tailwind). Even then, [`blume.config.ts`](/docs/configuration) is typed, so your editor guides you. + +## Can I use React components and MDX? + +Yes. Any page can be `.md` or `.mdx`, and MDX lets you drop in the [built-in components](/docs/content/components) with no imports. You can also add your own `.tsx`/`.jsx` [islands](/docs/content/islands) — Blume auto-enables React only for the pages that use them, so the core theme stays JavaScript-free everywhere else. + +## Where can I deploy it? + +Anywhere. `blume build` outputs static HTML by default, which you can serve from any static host or CDN — Vercel, Netlify, Cloudflare Pages, GitHub Pages, S3, or your own server. Server-only features (Ask AI, the MCP server, on-demand rendering) switch the build to a server function through an adapter for Vercel, Node, Netlify, or Cloudflare. See [Deployment](/docs/deployment). + +## Does search need a hosted service? + +No. [Orama](/docs/configuration/search) builds a local index that works in both dev and production with nothing to host or pay for. For very large sites, [Pagefind](/docs/configuration/search) is one flag away. Either way the index ships as part of your site. + +## How do I customize the look? + +Start with [theme tokens](/docs/configuration/theming) — accent color, fonts, radius, and a `theme.css` for anything else Tailwind can express. Go further by [overriding built-in components](/docs/configuration/customization) or adding [custom pages](/docs/configuration/customization#custom-pages). When you want the Astro project itself, [`blume eject`](/docs/reference/cli) hands you a standalone app that still uses the `blume` package. + +## Why is oxfmt / Ultracite collapsing my directives? + +If you format your Markdown with [Ultracite](https://www.ultracite.ai) (which runs oxlint + [oxfmt](https://oxc.rs)) — as Blume itself does — you may notice that container directives get flattened onto a single line after a format pass: + +```md +:::note +Regenerate the project with blume dev. +::: +``` + +becomes + +```md +:::note Regenerate the project with blume dev. ::: +``` + +Once the opening `:::note` fence is joined to the prose, it's no longer a directive, so it renders as literal text instead of a [callout](/docs/content/syntax#callouts). + +### Why it happens + +This is a bug in oxfmt's Markdown formatter (inherited from Prettier's Markdown printer — see [prettier/prettier#19040](https://github.com/prettier/prettier/pull/19040)). When it wraps prose, it treats the `:::` fence lines as ordinary text and joins them with the adjacent line, breaking the directive. It affects every container directive type — `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger`, `:::success`. + +We reported it upstream in [oxc-project/oxc#24096](https://github.com/oxc-project/oxc/issues/24096); until it's fixed there, the patch below is the workaround. + +### The fix + +Patch oxfmt so it preserves the line break that sits directly against a `:::` fence. Blume ships the same fix in its own repo, and you can apply it in any project. + +1. Save the patch as `patches/oxfmt@0.61.0.patch`: + + ```diff patches/oxfmt@0.61.0.patch + diff --git a/dist/markdown-ZuiQU4Xe.js b/dist/markdown-ZuiQU4Xe.js + index 566b9e6d27f36061d64b93736e238e871e1ee2b2..82d0595acc010807c2939fc4a1717dde887a8555 100644 + --- a/dist/markdown-ZuiQU4Xe.js + +++ b/dist/markdown-ZuiQU4Xe.js + @@ -4875,7 +4875,43 @@ function lu(e, t, r) { + case "sentence": return Oh(e, r); + case "word": return t.parser !== "mdx" ? zh(e, t) : Uh(e); + case "whitespace": { + - let { next: a } = e, u = a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !NE(e) && !(t.proseWrap === "preserve" && RE(e)) ? "never" : t.proseWrap; + + let { next: a, previous: oxfmtFencePrev } = e; + + // Preserve line breaks that sit directly against a `:::` container + + // directive fence, so `proseWrap: "never"` keeps the opening/closing + + // fence on their own lines instead of joining them into the prose (which + + // breaks the directive). Ordinary prose still wraps per proseWrap. + + // See prettier/prettier#19040. + + let oxfmtIsFence = (w) => w != null && typeof w.value === "string" && w.value.startsWith(":::"); + + // A titled directive (`:::warning[Heads up]`) parses its `[title]` as a + + // linkReference between two sentence nodes at the paragraph level: the + + // fence word ends the sentence before the reference, and the body's + + // leading newline opens the sentence after it. So when this whitespace + + // starts its sentence, climb to the paragraph and check whether the two + + // preceding siblings are a (link) reference and a sentence ending in a + + // `:::` fence word. + + let oxfmtPrevIsTitledFence = !1; + + if (oxfmtFencePrev == null && e.index === 0 && e.grandparent != null && Array.isArray(e.grandparent.children)) { + + let oxfmtSibs = e.grandparent.children, oxfmtSentIdx = oxfmtSibs.indexOf(e.parent); + + if (oxfmtSentIdx >= 2) { + + let oxfmtLink = oxfmtSibs[oxfmtSentIdx - 1], oxfmtBefore = oxfmtSibs[oxfmtSentIdx - 2]; + + let oxfmtLastWord = oxfmtBefore && oxfmtBefore.type === "sentence" && Array.isArray(oxfmtBefore.children) ? oxfmtBefore.children[oxfmtBefore.children.length - 1] : null; + + oxfmtPrevIsTitledFence = oxfmtLink != null && (oxfmtLink.type === "linkReference" || oxfmtLink.type === "link") && oxfmtIsFence(oxfmtLastWord); + + } + + } + + // The plain-markdown parser keeps a titled fence's `[title]` as literal + + // words, so the whole directive is one sentence. For a newline + + // whitespace, walk back to the start of its visual line within the + + // sentence; a line led by a `:::` word is a fence whose break must stay. + + if (!oxfmtPrevIsTitledFence && e.node.value.includes("\n") && e.parent != null && Array.isArray(e.parent.children)) { + + let oxfmtLineFirst = null; + + for (let oxfmtJ = e.index - 1; oxfmtJ >= 0; oxfmtJ--) { + + let oxfmtSib = e.parent.children[oxfmtJ]; + + if (oxfmtSib.type === "whitespace" && typeof oxfmtSib.value === "string" && oxfmtSib.value.includes("\n")) break; + + oxfmtLineFirst = oxfmtSib; + + } + + oxfmtPrevIsTitledFence = oxfmtIsFence(oxfmtLineFirst); + + } + + let u = oxfmtIsFence(oxfmtFencePrev) || oxfmtPrevIsTitledFence || oxfmtIsFence(a) ? "preserve" : a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !NE(e) && !(t.proseWrap === "preserve" && RE(e)) ? "never" : t.proseWrap; + return ou(e, n.value, u, !1, t); + } + case "emphasis": { + ``` + +2. Register it with your package manager's `patchedDependencies`. With Bun or pnpm, add to `package.json`: + + ```json package.json + { + "patchedDependencies": { + "oxfmt@0.61.0": "patches/oxfmt@0.61.0.patch" + } + } + ``` + +3. Reinstall so the patch is applied: + + ```package-install + bun install + ``` + +:::warning[Version-pinned] +The patch targets a specific oxfmt build — its diff references a file whose name is hashed per release (`dist/markdown-*.js`). When you bump oxfmt, regenerate the patch (e.g. `bun patch oxfmt`) or check whether the upstream fix has landed and the patch is no longer needed. +::: diff --git a/docs/content/coding/index.mdx b/docs/content/coding/index.mdx new file mode 100644 index 0000000..2907dd7 --- /dev/null +++ b/docs/content/coding/index.mdx @@ -0,0 +1,13 @@ +--- +title: 'Coding' +sidebar: + label: Overview + icon: arrow-right-circle + order: 1 +--- + +## Description + +Given two integer arrays `nums1` and `nums2`, return the minimum number of operations required to make both arrays equal. + +You have the following three choices to modify two arrays: diff --git a/docs/content/coding/two-pointers/index.mdx b/docs/content/coding/two-pointers/index.mdx new file mode 100644 index 0000000..9d83436 --- /dev/null +++ b/docs/content/coding/two-pointers/index.mdx @@ -0,0 +1,14 @@ +--- +title: 'Two Pointers' +sidebar: + label: Overview + icon: arrow-right-circle + badge: Easy + order: 1 +--- + +## Description + +Given two integer arrays `nums1` and `nums2`, return the minimum number of operations required to make both arrays equal. + +You have the following three choices to modify two arrays: diff --git a/docs/content/coding/two-pointers/problem.mdx b/docs/content/coding/two-pointers/problem.mdx new file mode 100644 index 0000000..e69de29 diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx deleted file mode 100644 index 6c70e42..0000000 --- a/docs/content/docs/index.mdx +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Hello World -description: Your favourite docs framework. -icon: Rocket ---- - -Hey there! Fumadocs is a docs framework that works on Waku! - -## Heading - -Hello World! - - - - - - -### CodeBlock - -```ts -console.log('Hello World'); -``` - -#### Table - -| Head | Description | -| ------------------------------- | ----------------------------------- | -| `hello` | Hello World | -| very **important** | Hey | -| _Surprisingly_ | Fumadocs | -| very long text that looks weird | hello world hello world hello world | diff --git a/docs/content/docs/test.mdx b/docs/content/docs/test.mdx deleted file mode 100644 index 68e1f4c..0000000 --- a/docs/content/docs/test.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Test -description: This is another page ---- - -Hello World again! - -## Installation - -```npm -npm i fumadocs-core fumadocs-ui -``` diff --git a/docs/content/index.mdx b/docs/content/index.mdx new file mode 100644 index 0000000..bb643ac --- /dev/null +++ b/docs/content/index.mdx @@ -0,0 +1,10 @@ +--- +title: Introduction +description: Welcome to your new Blume docs. +--- + +# Introduction + +Welcome to **Blume** — markdown-first docs powered by Astro and Vite. + +Edit `content/index.mdx` to get started, then run `blume dev`. diff --git a/docs/content/system-design/index.mdx b/docs/content/system-design/index.mdx new file mode 100644 index 0000000..2907dd7 --- /dev/null +++ b/docs/content/system-design/index.mdx @@ -0,0 +1,13 @@ +--- +title: 'Coding' +sidebar: + label: Overview + icon: arrow-right-circle + order: 1 +--- + +## Description + +Given two integer arrays `nums1` and `nums2`, return the minimum number of operations required to make both arrays equal. + +You have the following three choices to modify two arrays: diff --git a/docs/src/components/mdx.tsx b/docs/src/components/mdx.tsx deleted file mode 100644 index a640575..0000000 --- a/docs/src/components/mdx.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import defaultMdxComponents from 'fumadocs-ui/mdx'; -import type { MDXComponents } from 'mdx/types'; - -export function getMDXComponents(components?: MDXComponents) { - return { - ...defaultMdxComponents, - ...components, - } satisfies MDXComponents; -} - -export const useMDXComponents = getMDXComponents; - -declare global { - type MDXProvidedComponents = ReturnType; -} diff --git a/docs/src/components/provider.tsx b/docs/src/components/provider.tsx deleted file mode 100644 index fb30046..0000000 --- a/docs/src/components/provider.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; -import type { ReactNode } from 'react'; -import { RootProvider } from 'fumadocs-ui/provider/waku'; - -export function Provider({ children }: { children: ReactNode }) { - return {children}; -} diff --git a/docs/src/lib/cn.ts b/docs/src/lib/cn.ts deleted file mode 100644 index 241ffb3..0000000 --- a/docs/src/lib/cn.ts +++ /dev/null @@ -1 +0,0 @@ -export { cn } from 'cnfast'; diff --git a/docs/src/lib/layout.shared.tsx b/docs/src/lib/layout.shared.tsx deleted file mode 100644 index 88b9ca8..0000000 --- a/docs/src/lib/layout.shared.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; -import { appName, gitConfig } from './shared'; - -export function baseOptions(): BaseLayoutProps { - return { - nav: { - // JSX supported - title: appName, - }, - githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`, - }; -} diff --git a/docs/src/lib/shared.ts b/docs/src/lib/shared.ts deleted file mode 100644 index 4fa88de..0000000 --- a/docs/src/lib/shared.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const appName = 'Waku'; -export const docsRoute = '/docs'; -export const docsImageRoute = '/og/docs'; -export const docsContentRoute = '/llms.mdx/docs'; - -// fill this with your actual GitHub info, for example: -export const gitConfig = { - user: 'fuma-nama', - repo: 'fumadocs', - branch: 'main', -}; diff --git a/docs/src/lib/source.ts b/docs/src/lib/source.ts deleted file mode 100644 index 2e25e58..0000000 --- a/docs/src/lib/source.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { loader } from 'fumadocs-core/source'; -import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; -import { docsContentRoute, docsImageRoute, docsRoute } from './shared'; -import { defineDocs } from 'fumadocs-mdx/macro'; -import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; - -const docs = defineDocs({ - dir: 'content/docs', - docs: { - schema: pageSchema, - postprocess: { - includeProcessedMarkdown: true, - }, - }, - meta: { - schema: metaSchema, - }, -}); - -export const source = loader({ - source: docs.toFumadocsSource(), - baseUrl: docsRoute, - plugins: [lucideIconsPlugin()], -}); - -export function getPageImageUrl(page: (typeof source)['$inferPage']) { - const segments = [...page.slugs, 'image.webp']; - - return '/' + [page.locale, ...docsImageRoute.split('/'), ...segments].filter(Boolean).join('/'); -} - -export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) { - const segments = [...page.slugs, 'content.md']; - - return { - segments, - url: '/' + [page.locale, ...docsContentRoute.split('/'), ...segments].filter(Boolean).join('/'), - }; -} - -export async function getLLMText(page: (typeof source)['$inferPage']) { - const processed = await page.data.getText('processed'); - - return `# ${page.data.title} (${page.url}) - -${processed}`; -} diff --git a/docs/src/pages/(home)/404.tsx b/docs/src/pages/(home)/404.tsx deleted file mode 100644 index 0a9b00d..0000000 --- a/docs/src/pages/(home)/404.tsx +++ /dev/null @@ -1 +0,0 @@ -export { DefaultNotFound as default } from 'fumadocs-ui/layouts/home/not-found'; diff --git a/docs/src/pages/(home)/_layout.tsx b/docs/src/pages/(home)/_layout.tsx deleted file mode 100644 index dbecdd5..0000000 --- a/docs/src/pages/(home)/_layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type { ReactNode } from 'react'; -import { baseOptions } from '@/lib/layout.shared'; -import { HomeLayout } from 'fumadocs-ui/layouts/home'; - -export default function Layout({ children }: { children: ReactNode }) { - return {children}; -} diff --git a/docs/src/pages/(home)/index.tsx b/docs/src/pages/(home)/index.tsx deleted file mode 100644 index 58a364a..0000000 --- a/docs/src/pages/(home)/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Link } from 'waku'; - -export default function Home() { - return ( -
-

Fumadocs on Waku.

- - Open Docs - -
- ); -} - -export async function getConfig() { - return { - render: 'static', - }; -} diff --git a/docs/src/pages/_api/api/search.ts b/docs/src/pages/_api/api/search.ts deleted file mode 100644 index 6f73844..0000000 --- a/docs/src/pages/_api/api/search.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createFromSource } from 'fumadocs-core/search/server'; -import { source } from '@/lib/source'; - -export const { GET } = createFromSource(source); diff --git a/docs/src/pages/_api/llms-full.txt.ts b/docs/src/pages/_api/llms-full.txt.ts deleted file mode 100644 index 4a91c06..0000000 --- a/docs/src/pages/_api/llms-full.txt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getLLMText, source } from '@/lib/source'; - -export async function GET() { - const scan = source.getPages().map(getLLMText); - const scanned = await Promise.all(scan); - return new Response(scanned.join('\n\n')); -} - -export async function getConfig() { - return { - render: 'static' as const, - } as const; -} diff --git a/docs/src/pages/_api/llms.mdx/docs/[...slugs]/content.md.ts b/docs/src/pages/_api/llms.mdx/docs/[...slugs]/content.md.ts deleted file mode 100644 index 9c23617..0000000 --- a/docs/src/pages/_api/llms.mdx/docs/[...slugs]/content.md.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getLLMText, source } from '@/lib/source'; -import { ApiContext } from 'waku/router'; -import { unstable_notFound } from 'waku/router/server'; - -export async function GET( - _: Request, - { params }: ApiContext<'/llms.mdx/docs/[...slugs]/content.md'>, -) { - const slugs = params.slugs; - const page = source.getPage(slugs); - if (!page) unstable_notFound(); - - return new Response(await getLLMText(page), { - headers: { - 'Content-Type': 'text/markdown', - }, - }); -} - -export async function getConfig() { - const pages = source - .generateParams() - .map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug)); - - return { - render: 'static' as const, - staticPaths: pages, - } as const; -} diff --git a/docs/src/pages/_api/llms.txt.ts b/docs/src/pages/_api/llms.txt.ts deleted file mode 100644 index cf8e952..0000000 --- a/docs/src/pages/_api/llms.txt.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { source } from '@/lib/source'; -import { llms } from 'fumadocs-core/source'; - -export function GET() { - return new Response(llms(source).index()); -} - -export async function getConfig() { - return { - render: 'static' as const, - } as const; -} diff --git a/docs/src/pages/_api/og/docs/[...slugs]/image.webp.tsx b/docs/src/pages/_api/og/docs/[...slugs]/image.webp.tsx deleted file mode 100644 index 45578a6..0000000 --- a/docs/src/pages/_api/og/docs/[...slugs]/image.webp.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { appName } from '@/lib/shared'; -import { source } from '@/lib/source'; -import { ImageResponse } from 'takumi-js/response'; -import { generate as DefaultImage } from 'fumadocs-ui/og/takumi'; -import { ApiContext } from 'waku/router'; - -export async function GET(_: Request, { params }: ApiContext<'/og/docs/[...slugs]/image.webp'>) { - const page = source.getPage(params.slugs); - - if (!page) return new Response(undefined, { status: 404 }); - - return new ImageResponse( - , - { - width: 1200, - height: 630, - format: 'webp', - }, - ); -} - -export async function getConfig() { - const pages = source - .generateParams() - .map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug)); - - return { - render: 'static' as const, - staticPaths: pages, - } as const; -} diff --git a/docs/src/pages/_root.tsx b/docs/src/pages/_root.tsx deleted file mode 100644 index 9034c08..0000000 --- a/docs/src/pages/_root.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactNode } from 'react'; -import { Provider } from '@/components/provider'; -import '@/styles/globals.css'; - -export default async function RootElement({ children }: { children: ReactNode }) { - return ( - - - - {children} - - - ); -} - -export async function getConfig() { - return { - render: 'static', - } as const; -} diff --git a/docs/src/pages/docs/[...slugs].tsx b/docs/src/pages/docs/[...slugs].tsx deleted file mode 100644 index bd590cc..0000000 --- a/docs/src/pages/docs/[...slugs].tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { getPageImageUrl, getPageMarkdownUrl, source } from '@/lib/source'; -import { PageProps } from 'waku/router'; -import { createRelativeLink } from 'fumadocs-ui/mdx'; -import { - DocsBody, - DocsDescription, - DocsPage, - DocsTitle, - MarkdownCopyButton, - ViewOptionsPopover, -} from 'fumadocs-ui/layouts/docs/page'; -import { unstable_notFound } from 'waku/router/server'; -import { gitConfig } from '@/lib/shared'; -import { getMDXComponents } from '@/components/mdx'; - -export default function Page({ slugs }: PageProps<'/docs/[...slugs]'>) { - const page = source.getPage(slugs); - if (!page) unstable_notFound(); - - const MDX = page.data.body; - const markdownUrl = getPageMarkdownUrl(page).url; - return ( - - - {page.data.title} - {page.data.description} -
- - -
- - - -
- ); -} - -export async function getConfig() { - const pages = source - .generateParams() - .map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug)); - - return { - render: 'static' as const, - staticPaths: pages, - } as const; -} diff --git a/docs/src/pages/docs/_layout.tsx b/docs/src/pages/docs/_layout.tsx deleted file mode 100644 index 44b4475..0000000 --- a/docs/src/pages/docs/_layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { ReactNode } from 'react'; -import { DocsLayout } from 'fumadocs-ui/layouts/docs'; -import { source } from '@/lib/source'; -import { baseOptions } from '@/lib/layout.shared'; - -export default function Layout({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} diff --git a/docs/src/styles/globals.css b/docs/src/styles/globals.css deleted file mode 100644 index dd2ef5f..0000000 --- a/docs/src/styles/globals.css +++ /dev/null @@ -1,16 +0,0 @@ -@import "tailwindcss"; -@import "fumadocs-ui/css/solar.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; -} - -:root { - --fd-layout-width: 1400px; -} diff --git a/docs/worker.js b/docs/worker.js new file mode 100644 index 0000000..4a4e87c --- /dev/null +++ b/docs/worker.js @@ -0,0 +1,28 @@ +export default { + async fetch(request, env) { + const auth = request.headers.get("Authorization") ?? ""; + if (auth.startsWith("Basic ")) { + let ok = false; + try { + const decoded = atob(auth.slice(6)); + const password = decoded.slice(decoded.indexOf(":") + 1); // password may contain ':' + ok = await passwordMatches(password, env.SITE_PASSWORD); + } catch {} + if (ok) return env.ASSETS.fetch(request); + } + return new Response("Authentication required", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="leetcode-docs", charset="UTF-8"' }, + }); + }, +}; + +// timingSafeEqual needs equal-length buffers → hash both sides first +async function passwordMatches(candidate, expected) { + const enc = new TextEncoder(); + const [a, b] = await Promise.all([ + crypto.subtle.digest("SHA-256", enc.encode(candidate)), + crypto.subtle.digest("SHA-256", enc.encode(expected)), + ]); + return crypto.subtle.timingSafeEqual(a, b); +}