mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-16 23:16:26 +00:00
refactor(docs): migrate documentation to Blume and restructure content
This commit is contained in:
@@ -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
|
|
||||||
```
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
||||||
|
:::
|
||||||
@@ -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:
|
||||||
@@ -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:
|
||||||
@@ -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!
|
|
||||||
|
|
||||||
<Cards>
|
|
||||||
<Card title="Learn more about Waku" href="https://waku.gg" />
|
|
||||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
|
|
||||||
</Cards>
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
title: Test
|
|
||||||
description: This is another page
|
|
||||||
---
|
|
||||||
|
|
||||||
Hello World again!
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```npm
|
|
||||||
npm i fumadocs-core fumadocs-ui
|
|
||||||
```
|
|
||||||
@@ -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`.
|
||||||
@@ -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:
|
||||||
@@ -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<typeof getMDXComponents>;
|
|
||||||
}
|
|
||||||
@@ -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 <RootProvider>{children}</RootProvider>;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { cn } from 'cnfast';
|
|
||||||
@@ -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}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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',
|
|
||||||
};
|
|
||||||
@@ -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}`;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { DefaultNotFound as default } from 'fumadocs-ui/layouts/home/not-found';
|
|
||||||
@@ -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 <HomeLayout {...baseOptions()}>{children}</HomeLayout>;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Link } from 'waku';
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<div className="flex-1 flex flex-col items-center justify-center text-center">
|
|
||||||
<h1 className="font-medium text-xl mb-4">Fumadocs on Waku.</h1>
|
|
||||||
<Link
|
|
||||||
to="/docs"
|
|
||||||
className="px-3 py-2 rounded-lg bg-fd-primary text-fd-primary-foreground font-medium text-sm mx-auto"
|
|
||||||
>
|
|
||||||
Open Docs
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getConfig() {
|
|
||||||
return {
|
|
||||||
render: 'static',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { createFromSource } from 'fumadocs-core/search/server';
|
|
||||||
import { source } from '@/lib/source';
|
|
||||||
|
|
||||||
export const { GET } = createFromSource(source);
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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(
|
|
||||||
<DefaultImage title={page.data.title} description={page.data.description} site={appName} />,
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<html lang="en" suppressHydrationWarning>
|
|
||||||
<head></head>
|
|
||||||
<body data-version="1.0" className="flex flex-col min-h-screen">
|
|
||||||
<Provider>{children}</Provider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getConfig() {
|
|
||||||
return {
|
|
||||||
render: 'static',
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<DocsPage toc={page.data.toc}>
|
|
||||||
<meta property="og:image" content={getPageImageUrl(page)} />
|
|
||||||
<DocsTitle>{page.data.title}</DocsTitle>
|
|
||||||
<DocsDescription className="mb-0">{page.data.description}</DocsDescription>
|
|
||||||
<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6">
|
|
||||||
<MarkdownCopyButton markdownUrl={markdownUrl} />
|
|
||||||
<ViewOptionsPopover
|
|
||||||
markdownUrl={markdownUrl}
|
|
||||||
githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${page.path}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<DocsBody>
|
|
||||||
<MDX
|
|
||||||
components={getMDXComponents({
|
|
||||||
// this allows you to link to other pages with relative file paths
|
|
||||||
a: createRelativeLink(source, page),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</DocsBody>
|
|
||||||
</DocsPage>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
|
|
||||||
{children}
|
|
||||||
</DocsLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user