Skip to content

Commit 27ab2a9

Browse files
authored
Merge pull request #24 from lumpinif/ref/create-deepcrawl-312
pkg:create-deepcrawl:feat add jwt onboarding and dry-run preview
2 parents d9e9b44 + 782cc9c commit 27ab2a9

55 files changed

Lines changed: 3282 additions & 86 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/app/app/(docs)/docs/[[...slug]]/page.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
DocsTitle,
66
} from 'fumadocs-ui/page';
77
import { notFound } from 'next/navigation';
8+
import { CopyMarkdownButton } from '@/components/docs/copy-markdown-button';
9+
import { OpenDocsMenu } from '@/components/docs/open-docs-menu';
810
import { getMDXComponents } from '@/components/mdx-components';
911
import { absoluteUrl } from '@/lib/navigation-config';
1012
import { source } from '@/lib/source';
@@ -71,6 +73,9 @@ export default async function Page(props: {
7173
}
7274

7375
const MDX = page.data.body;
76+
const pageUrl = absoluteUrl(page.url);
77+
const markdownUrl = `${page.url}.mdx`;
78+
const githubUrl = `https://github.com/lumpinif/deepcrawl/blob/main/apps/app/content/docs/${page.path}`;
7479

7580
return (
7681
<DocsPage
@@ -89,8 +94,20 @@ export default async function Page(props: {
8994
toc={page.data.toc}
9095
// lastUpdate={page.data.lastUpdated}
9196
>
92-
<DocsTitle>{page.data.title}</DocsTitle>
93-
<DocsDescription>{page.data.description}</DocsDescription>
97+
<div className="mb-4 flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
98+
<DocsTitle className="mb-0">{page.data.title}</DocsTitle>
99+
<div className="flex shrink-0 flex-wrap items-center gap-2 md:justify-end">
100+
<CopyMarkdownButton markdownUrl={markdownUrl} />
101+
<OpenDocsMenu
102+
githubUrl={githubUrl}
103+
markdownUrl={markdownUrl}
104+
pageUrl={pageUrl}
105+
/>
106+
</div>
107+
</div>
108+
<DocsDescription className="mb-8">
109+
{page.data.description}
110+
</DocsDescription>
94111
<DocsBody>
95112
<MDX components={getMDXComponents()} />
96113
</DocsBody>

apps/app/app/(www)/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Banner } from '@/components/www/banner';
2+
import { CreateDeepcrawlCliSection } from '@/components/www/sections/create-deepcrawl-cli';
23
import { Faq } from '@/components/www/sections/faq';
34
import { Footer } from '@/components/www/sections/footer';
45
import { Hero } from '@/components/www/sections/hero';
@@ -11,6 +12,7 @@ export default function IndexPage() {
1112
<div className="container relative mx-auto size-full max-w-6xl divide-y px-0 [&>*:nth-child(n+3)]:sm:border-x">
1213
<Banner />
1314
<Hero />
15+
<CreateDeepcrawlCliSection />
1416
<ValueProp />
1517
<ToolkitSuite />
1618
<Surfaces />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getLLMText } from '@/lib/get-llm-text';
2+
import { source } from '@/lib/source';
3+
4+
// Next 16 already emits this route handler as SSG from generateStaticParams,
5+
// so we intentionally do not add dynamic = 'force-static' here.
6+
export function generateStaticParams() {
7+
return source.generateParams();
8+
}
9+
10+
export async function GET(
11+
_request: Request,
12+
{ params }: { params: Promise<{ slug?: string[] }> },
13+
) {
14+
const { slug } = await params;
15+
const page = source.getPage(slug);
16+
17+
if (!page) {
18+
return new Response('Not found', {
19+
status: 404,
20+
});
21+
}
22+
23+
return new Response(await getLLMText(page), {
24+
headers: {
25+
'Content-Type': 'text/markdown; charset=utf-8',
26+
},
27+
});
28+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use client';
2+
3+
import { Button } from '@deepcrawl/ui/components/ui/button';
4+
import { cn } from '@deepcrawl/ui/lib/utils';
5+
import { Check, Copy } from 'lucide-react';
6+
import { useEffect, useState } from 'react';
7+
import { toast } from 'sonner';
8+
import { copyToClipboard } from '@/utils';
9+
10+
type CopyMarkdownButtonProps = {
11+
className?: string;
12+
markdownUrl: string;
13+
};
14+
15+
const RESET_DELAY_MS = 1500;
16+
17+
export function CopyMarkdownButton({
18+
className,
19+
markdownUrl,
20+
}: CopyMarkdownButtonProps) {
21+
const [isCopied, setIsCopied] = useState(false);
22+
23+
useEffect(() => {
24+
if (!isCopied) {
25+
return;
26+
}
27+
28+
const timer = window.setTimeout(() => {
29+
setIsCopied(false);
30+
}, RESET_DELAY_MS);
31+
32+
return () => {
33+
window.clearTimeout(timer);
34+
};
35+
}, [isCopied]);
36+
37+
const handleCopy = async () => {
38+
try {
39+
const response = await fetch(markdownUrl, {
40+
headers: {
41+
Accept: 'text/markdown',
42+
},
43+
});
44+
45+
if (!response.ok) {
46+
throw new Error('Failed to load markdown');
47+
}
48+
49+
const markdown = await response.text();
50+
const didCopy = await copyToClipboard(markdown);
51+
52+
if (!didCopy) {
53+
throw new Error('Failed to copy markdown');
54+
}
55+
56+
setIsCopied(true);
57+
} catch (error) {
58+
setIsCopied(false);
59+
toast.error(
60+
error instanceof Error ? error.message : 'Failed to copy markdown',
61+
);
62+
}
63+
};
64+
65+
return (
66+
<Button
67+
aria-label={isCopied ? 'Markdown copied' : 'Copy page markdown'}
68+
className={cn(
69+
'gap-1.5 rounded-xl border-fd-border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground',
70+
className,
71+
)}
72+
onClick={handleCopy}
73+
size="sm"
74+
type="button"
75+
variant="outline"
76+
>
77+
{isCopied ? <Check className="size-4" /> : <Copy className="size-4" />}
78+
<span>{isCopied ? 'Copied' : 'Copy Markdown'}</span>
79+
</Button>
80+
);
81+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use client';
2+
3+
import { Button } from '@deepcrawl/ui/components/ui/button';
4+
import {
5+
DropdownMenu,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuSeparator,
9+
DropdownMenuTrigger,
10+
} from '@deepcrawl/ui/components/ui/dropdown-menu';
11+
import { cn } from '@deepcrawl/ui/lib/utils';
12+
import {
13+
Bot,
14+
ChevronDown,
15+
ExternalLink,
16+
FileText,
17+
Github,
18+
Sparkles,
19+
} from 'lucide-react';
20+
21+
type OpenDocsMenuProps = {
22+
className?: string;
23+
githubUrl: string;
24+
markdownUrl: string;
25+
pageUrl: string;
26+
};
27+
28+
const AI_PROMPT = 'I want to ask questions about it.';
29+
30+
export function OpenDocsMenu({
31+
className,
32+
githubUrl,
33+
markdownUrl,
34+
pageUrl,
35+
}: OpenDocsMenuProps) {
36+
return (
37+
<DropdownMenu modal={false}>
38+
<DropdownMenuTrigger asChild>
39+
<Button
40+
className={cn(
41+
'gap-1.5 rounded-xl border-fd-border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground',
42+
className,
43+
)}
44+
size="sm"
45+
variant="outline"
46+
>
47+
<ExternalLink className="size-4" />
48+
<span>Open</span>
49+
<ChevronDown className="size-4" />
50+
</Button>
51+
</DropdownMenuTrigger>
52+
<DropdownMenuContent
53+
align="end"
54+
className="w-72 rounded-xl border-fd-border bg-fd-popover p-1.5"
55+
side="bottom"
56+
>
57+
<DropdownMenuItem asChild>
58+
<a href={githubUrl} rel="noreferrer noopener" target="_blank">
59+
<Github className="size-4" />
60+
<span>Open in GitHub</span>
61+
</a>
62+
</DropdownMenuItem>
63+
<DropdownMenuItem asChild>
64+
<a href={markdownUrl} rel="noreferrer noopener" target="_blank">
65+
<FileText className="size-4" />
66+
<span>View as Markdown</span>
67+
</a>
68+
</DropdownMenuItem>
69+
<DropdownMenuSeparator />
70+
<DropdownMenuItem asChild>
71+
<a
72+
href={buildSciraUrl(pageUrl)}
73+
rel="noreferrer noopener"
74+
target="_blank"
75+
>
76+
<Sparkles className="size-4" />
77+
<span>Open in Scira AI</span>
78+
</a>
79+
</DropdownMenuItem>
80+
<DropdownMenuItem asChild>
81+
<a
82+
href={buildChatGptUrl(pageUrl)}
83+
rel="noreferrer noopener"
84+
target="_blank"
85+
>
86+
<Bot className="size-4" />
87+
<span>Open in ChatGPT</span>
88+
</a>
89+
</DropdownMenuItem>
90+
<DropdownMenuItem asChild>
91+
<a
92+
href={buildClaudeUrl(pageUrl)}
93+
rel="noreferrer noopener"
94+
target="_blank"
95+
>
96+
<Bot className="size-4" />
97+
<span>Open in Claude</span>
98+
</a>
99+
</DropdownMenuItem>
100+
<DropdownMenuItem asChild>
101+
<a
102+
href={buildCursorUrl(pageUrl)}
103+
rel="noreferrer noopener"
104+
target="_blank"
105+
>
106+
<Bot className="size-4" />
107+
<span>Open in Cursor</span>
108+
</a>
109+
</DropdownMenuItem>
110+
</DropdownMenuContent>
111+
</DropdownMenu>
112+
);
113+
}
114+
115+
function buildSciraUrl(pageUrl: string): string {
116+
return `https://scira.ai/?q=${encodePrompt(pageUrl)}`;
117+
}
118+
119+
function buildChatGptUrl(pageUrl: string): string {
120+
return `https://chatgpt.com/?hints=search&q=${encodePrompt(pageUrl)}`;
121+
}
122+
123+
function buildClaudeUrl(pageUrl: string): string {
124+
return `https://claude.ai/new?q=${encodePrompt(pageUrl)}`;
125+
}
126+
127+
function buildCursorUrl(pageUrl: string): string {
128+
return `https://cursor.com/link/prompt?text=${encodePrompt(pageUrl)}`;
129+
}
130+
131+
function encodePrompt(pageUrl: string): string {
132+
return encodeURIComponent(`Read ${pageUrl}, ${AI_PROMPT}`);
133+
}

0 commit comments

Comments
 (0)