Skip to content

Commit 26b1281

Browse files
authored
add Copy Documentation button/dropdown feature as LLM-Optimized Prompt (#4733)
* add Copy Documentation button/dropdown feature as LLM-Optimized Prompt * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * add use client * upd * add changeset
1 parent 0347b68 commit 26b1281

File tree

32 files changed

+390
-49
lines changed

32 files changed

+390
-49
lines changed

.changeset/social-bikes-look.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'nextra-theme-docs': minor
3+
'nextra': minor
4+
---
5+
6+
feat: add Copy Documentation button/dropdown feature as LLM-Optimized Prompt
7+
8+
![](https://private-user-images.githubusercontent.com/7361780/473206831-aa851d94-3b83-46e8-8b00-5bf06c33314f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTQ0NjgwNzUsIm5iZiI6MTc1NDQ2Nzc3NSwicGF0aCI6Ii83MzYxNzgwLzQ3MzIwNjgzMS1hYTg1MWQ5NC0zYjgzLTQ2ZTgtOGIwMC01YmYwNmMzMzMxNGYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDgwNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA4MDZUMDgwOTM1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MjgwZTI3YWMzODYwNjEwNTgwNGUyOTJkMDg5MzNjOGRjZWE0MWYzMWQwZmM3ZDJkMjQzMDc2ZDRjMzQ4ZTRlZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.C7LZXaay7uuPQsDBb3MKLrbiBWxTPM2q9nfSqUGp3_0)
9+
10+
> **Note**
11+
>
12+
> If you are using [`content` directory](https://nextra.site/docs/file-conventions/content-directory), you **must** pass the `sourceCode` prop to enable this feature.
13+
>
14+
> ```diff
15+
> const {
16+
> default: MDXContent,
17+
> toc,
18+
> metadata,
19+
> + sourceCode
20+
> } = await importPage(params.mdxPath)
21+
> return (
22+
> - <Wrapper toc={toc} metadata={metadata}>
23+
> + <Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
24+
> <MDXContent {...props} params={params} />
25+
> </Wrapper>
26+
> )
27+
> ```

docs/app/api/[name]/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,14 @@ type PageProps = Readonly<{
117117
}>
118118

119119
const Page: FC<PageProps> = async props => {
120-
const { default: MDXContent, toc, metadata } = await getReference(props)
120+
const {
121+
default: MDXContent,
122+
toc,
123+
metadata,
124+
sourceCode
125+
} = await getReference(props)
121126
return (
122-
<Wrapper toc={toc} metadata={metadata}>
127+
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
123128
<MDXContent />
124129
</Wrapper>
125130
)

docs/app/docs/built-ins/[name]/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,14 @@ type PageProps = Readonly<{
171171
}>
172172

173173
const Page: FC<PageProps> = async props => {
174-
const { default: MDXContent, toc, metadata } = await getReference(props)
174+
const {
175+
default: MDXContent,
176+
toc,
177+
metadata,
178+
sourceCode
179+
} = await getReference(props)
175180
return (
176-
<Wrapper toc={toc} metadata={metadata}>
181+
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
177182
<MDXContent />
178183
</Wrapper>
179184
)

examples/docs/src/app/docs/[[...mdxPath]]/page.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ const Wrapper = getMDXComponents().wrapper
1313

1414
export default async function Page(props) {
1515
const params = await props.params
16-
const result = await importPage(params.mdxPath)
17-
const { default: MDXContent, toc, metadata } = result
16+
const {
17+
default: MDXContent,
18+
toc,
19+
metadata,
20+
sourceCode
21+
} = await importPage(params.mdxPath)
1822
return (
19-
<Wrapper toc={toc} metadata={metadata}>
23+
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
2024
<MDXContent {...props} params={params} />
2125
</Wrapper>
2226
)

examples/swr-site/app/[lang]/[[...mdxPath]]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ const Wrapper = getMDXComponents().wrapper
2222
const Page: FC<PageProps> = async props => {
2323
const params = await props.params
2424
const result = await importPage(params.mdxPath, params.lang)
25-
const { default: MDXContent, toc, metadata } = result
25+
const { default: MDXContent, toc, metadata, sourceCode } = result
2626
return (
27-
<Wrapper toc={toc} metadata={metadata}>
27+
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
2828
<MDXContent {...props} params={params} />
2929
</Wrapper>
3030
)

examples/swr-site/app/[lang]/graphql-eslint/[[...slug]]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export default async function Page(props: PageProps) {
7070
const { default: MDXContent, toc, metadata } = evaluate(rawJs, components)
7171

7272
return (
73-
<Wrapper toc={toc} metadata={metadata}>
73+
<Wrapper toc={toc} metadata={metadata} sourceCode={rawJs}>
7474
<MDXContent />
7575
</Wrapper>
7676
)

examples/swr-site/app/[lang]/remote/graphql-yoga/[[...slug]]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export default async function Page(props: PageProps) {
8989

9090
const { default: MDXContent, toc, metadata } = evaluate(rawJs, components)
9191
return (
92-
<Wrapper toc={toc} metadata={metadata}>
92+
<Wrapper toc={toc} metadata={metadata} sourceCode={rawJs}>
9393
<MDXContent />
9494
</Wrapper>
9595
)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import cn from 'clsx'
2+
import { Button, Select } from 'nextra/components'
3+
import { useCopy } from 'nextra/hooks'
4+
import {
5+
ArrowRightIcon,
6+
ChatGPTIcon,
7+
ClaudeIcon,
8+
CopyIcon,
9+
LinkArrowIcon
10+
} from 'nextra/icons'
11+
import type { FC, SVGProps } from 'react'
12+
13+
const Item: FC<{
14+
icon: FC<SVGProps<SVGElement>>
15+
title: string
16+
description: string
17+
isExternal?: boolean
18+
}> = ({ icon: Icon, title, description, isExternal }) => {
19+
return (
20+
<div className="x:flex x:gap-3 x:items-center">
21+
<Icon width="16" />
22+
<div className="x:flex x:flex-col">
23+
<span className="x:font-medium x:flex x:gap-1">
24+
{title}
25+
{isExternal && <LinkArrowIcon height="1em" />}
26+
</span>
27+
<span className="x:text-xs">{description}</span>
28+
</div>
29+
</div>
30+
)
31+
}
32+
33+
export const CopyPage: FC<{ sourceCode: string }> = ({ sourceCode }) => {
34+
const { copy, isCopied } = useCopy()
35+
36+
function handleCopy() {
37+
copy(sourceCode)
38+
}
39+
40+
return (
41+
<div className="x:border x:inline-flex x:rounded-md x:items-stretch nextra-border x:float-end x:overflow-hidden">
42+
<Button
43+
className={({ hover }) =>
44+
cn(
45+
'x:ps-2 x:pe-1 x:flex x:gap-2 x:text-sm x:font-medium x:items-center',
46+
isCopied && 'x:opacity-70',
47+
hover &&
48+
'x:bg-gray-200 x:text-gray-900 x:dark:bg-primary-100/5 x:dark:text-gray-50'
49+
)
50+
}
51+
onClick={handleCopy}
52+
>
53+
<CopyIcon width="16" />
54+
{isCopied ? 'Copied' : 'Copy page'}
55+
</Button>
56+
<Select
57+
anchor={{ to: 'bottom end', gap: 10 }}
58+
className="x:rounded-none"
59+
options={[
60+
{
61+
id: 'copy',
62+
name: (
63+
<Item
64+
icon={CopyIcon}
65+
title="Copy page"
66+
description="Copy page as Markdown for LLMs"
67+
/>
68+
)
69+
},
70+
{
71+
id: 'chatgpt',
72+
name: (
73+
<Item
74+
icon={ChatGPTIcon}
75+
title="Open in ChatGPT"
76+
description="Ask questions about this page"
77+
isExternal
78+
/>
79+
)
80+
},
81+
{
82+
id: 'claude',
83+
name: (
84+
<Item
85+
icon={ClaudeIcon}
86+
title="Open in Claude"
87+
description="Ask questions about this page"
88+
isExternal
89+
/>
90+
)
91+
}
92+
]}
93+
value=""
94+
selectedOption={<ArrowRightIcon width="12" className="x:rotate-90" />}
95+
onChange={value => {
96+
if (value === 'copy') {
97+
handleCopy()
98+
return
99+
}
100+
const url =
101+
value === 'chatgpt'
102+
? 'chatgpt.com/?hints=search&prompt'
103+
: 'claude.ai/new?q'
104+
const query = `Read from ${location.href} so I can ask questions about it.`
105+
window.open(`https://${url}=${encodeURIComponent(query)}`, '_blank')
106+
}}
107+
/>
108+
</div>
109+
)
110+
}

packages/nextra-theme-docs/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { Breadcrumb } from './breadcrumb'
2+
export { CopyPage } from './copy-page'
23
export { Footer } from './footer'
34
export { LastUpdated } from './last-updated'
45
export { LocaleSwitch } from './locale-switch'

packages/nextra-theme-docs/src/mdx-components/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const DEFAULT_COMPONENTS = getNextraMDXComponents({
8585
{...props}
8686
/>
8787
),
88-
wrapper({ toc, children, metadata, bottomContent, ...props }) {
88+
wrapper({ toc, children, metadata, bottomContent, sourceCode, ...props }) {
8989
// @ts-expect-error fixme
9090
toc = toc.map(item => ({
9191
...item,
@@ -99,7 +99,11 @@ const DEFAULT_COMPONENTS = getNextraMDXComponents({
9999
>
100100
<TOCProvider value={toc}>
101101
<Sidebar />
102-
<ClientWrapper metadata={metadata} bottomContent={bottomContent}>
102+
<ClientWrapper
103+
metadata={metadata}
104+
bottomContent={bottomContent}
105+
sourceCode={sourceCode}
106+
>
103107
<SkipNavContent />
104108
<main
105109
data-pagefind-body={

0 commit comments

Comments
 (0)