Skip to content

Commit fd2b209

Browse files
committed
Merge branch 'main' into 10.4-alpha-docs
* main: More fixes Fix type error Various features and fixes Fix copy markdown and .md routes for /docs root page Fix Next error Add copy markdown button to docs pages Transform links to absolute in markdown content fix lint generate storybook versions from npmjs instead of from the monorepo
2 parents 3c85577 + 082f7b8 commit fd2b209

27 files changed

Lines changed: 539 additions & 105 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ yarn-error.log*
3737
# Generated content
3838
apps/frontpage/content/docs
3939
apps/frontpage/content/snippets
40+
apps/frontpage/generated-redirects.json
41+
apps/frontpage/generated-versions.json
4042
apps/frontpage/public/docs-assets
4143
apps/frontpage/public/_redirects
4244

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ This project is structured around [Turborepo](https://turbo.build/repo). This do
2525

2626
> Generate redirects file
2727
28+
#### `npx turbo generate-versions`
29+
30+
> Generate Storybook version metadata from npm dist-tags
31+
2832
#### `npx turbo dev`
2933

3034
> Run all apps locally

apps/frontpage/app/llms-full.txt/route.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
resolveVersionFromSlug,
1111
} from '../../lib/resolve-doc-for-llm';
1212
import { findDocFile } from '../../lib/get-page';
13-
import { getLlmsBannerLines } from '../llms.txt/route';
13+
import { getLlmsBannerLines } from '../../lib/get-llm-banner-lines';
1414

1515
export const dynamic = 'force-dynamic';
1616

@@ -27,7 +27,7 @@ export function GET(request: NextRequest) {
2727
const tree = listOfTrees.find((t) => t.name === versionId);
2828
const flatTree = tree?.children ? getFlatTree({ tree: tree.children }) : [];
2929

30-
const baseUrl = 'https://storybook.js.org';
30+
const baseUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}`;
3131

3232
// Collect all renderers and languages across all pages
3333
const globalRenderers = new Set<string>();
@@ -52,12 +52,17 @@ export function GET(request: NextRequest) {
5252
const fileContent = fs.readFileSync(fullPath, 'utf8');
5353
const { content: rawContent, data } = matter(fileContent);
5454

55+
const pagePath = [versionId, ...docPath.split('/')];
56+
5557
const { content, availableRenderers, availableLanguages } =
5658
resolveDocForLLM(rawContent, {
5759
versionId,
5860
renderer,
5961
language,
6062
codeOnly,
63+
pagePath,
64+
isIndexPage: result.isIndexPage,
65+
baseUrl,
6166
});
6267

6368
for (const r of availableRenderers) globalRenderers.add(r);
@@ -73,7 +78,7 @@ export function GET(request: NextRequest) {
7378
}
7479

7580
const header = [
76-
...getLlmsBannerLines({ version: version ?? latestVersion }),
81+
...getLlmsBannerLines({ baseUrl, version: version ?? latestVersion }),
7782
'---',
7883
'',
7984
].join('\n');

apps/frontpage/app/llms.txt/route.ts

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,14 @@
11
import { type NextRequest, NextResponse } from 'next/server';
2-
import { type DocsVersion, docsVersions, latestVersion } from '@repo/utils';
2+
import { docsVersions, latestVersion } from '@repo/utils';
33
import { getAllTrees } from '../../lib/get-all-trees';
44
import { getFlatTree } from '../../lib/get-flat-tree';
55
import { resolveVersionFromSlug } from '../../lib/resolve-doc-for-llm';
6+
import { getLlmsBannerLines } from '../../lib/get-llm-banner-lines';
67

78
export const dynamic = 'force-dynamic';
89

9-
const baseUrl = 'https://storybook.js.org';
10-
11-
export const getLlmsBannerLines = ({ version }: { version: DocsVersion }) => [
12-
'# Storybook',
13-
'',
14-
'> Storybook is a frontend workshop for building UI components and pages in isolation. It helps with UI development, testing, and documentation.',
15-
'',
16-
`Current version: ${version.label} (${version.id})`,
17-
'',
18-
'## Documentation',
19-
'',
20-
`- [Storybook Docs](${baseUrl}/docs): Main documentation`,
21-
`- [Full Documentation (Markdown)](${baseUrl}/llms-full.txt): Complete documentation in plain text for LLM consumption`,
22-
'',
23-
'## Markdown Access',
24-
'',
25-
'Append `.md` to any docs URL to get clean markdown with code examples:',
26-
`- \`${baseUrl}/docs/writing-stories/decorators.md\``,
27-
`- \`${baseUrl}/docs/9/writing-stories/decorators.md\` (Version 9)`,
28-
'',
29-
'### Query Parameters',
30-
'',
31-
'All markdown endpoints (`.md` URLs and `llms-full.txt`) support these query parameters:',
32-
'- `renderer` - Framework filter for code snippets (default: `react`). Options: `react`, `vue`, `angular`, `svelte`, `web-components`, `solid`, `preact`, `html`, `ember`, `qwik`',
33-
'- `language` - Language filter for code snippets (default: `ts`). Options: `ts`, `js`',
34-
'- `codeOnly` - When `true`, returns only the code snippets without prose',
35-
'',
36-
'Examples:',
37-
'- `GET /docs/writing-stories/decorators.md?renderer=vue&language=ts`',
38-
'- `GET /docs/writing-stories/decorators.md?codeOnly=true`',
39-
'- `GET /llms-full.txt?renderer=angular&language=js`',
40-
'',
41-
'### Versioned Access',
42-
'',
43-
'Prefix the path with a version slug for older versions:',
44-
];
45-
4610
export function GET(request: NextRequest) {
11+
const baseUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}`;
4712
const versionSlug = request.nextUrl.searchParams.get('version') ?? undefined;
4813
const versionId = resolveVersionFromSlug(versionSlug);
4914
const activeVersion =
@@ -53,7 +18,7 @@ export function GET(request: NextRequest) {
5318
const tree = listOfTrees.find((t) => t.name === versionId);
5419
const flatTree = tree?.children ? getFlatTree({ tree: tree.children }) : [];
5520

56-
const lines = [...getLlmsBannerLines({ version: activeVersion })];
21+
const lines = [...getLlmsBannerLines({ baseUrl, version: activeVersion })];
5722

5823
for (const version of docsVersions) {
5924
if (version.id === latestVersion.id) continue;

apps/frontpage/app/md-api/[...path]/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,19 @@ export async function GET(request: NextRequest, context: RouteContext) {
4545
const fileContent = fs.readFileSync(fullPath, 'utf8');
4646
const { content: rawContent, data } = matter(fileContent);
4747

48+
const pagePath = [versionId, ...slug.split('/')];
49+
const baseUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}`;
50+
4851
const { content, availableRenderers, availableLanguages } = resolveDocForLLM(
4952
rawContent,
5053
{
5154
versionId,
5255
renderer,
5356
language,
5457
codeOnly,
58+
pagePath,
59+
isIndexPage: result.isIndexPage,
60+
baseUrl,
5561
},
5662
);
5763

apps/frontpage/app/versions/route.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import crypto from 'node:crypto';
2-
import { readFile } from 'node:fs/promises';
3-
import path from 'node:path';
42
import { headers } from 'next/headers';
53
import type { NextRequest } from 'next/server';
64
import { BigQuery } from '@google-cloud/bigquery';
75
import { parse as semverParse } from 'semver';
8-
import { latestVersion } from '@repo/utils';
6+
import { readGeneratedVersions } from '../../lib/generated-versions';
97

108
// eslint-disable-next-line no-useless-escape, prefer-named-capture-group -- Escape is absolutely necessary for regex?
119
const SEP_REGEX = /([\.:])/;
@@ -15,13 +13,6 @@ const logger = { log: (..._msgs: unknown[]) => {} };
1513

1614
const { GCP_CREDENTIALS, SKIP_IP_HASH } = process.env;
1715

18-
const versionFilesDir = path.join(
19-
process.cwd(),
20-
'content/docs',
21-
latestVersion.id,
22-
'versions',
23-
);
24-
2516
const md5 = (host: string) => {
2617
const hash = crypto.createHash('md5');
2718
hash.update(host);
@@ -99,27 +90,10 @@ const log = async (searchParams: URLSearchParams) => {
9990
await table.insert([row]);
10091
};
10192

102-
type DistTag = 'latest' | 'next';
103-
10493
const versions = async () => {
10594
logger.log('fetching versions');
10695

107-
async function getVersionData(distTag: DistTag) {
108-
const data = JSON.parse(
109-
await readFile(
110-
new URL(path.join(versionFilesDir, `${distTag}.json`), import.meta.url),
111-
'utf8',
112-
),
113-
);
114-
// Strip off no-longer-used `info` property
115-
const { version } = data as { version: string };
116-
return { version };
117-
}
118-
119-
const latest = await getVersionData('latest');
120-
const next = await getVersionData('next');
121-
122-
return JSON.stringify({ latest, next });
96+
return JSON.stringify(await readGeneratedVersions());
12397
};
12498

12599
export async function GET(request: NextRequest) {
File renamed without changes.

apps/frontpage/components/docs/content.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type FC } from 'react';
22
import { cn } from '@repo/utils';
33
import { type PageDataProps } from '../../lib/get-page';
4+
import { CopyMarkdownButton } from './copy-markdown-button';
45
import { Renderers } from './renderers';
56
import { DocsFooter } from './footer/footer';
67
import { PageTabs } from './page-tabs';
@@ -17,7 +18,10 @@ export const Content: FC<{ page: PageDataProps }> = ({ page }) => {
1718
>
1819
{page.title ?? 'Title is missing'}
1920
</h1>
20-
{!page.hideRendererSelector && <Renderers />}
21+
<div className="mb-8 flex flex-col items-start gap-2 min-[430px]:flex-row min-[430px]:justify-between">
22+
{!page.hideRendererSelector && <Renderers />}
23+
<CopyMarkdownButton />
24+
</div>
2125
{page.tabs && page.tabs.length > 0 ? (
2226
<PageTabs {...page} />
2327
) : null}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use client';
2+
3+
import { type FC, useCallback, useState } from 'react';
4+
import { usePathname } from 'next/navigation';
5+
import { latestVersion } from '@repo/utils';
6+
import { useDocs } from '../../app/docs/provider';
7+
import { Button } from './button';
8+
9+
export const CopyMarkdownButton: FC = () => {
10+
const pathname = usePathname();
11+
const { activeRenderer, activeLanguage } = useDocs();
12+
const [copied, setCopied] = useState(false);
13+
14+
const handleClick = useCallback(() => {
15+
const mdApiPath =
16+
/^\/docs\/?$/.test(pathname)
17+
? `/md-api/${latestVersion.id}`
18+
: pathname.replace(/^\/docs\//, '/md-api/');
19+
const params = new URLSearchParams();
20+
if (activeRenderer) params.set('renderer', activeRenderer);
21+
if (activeLanguage) params.set('language', activeLanguage);
22+
23+
const url = `${mdApiPath}?${params.toString()}`;
24+
25+
void fetch(url)
26+
.then((response) => response.text())
27+
.then((text) => navigator.clipboard.writeText(text))
28+
.then(() => {
29+
setCopied(true);
30+
setTimeout(() => {
31+
setCopied(false);
32+
}, 2000);
33+
});
34+
}, [pathname, activeRenderer, activeLanguage]);
35+
36+
return (
37+
<Button onClick={handleClick}>
38+
{copied ? 'Copied!' : 'Copy markdown'}
39+
</Button>
40+
);
41+
};

apps/frontpage/components/docs/mdx/code-snippets/code-snippets.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const getActiveRenderer = (activeRendererIn: string | null, pathname: string) =>
6565
'nextjs': 'react',
6666
'react-native-web': 'react',
6767
'sveltekit': 'svelte',
68+
'tanstack-react': 'react',
6869
'vue3': 'vue',
6970
};
7071

0 commit comments

Comments
 (0)