create-bunspace 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +132 -1
- package/dist/templates/fumadocs/.env.example +49 -0
- package/dist/templates/fumadocs/.github/workflows/deploy.yml +89 -0
- package/dist/templates/fumadocs/CLAUDE.md +164 -0
- package/dist/templates/fumadocs/LICENSE +21 -0
- package/dist/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +269 -0
- package/dist/templates/fumadocs/README.md +319 -0
- package/dist/templates/fumadocs/biome.json +41 -0
- package/dist/templates/fumadocs/bun.lock +883 -0
- package/dist/templates/fumadocs/content-template/docs/getting-started/index.mdx +92 -0
- package/dist/templates/fumadocs/content-template/docs/getting-started/installation.mdx +168 -0
- package/dist/templates/fumadocs/content-template/docs/getting-started/quick-start.mdx +168 -0
- package/dist/templates/fumadocs/content-template/docs/index.mdx +70 -0
- package/dist/templates/fumadocs/content-template/en/docs/getting-started/index.mdx +92 -0
- package/dist/templates/fumadocs/content-template/en/docs/getting-started/installation.mdx +168 -0
- package/dist/templates/fumadocs/content-template/en/docs/getting-started/quick-start.mdx +168 -0
- package/dist/templates/fumadocs/content-template/en/docs/index.mdx +69 -0
- package/dist/templates/fumadocs/messages/en.json +14 -0
- package/dist/templates/fumadocs/messages/es.json +14 -0
- package/dist/templates/fumadocs/next.config.mjs +35 -0
- package/dist/templates/fumadocs/oxlint.json +14 -0
- package/dist/templates/fumadocs/package.json +35 -0
- package/dist/templates/fumadocs/postcss.config.mjs +5 -0
- package/dist/templates/fumadocs/source.config.ts +31 -0
- package/dist/templates/fumadocs/src/app/(home)/layout.tsx +6 -0
- package/dist/templates/fumadocs/src/app/(home)/page.tsx +132 -0
- package/dist/templates/fumadocs/src/app/api/search/route.ts +9 -0
- package/dist/templates/fumadocs/src/app/docs/[[...slug]]/page.tsx +62 -0
- package/dist/templates/fumadocs/src/app/docs/layout.tsx +11 -0
- package/dist/templates/fumadocs/src/app/en/docs/[[...slug]]/page.tsx +61 -0
- package/dist/templates/fumadocs/src/app/en/docs/layout.tsx +11 -0
- package/dist/templates/fumadocs/src/app/global.css +3 -0
- package/dist/templates/fumadocs/src/app/layout.tsx +47 -0
- package/dist/templates/fumadocs/src/app/llms-full.txt/route.ts +10 -0
- package/dist/templates/fumadocs/src/app/og/docs/[...slug]/route.tsx +27 -0
- package/dist/templates/fumadocs/src/components/language-selector.tsx +56 -0
- package/dist/templates/fumadocs/src/components/markdown-actions.tsx +61 -0
- package/dist/templates/fumadocs/src/config/site.config.ts +115 -0
- package/dist/templates/fumadocs/src/lib/layout.shared.tsx +23 -0
- package/dist/templates/fumadocs/src/lib/source.ts +91 -0
- package/dist/templates/fumadocs/src/mdx-components.tsx +14 -0
- package/dist/templates/fumadocs/tsconfig.json +46 -0
- package/package.json +1 -1
- package/templates/fumadocs/.env.example +49 -0
- package/templates/fumadocs/.github/workflows/deploy.yml +89 -0
- package/templates/fumadocs/CLAUDE.md +164 -0
- package/templates/fumadocs/LICENSE +21 -0
- package/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +269 -0
- package/templates/fumadocs/README.md +319 -0
- package/templates/fumadocs/biome.json +41 -0
- package/templates/fumadocs/bun.lock +883 -0
- package/templates/fumadocs/content-template/docs/getting-started/index.mdx +92 -0
- package/templates/fumadocs/content-template/docs/getting-started/installation.mdx +168 -0
- package/templates/fumadocs/content-template/docs/getting-started/quick-start.mdx +168 -0
- package/templates/fumadocs/content-template/docs/index.mdx +70 -0
- package/templates/fumadocs/content-template/en/docs/getting-started/index.mdx +92 -0
- package/templates/fumadocs/content-template/en/docs/getting-started/installation.mdx +168 -0
- package/templates/fumadocs/content-template/en/docs/getting-started/quick-start.mdx +168 -0
- package/templates/fumadocs/content-template/en/docs/index.mdx +69 -0
- package/templates/fumadocs/messages/en.json +14 -0
- package/templates/fumadocs/messages/es.json +14 -0
- package/templates/fumadocs/next.config.mjs +35 -0
- package/templates/fumadocs/oxlint.json +14 -0
- package/templates/fumadocs/package.json +35 -0
- package/templates/fumadocs/postcss.config.mjs +5 -0
- package/templates/fumadocs/source.config.ts +31 -0
- package/templates/fumadocs/src/app/(home)/layout.tsx +6 -0
- package/templates/fumadocs/src/app/(home)/page.tsx +132 -0
- package/templates/fumadocs/src/app/api/search/route.ts +9 -0
- package/templates/fumadocs/src/app/docs/[[...slug]]/page.tsx +62 -0
- package/templates/fumadocs/src/app/docs/layout.tsx +11 -0
- package/templates/fumadocs/src/app/en/docs/[[...slug]]/page.tsx +61 -0
- package/templates/fumadocs/src/app/en/docs/layout.tsx +11 -0
- package/templates/fumadocs/src/app/global.css +3 -0
- package/templates/fumadocs/src/app/layout.tsx +47 -0
- package/templates/fumadocs/src/app/llms-full.txt/route.ts +10 -0
- package/templates/fumadocs/src/app/og/docs/[...slug]/route.tsx +27 -0
- package/templates/fumadocs/src/components/language-selector.tsx +56 -0
- package/templates/fumadocs/src/components/markdown-actions.tsx +61 -0
- package/templates/fumadocs/src/config/site.config.ts +115 -0
- package/templates/fumadocs/src/lib/layout.shared.tsx +23 -0
- package/templates/fumadocs/src/lib/source.ts +91 -0
- package/templates/fumadocs/src/mdx-components.tsx +14 -0
- package/templates/fumadocs/tsconfig.json +46 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { ArrowRight } from 'lucide-react';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
export default function HomePage() {
|
|
8
|
+
const [mounted, setMounted] = useState(false);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setMounted(true);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="min-h-screen bg-white dark:bg-black">
|
|
16
|
+
<main className="max-w-4xl mx-auto px-6 py-24">
|
|
17
|
+
{/* Header */}
|
|
18
|
+
<div className={`mb-20 transition-opacity duration-700 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
|
|
19
|
+
<h1 className="text-5xl md:text-7xl font-semibold tracking-tight text-gray-900 dark:text-white mb-6">
|
|
20
|
+
Telegram Bot Manager
|
|
21
|
+
</h1>
|
|
22
|
+
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-2xl leading-relaxed">
|
|
23
|
+
TypeScript library and CLI for automating Telegram bot management via @BotFather using GramJS MTProto.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{/* Code Example */}
|
|
28
|
+
<div className={`mb-20 transition-opacity duration-700 delay-200 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
|
|
29
|
+
<div className="flex items-center gap-2 mb-4">
|
|
30
|
+
<div className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-700" />
|
|
31
|
+
<div className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-700" />
|
|
32
|
+
<div className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-700" />
|
|
33
|
+
<span className="ml-2 text-xs text-gray-500 dark:text-gray-500 font-mono uppercase tracking-wide">Terminal</span>
|
|
34
|
+
</div>
|
|
35
|
+
<pre className="bg-gray-50 dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-lg p-6 overflow-x-auto">
|
|
36
|
+
<code className="text-sm font-mono text-gray-800 dark:text-gray-200">
|
|
37
|
+
<span className="text-gray-500 dark:text-gray-500">$</span> npx @mks2508/telegram-bot-manager bootstrap
|
|
38
|
+
<br />
|
|
39
|
+
<span className="text-green-600">→</span> Creating bot via BotFather...
|
|
40
|
+
<br />
|
|
41
|
+
<span className="text-green-600">→</span> Bot created: @my_bot
|
|
42
|
+
<br />
|
|
43
|
+
<span className="text-green-600">→</span> Forum created: -1001234567890
|
|
44
|
+
<br />
|
|
45
|
+
<span className="text-green-600">→</span> Topics configured: General, Control, Logs
|
|
46
|
+
<br />
|
|
47
|
+
<span className="text-green-600">→</span> Token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
|
48
|
+
</code>
|
|
49
|
+
</pre>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Features - Simple List */}
|
|
53
|
+
<div className={`mb-20 transition-opacity duration-700 delay-400 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
|
|
54
|
+
<h2 className="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-8">
|
|
55
|
+
Features
|
|
56
|
+
</h2>
|
|
57
|
+
<div className="space-y-6">
|
|
58
|
+
<div className="flex items-start gap-4">
|
|
59
|
+
<span className="text-blue-600 dark:text-blue-500 font-mono text-sm mt-0.5">01</span>
|
|
60
|
+
<div>
|
|
61
|
+
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">BotFather Automation</h3>
|
|
62
|
+
<p className="text-gray-600 dark:text-gray-400">Create bots, configure commands, set descriptions, retrieve tokens programmatically</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex items-start gap-4">
|
|
66
|
+
<span className="text-blue-600 dark:text-blue-500 font-mono text-sm mt-0.5">02</span>
|
|
67
|
+
<div>
|
|
68
|
+
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">CLI & Library</h3>
|
|
69
|
+
<p className="text-gray-600 dark:text-gray-400">Use via npx for quick automation or import as TypeScript library</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="flex items-start gap-4">
|
|
73
|
+
<span className="text-blue-600 dark:text-blue-500 font-mono text-sm mt-0.5">03</span>
|
|
74
|
+
<div>
|
|
75
|
+
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">Multi-Bot Support</h3>
|
|
76
|
+
<p className="text-gray-600 dark:text-gray-400">Manage multiple bots with environment-based configuration (local, staging, production)</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="flex items-start gap-4">
|
|
80
|
+
<span className="text-blue-600 dark:text-blue-500 font-mono text-sm mt-0.5">04</span>
|
|
81
|
+
<div>
|
|
82
|
+
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">Forum & Topics</h3>
|
|
83
|
+
<p className="text-gray-600 dark:text-gray-400">Create forum groups with custom topics, colors, and admin permissions</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* CTA */}
|
|
90
|
+
<div className={`transition-opacity duration-700 delay-600 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
|
|
91
|
+
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center">
|
|
92
|
+
<Link
|
|
93
|
+
href="/docs/introduccion/quick-start/"
|
|
94
|
+
className="inline-flex items-center gap-2 px-6 py-3 bg-gray-900 dark:bg-white text-white dark:text-black rounded font-medium hover:opacity-90 transition-opacity"
|
|
95
|
+
>
|
|
96
|
+
Get Started
|
|
97
|
+
<ArrowRight className="w-4 h-4" />
|
|
98
|
+
</Link>
|
|
99
|
+
<span className="text-gray-500 dark:text-gray-500">or</span>
|
|
100
|
+
<Link
|
|
101
|
+
href="/docs/introduccion/"
|
|
102
|
+
className="text-blue-600 dark:text-blue-500 hover:underline font-medium"
|
|
103
|
+
>
|
|
104
|
+
Read the docs
|
|
105
|
+
</Link>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Footer Links */}
|
|
110
|
+
<div className={`mt-32 pt-16 border-t border-gray-200 dark:border-gray-800 transition-opacity duration-700 delay-800 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
|
|
111
|
+
<nav className="flex flex-wrap gap-x-8 gap-y-4 text-sm">
|
|
112
|
+
<Link href="/docs/introduccion/installation/" className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
113
|
+
Installation
|
|
114
|
+
</Link>
|
|
115
|
+
<Link href="/docs/referencia-de-biblioteca/" className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
116
|
+
API Reference
|
|
117
|
+
</Link>
|
|
118
|
+
<Link href="/docs/referencia-de-cli/" className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
119
|
+
CLI Reference
|
|
120
|
+
</Link>
|
|
121
|
+
<Link href="/docs/configuracion/" className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
122
|
+
Configuration
|
|
123
|
+
</Link>
|
|
124
|
+
<Link href="/en/docs/" className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
125
|
+
English
|
|
126
|
+
</Link>
|
|
127
|
+
</nav>
|
|
128
|
+
</div>
|
|
129
|
+
</main>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Search API no compatible con static export (GitHub Pages)
|
|
2
|
+
// Redirigir a una página de búsqueda estática o eliminar
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-static';
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
return NextResponse.json({ message: 'Search not available in static export' }, { status: 501 });
|
|
9
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getPageImage, source, getRawMarkdownContent } from '@/lib/source';
|
|
2
|
+
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
4
|
+
import { getMDXComponents } from '@/mdx-components';
|
|
5
|
+
import { MarkdownActions } from '@/components/markdown-actions';
|
|
6
|
+
import type { Metadata } from 'next';
|
|
7
|
+
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
|
8
|
+
|
|
9
|
+
export default async function Page(props: PageProps<'/docs/[[...slug]]'>) {
|
|
10
|
+
const params = await props.params;
|
|
11
|
+
const page = source.getPage(params.slug);
|
|
12
|
+
if (!page) notFound();
|
|
13
|
+
|
|
14
|
+
const MDX = page.data.body;
|
|
15
|
+
const markdownContent = await getRawMarkdownContent(page);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<DocsPage
|
|
19
|
+
toc={page.data.toc}
|
|
20
|
+
full={page.data.full}
|
|
21
|
+
tableOfContent={{
|
|
22
|
+
header: (
|
|
23
|
+
<div className="mb-4">
|
|
24
|
+
<h3 className="text-xs font-semibold text-fd-muted-foreground uppercase">
|
|
25
|
+
Actions
|
|
26
|
+
</h3>
|
|
27
|
+
<MarkdownActions content={markdownContent} title={page.data.title} locale="es" />
|
|
28
|
+
</div>
|
|
29
|
+
),
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<DocsTitle>{page.data.title}</DocsTitle>
|
|
33
|
+
<DocsDescription>{page.data.description}</DocsDescription>
|
|
34
|
+
<DocsBody>
|
|
35
|
+
<MDX
|
|
36
|
+
components={getMDXComponents({
|
|
37
|
+
// this allows you to link to other pages with relative file paths
|
|
38
|
+
a: createRelativeLink(source, page),
|
|
39
|
+
})}
|
|
40
|
+
/>
|
|
41
|
+
</DocsBody>
|
|
42
|
+
</DocsPage>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function generateStaticParams() {
|
|
47
|
+
return source.generateParams();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function generateMetadata(props: PageProps<'/docs/[[...slug]]'>): Promise<Metadata> {
|
|
51
|
+
const params = await props.params;
|
|
52
|
+
const page = source.getPage(params.slug);
|
|
53
|
+
if (!page) notFound();
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
title: page.data.title,
|
|
57
|
+
description: page.data.description,
|
|
58
|
+
openGraph: {
|
|
59
|
+
images: getPageImage(page).url,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { source } from '@/lib/source';
|
|
2
|
+
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
3
|
+
import { baseOptions } from '@/lib/layout.shared';
|
|
4
|
+
|
|
5
|
+
export default function Layout({ children }: LayoutProps<'/docs'>) {
|
|
6
|
+
return (
|
|
7
|
+
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
|
|
8
|
+
{children}
|
|
9
|
+
</DocsLayout>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getPageImage, sourceEn, getRawMarkdownContent } from '@/lib/source';
|
|
2
|
+
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
4
|
+
import { getMDXComponents } from '@/mdx-components';
|
|
5
|
+
import { MarkdownActions } from '@/components/markdown-actions';
|
|
6
|
+
import type { Metadata } from 'next';
|
|
7
|
+
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
|
8
|
+
|
|
9
|
+
export default async function Page(props: PageProps<'/en/docs/[[...slug]]'>) {
|
|
10
|
+
const params = await props.params;
|
|
11
|
+
const page = sourceEn.getPage(params.slug);
|
|
12
|
+
if (!page) notFound();
|
|
13
|
+
|
|
14
|
+
const MDX = page.data.body;
|
|
15
|
+
const markdownContent = await getRawMarkdownContent(page);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<DocsPage
|
|
19
|
+
toc={page.data.toc}
|
|
20
|
+
full={page.data.full}
|
|
21
|
+
tableOfContent={{
|
|
22
|
+
header: (
|
|
23
|
+
<div className="mb-4">
|
|
24
|
+
<h3 className="text-xs font-semibold text-fd-muted-foreground uppercase">
|
|
25
|
+
Actions
|
|
26
|
+
</h3>
|
|
27
|
+
<MarkdownActions content={markdownContent} title={page.data.title} locale="en" />
|
|
28
|
+
</div>
|
|
29
|
+
),
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<DocsTitle>{page.data.title}</DocsTitle>
|
|
33
|
+
<DocsDescription>{page.data.description}</DocsDescription>
|
|
34
|
+
<DocsBody>
|
|
35
|
+
<MDX
|
|
36
|
+
components={getMDXComponents({
|
|
37
|
+
a: createRelativeLink(sourceEn, page),
|
|
38
|
+
})}
|
|
39
|
+
/>
|
|
40
|
+
</DocsBody>
|
|
41
|
+
</DocsPage>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function generateStaticParams() {
|
|
46
|
+
return sourceEn.generateParams();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function generateMetadata(props: PageProps<'/en/docs/[[...slug]]'>): Promise<Metadata> {
|
|
50
|
+
const params = await props.params;
|
|
51
|
+
const page = sourceEn.getPage(params.slug);
|
|
52
|
+
if (!page) notFound();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
title: page.data.title,
|
|
56
|
+
description: page.data.description,
|
|
57
|
+
openGraph: {
|
|
58
|
+
images: getPageImage(page).url,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { sourceEn } from '@/lib/source';
|
|
2
|
+
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
3
|
+
import { baseOptions } from '@/lib/layout.shared';
|
|
4
|
+
|
|
5
|
+
export default function Layout({ children }: LayoutProps<'/en/docs'>) {
|
|
6
|
+
return (
|
|
7
|
+
<DocsLayout tree={sourceEn.getPageTree()} {...baseOptions()}>
|
|
8
|
+
{children}
|
|
9
|
+
</DocsLayout>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { RootProvider } from 'fumadocs-ui/provider/next';
|
|
2
|
+
import './global.css';
|
|
3
|
+
import { Inter } from 'next/font/google';
|
|
4
|
+
import { LanguageSelector } from '@/components/language-selector';
|
|
5
|
+
import { siteConfig } from '@/config/site.config';
|
|
6
|
+
|
|
7
|
+
const inter = Inter({
|
|
8
|
+
subsets: ['latin'],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
title: {
|
|
13
|
+
default: siteConfig.name,
|
|
14
|
+
template: `%s | ${siteConfig.name}`,
|
|
15
|
+
},
|
|
16
|
+
description: siteConfig.description,
|
|
17
|
+
keywords: process.env.KEYWORDS?.split(',') || [],
|
|
18
|
+
authors: [{ name: siteConfig.author }],
|
|
19
|
+
openGraph: {
|
|
20
|
+
type: 'website' as const,
|
|
21
|
+
url: siteConfig.url,
|
|
22
|
+
title: siteConfig.name,
|
|
23
|
+
description: siteConfig.description,
|
|
24
|
+
siteName: siteConfig.name,
|
|
25
|
+
images: siteConfig.ogImageUrl ? [siteConfig.ogImageUrl] : [],
|
|
26
|
+
},
|
|
27
|
+
twitter: {
|
|
28
|
+
card: 'summary_large_image' as const,
|
|
29
|
+
title: siteConfig.name,
|
|
30
|
+
description: siteConfig.description,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function Layout({ children }: LayoutProps<'/'>) {
|
|
35
|
+
return (
|
|
36
|
+
<html lang="es" className={inter.className} suppressHydrationWarning>
|
|
37
|
+
<body className="flex flex-col min-h-screen bg-[var(--bg-primary)] text-[var(--text-primary)]">
|
|
38
|
+
<RootProvider>
|
|
39
|
+
<nav className="fixed top-4 right-4 z-50">
|
|
40
|
+
<LanguageSelector />
|
|
41
|
+
</nav>
|
|
42
|
+
{children}
|
|
43
|
+
</RootProvider>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getLLMText, source } from '@/lib/source';
|
|
2
|
+
|
|
3
|
+
export const revalidate = false;
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const scan = source.getPages().map(getLLMText);
|
|
7
|
+
const scanned = await Promise.all(scan);
|
|
8
|
+
|
|
9
|
+
return new Response(scanned.join('\n\n'));
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getPageImage, source } from '@/lib/source';
|
|
2
|
+
import { notFound } from 'next/navigation';
|
|
3
|
+
import { ImageResponse } from 'next/og';
|
|
4
|
+
import { generate as DefaultImage } from 'fumadocs-ui/og';
|
|
5
|
+
|
|
6
|
+
export const revalidate = false;
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) {
|
|
9
|
+
const { slug } = await params;
|
|
10
|
+
const page = source.getPage(slug.slice(0, -1));
|
|
11
|
+
if (!page) notFound();
|
|
12
|
+
|
|
13
|
+
return new ImageResponse(
|
|
14
|
+
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
|
|
15
|
+
{
|
|
16
|
+
width: 1200,
|
|
17
|
+
height: 630,
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function generateStaticParams() {
|
|
23
|
+
return source.getPages().map((page) => ({
|
|
24
|
+
lang: page.locale,
|
|
25
|
+
slug: getPageImage(page).segments,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
const languages = [
|
|
8
|
+
{ code: 'es', name: 'Español', path: '/docs' },
|
|
9
|
+
{ code: 'en', name: 'English', path: '/en/docs' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function LanguageSelector() {
|
|
13
|
+
const pathname = usePathname();
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
|
|
16
|
+
// Detect current language
|
|
17
|
+
const currentLang = pathname.startsWith('/en') ? 'en' : 'es';
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="relative">
|
|
21
|
+
<button
|
|
22
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
23
|
+
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-[var(--bg-secondary)] hover:bg-[var(--bg-tertiary)] transition-colors"
|
|
24
|
+
>
|
|
25
|
+
<span className="text-sm font-medium">
|
|
26
|
+
{languages.find(l => l.code === currentLang)?.name}
|
|
27
|
+
</span>
|
|
28
|
+
<svg
|
|
29
|
+
className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
|
30
|
+
fill="none"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
>
|
|
34
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
35
|
+
</svg>
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
{isOpen && (
|
|
39
|
+
<div className="absolute top-full right-0 mt-2 py-1 rounded-lg bg-[var(--bg-secondary)] border border-[var(--border-default)] shadow-lg z-50">
|
|
40
|
+
{languages.map((lang) => (
|
|
41
|
+
<Link
|
|
42
|
+
key={lang.code}
|
|
43
|
+
href={lang.path}
|
|
44
|
+
onClick={() => setIsOpen(false)}
|
|
45
|
+
className={`block px-4 py-2 text-sm hover:bg-[var(--bg-tertiary)] transition-colors ${
|
|
46
|
+
currentLang === lang.code ? 'text-[var(--color-primary)] font-medium' : 'text-[var(--text-secondary)]'
|
|
47
|
+
}`}
|
|
48
|
+
>
|
|
49
|
+
{lang.name}
|
|
50
|
+
</Link>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Copy, ExternalLink, Check } from 'lucide-react';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
|
|
6
|
+
interface MarkdownActionsProps {
|
|
7
|
+
content: string;
|
|
8
|
+
title: string;
|
|
9
|
+
locale: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function MarkdownActions({ content, title, locale }: MarkdownActionsProps) {
|
|
13
|
+
const [copied, setCopied] = useState(false);
|
|
14
|
+
|
|
15
|
+
const labels = locale === 'en'
|
|
16
|
+
? { copy: 'Copy as Markdown', open: 'Open as Markdown', copied: 'Copied!' }
|
|
17
|
+
: { copy: 'Copiar como Markdown', open: 'Abrir como Markdown', copied: '¡Copiado!' };
|
|
18
|
+
|
|
19
|
+
const handleCopy = async () => {
|
|
20
|
+
try {
|
|
21
|
+
await navigator.clipboard.writeText(content);
|
|
22
|
+
setCopied(true);
|
|
23
|
+
setTimeout(() => setCopied(false), 2000);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('Failed to copy:', err);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleOpen = () => {
|
|
30
|
+
const blob = new Blob([content], { type: 'text/markdown' });
|
|
31
|
+
const url = URL.createObjectURL(blob);
|
|
32
|
+
window.open(url, '_blank');
|
|
33
|
+
URL.revokeObjectURL(url);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-col gap-2">
|
|
38
|
+
<button
|
|
39
|
+
onClick={handleCopy}
|
|
40
|
+
className="flex items-center gap-2 px-3 py-2 text-sm rounded-md
|
|
41
|
+
text-fd-muted-foreground hover:bg-fd-accent hover:text-fd-accent-foreground
|
|
42
|
+
transition-colors"
|
|
43
|
+
aria-label={labels.copy}
|
|
44
|
+
>
|
|
45
|
+
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
|
46
|
+
<span>{copied ? labels.copied : labels.copy}</span>
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<button
|
|
50
|
+
onClick={handleOpen}
|
|
51
|
+
className="flex items-center gap-2 px-3 py-2 text-sm rounded-md
|
|
52
|
+
text-fd-muted-foreground hover:bg-fd-accent hover:text-fd-accent-foreground
|
|
53
|
+
transition-colors"
|
|
54
|
+
aria-label={labels.open}
|
|
55
|
+
>
|
|
56
|
+
<ExternalLink className="w-4 h-4" />
|
|
57
|
+
<span>{labels.open}</span>
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site configuration for Fumadocs template.
|
|
3
|
+
*
|
|
4
|
+
* This configuration object contains all site-specific values that would otherwise
|
|
5
|
+
* be hardcoded throughout the application. It uses environment variables with
|
|
6
|
+
* fallback defaults to support both template usage and production deployments.
|
|
7
|
+
*
|
|
8
|
+
* @module config/site.config
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Site configuration interface.
|
|
13
|
+
*
|
|
14
|
+
* Defines all configurable aspects of the documentation site including metadata,
|
|
15
|
+
* i18n settings, deployment configuration, and theme customization.
|
|
16
|
+
*
|
|
17
|
+
* @interface ISiteConfig
|
|
18
|
+
*/
|
|
19
|
+
export interface ISiteConfig {
|
|
20
|
+
/** Site name used in titles, headers, and metadata */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Site description for SEO and metadata */
|
|
23
|
+
description: string;
|
|
24
|
+
/** Author name for metadata and copyright */
|
|
25
|
+
author: string;
|
|
26
|
+
/** Full URL of the site (including protocol) */
|
|
27
|
+
url: string;
|
|
28
|
+
/** Optional custom OpenGraph image URL */
|
|
29
|
+
ogImageUrl?: string;
|
|
30
|
+
|
|
31
|
+
// i18n
|
|
32
|
+
/** Default locale for the documentation ('es' | 'en') */
|
|
33
|
+
defaultLocale: 'es' | 'en';
|
|
34
|
+
/** Array of supported locale codes */
|
|
35
|
+
supportedLocales: string[];
|
|
36
|
+
|
|
37
|
+
// Deployment
|
|
38
|
+
/** Base path for GitHub Pages or subdirectory deployment (e.g., '/docs') */
|
|
39
|
+
basePath: string;
|
|
40
|
+
/** Whether to append trailing slash to all URLs */
|
|
41
|
+
trailingSlash: boolean;
|
|
42
|
+
|
|
43
|
+
// Theme
|
|
44
|
+
/** Optional primary color override (CSS variable format) */
|
|
45
|
+
primaryColor?: string;
|
|
46
|
+
/** Logo configuration with SVG markup or text fallback */
|
|
47
|
+
logo?: {
|
|
48
|
+
/** Raw SVG markup for the logo */
|
|
49
|
+
svg?: string;
|
|
50
|
+
/** Text fallback when SVG is not provided */
|
|
51
|
+
text?: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Site configuration instance.
|
|
57
|
+
*
|
|
58
|
+
* Reads values from environment variables with sensible defaults for local development.
|
|
59
|
+
* In production, these should be set via `.env.local` or deployment platform variables.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```bash
|
|
63
|
+
* # .env.local
|
|
64
|
+
* PROJECT_NAME=My Docs
|
|
65
|
+
* DESCRIPTION=Documentation for My Project
|
|
66
|
+
* AUTHOR=Your Name
|
|
67
|
+
* BASE_PATH=/my-project
|
|
68
|
+
* DEFAULT_LOCALE=en
|
|
69
|
+
* SUPPORTED_LOCALES=en,es
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @constant
|
|
73
|
+
* @type {ISiteConfig}
|
|
74
|
+
*/
|
|
75
|
+
export const siteConfig: ISiteConfig = {
|
|
76
|
+
name: process.env.PROJECT_NAME || 'Mi Documentación',
|
|
77
|
+
description: process.env.DESCRIPTION || 'Documentación con Fumadocs',
|
|
78
|
+
author: process.env.AUTHOR || 'Autor',
|
|
79
|
+
|
|
80
|
+
url: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
|
|
81
|
+
|
|
82
|
+
ogImageUrl: process.env.OG_IMAGE_URL,
|
|
83
|
+
|
|
84
|
+
defaultLocale: (process.env.DEFAULT_LOCALE as 'es' | 'en') || 'es',
|
|
85
|
+
supportedLocales: process.env.SUPPORTED_LOCALES?.split(',') || ['es', 'en'],
|
|
86
|
+
|
|
87
|
+
basePath: process.env.BASE_PATH || '',
|
|
88
|
+
trailingSlash: process.env.TRAILING_SLASH === 'true',
|
|
89
|
+
|
|
90
|
+
primaryColor: process.env.PRIMARY_COLOR,
|
|
91
|
+
logo: process.env.LOGO_SVG
|
|
92
|
+
? { svg: process.env.LOGO_SVG }
|
|
93
|
+
: process.env.LOGO_TEXT
|
|
94
|
+
? { text: process.env.LOGO_TEXT }
|
|
95
|
+
: undefined,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default site configuration values for documentation purposes.
|
|
100
|
+
*
|
|
101
|
+
* These are the fallback values used when environment variables are not set.
|
|
102
|
+
*
|
|
103
|
+
* @constant
|
|
104
|
+
* @type {ISiteConfig}
|
|
105
|
+
*/
|
|
106
|
+
export const defaultSiteConfig: ISiteConfig = {
|
|
107
|
+
name: 'Mi Documentación',
|
|
108
|
+
description: 'Documentación con Fumadocs',
|
|
109
|
+
author: 'Autor',
|
|
110
|
+
url: 'http://localhost:3000',
|
|
111
|
+
defaultLocale: 'es',
|
|
112
|
+
supportedLocales: ['es', 'en'],
|
|
113
|
+
basePath: '',
|
|
114
|
+
trailingSlash: false,
|
|
115
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
|
2
|
+
import { siteConfig } from '@/config/site.config';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base layout options for Fumadocs.
|
|
6
|
+
*
|
|
7
|
+
* Provides the navigation title/branding for the documentation site.
|
|
8
|
+
* Uses siteConfig.logo if defined, otherwise falls back to siteConfig.name.
|
|
9
|
+
*
|
|
10
|
+
* @returns {BaseLayoutProps} Fumadocs base layout configuration
|
|
11
|
+
*/
|
|
12
|
+
export function baseOptions(): BaseLayoutProps {
|
|
13
|
+
const logoContent = siteConfig.logo?.svg
|
|
14
|
+
? // eslint-disable-next-line react/no-danger-with-children
|
|
15
|
+
<div dangerouslySetInnerHTML={{ __html: siteConfig.logo.svg }} />
|
|
16
|
+
: siteConfig.logo?.text || siteConfig.name;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
nav: {
|
|
20
|
+
title: logoContent,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|