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.
Files changed (84) hide show
  1. package/dist/bin.js +132 -1
  2. package/dist/templates/fumadocs/.env.example +49 -0
  3. package/dist/templates/fumadocs/.github/workflows/deploy.yml +89 -0
  4. package/dist/templates/fumadocs/CLAUDE.md +164 -0
  5. package/dist/templates/fumadocs/LICENSE +21 -0
  6. package/dist/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +269 -0
  7. package/dist/templates/fumadocs/README.md +319 -0
  8. package/dist/templates/fumadocs/biome.json +41 -0
  9. package/dist/templates/fumadocs/bun.lock +883 -0
  10. package/dist/templates/fumadocs/content-template/docs/getting-started/index.mdx +92 -0
  11. package/dist/templates/fumadocs/content-template/docs/getting-started/installation.mdx +168 -0
  12. package/dist/templates/fumadocs/content-template/docs/getting-started/quick-start.mdx +168 -0
  13. package/dist/templates/fumadocs/content-template/docs/index.mdx +70 -0
  14. package/dist/templates/fumadocs/content-template/en/docs/getting-started/index.mdx +92 -0
  15. package/dist/templates/fumadocs/content-template/en/docs/getting-started/installation.mdx +168 -0
  16. package/dist/templates/fumadocs/content-template/en/docs/getting-started/quick-start.mdx +168 -0
  17. package/dist/templates/fumadocs/content-template/en/docs/index.mdx +69 -0
  18. package/dist/templates/fumadocs/messages/en.json +14 -0
  19. package/dist/templates/fumadocs/messages/es.json +14 -0
  20. package/dist/templates/fumadocs/next.config.mjs +35 -0
  21. package/dist/templates/fumadocs/oxlint.json +14 -0
  22. package/dist/templates/fumadocs/package.json +35 -0
  23. package/dist/templates/fumadocs/postcss.config.mjs +5 -0
  24. package/dist/templates/fumadocs/source.config.ts +31 -0
  25. package/dist/templates/fumadocs/src/app/(home)/layout.tsx +6 -0
  26. package/dist/templates/fumadocs/src/app/(home)/page.tsx +132 -0
  27. package/dist/templates/fumadocs/src/app/api/search/route.ts +9 -0
  28. package/dist/templates/fumadocs/src/app/docs/[[...slug]]/page.tsx +62 -0
  29. package/dist/templates/fumadocs/src/app/docs/layout.tsx +11 -0
  30. package/dist/templates/fumadocs/src/app/en/docs/[[...slug]]/page.tsx +61 -0
  31. package/dist/templates/fumadocs/src/app/en/docs/layout.tsx +11 -0
  32. package/dist/templates/fumadocs/src/app/global.css +3 -0
  33. package/dist/templates/fumadocs/src/app/layout.tsx +47 -0
  34. package/dist/templates/fumadocs/src/app/llms-full.txt/route.ts +10 -0
  35. package/dist/templates/fumadocs/src/app/og/docs/[...slug]/route.tsx +27 -0
  36. package/dist/templates/fumadocs/src/components/language-selector.tsx +56 -0
  37. package/dist/templates/fumadocs/src/components/markdown-actions.tsx +61 -0
  38. package/dist/templates/fumadocs/src/config/site.config.ts +115 -0
  39. package/dist/templates/fumadocs/src/lib/layout.shared.tsx +23 -0
  40. package/dist/templates/fumadocs/src/lib/source.ts +91 -0
  41. package/dist/templates/fumadocs/src/mdx-components.tsx +14 -0
  42. package/dist/templates/fumadocs/tsconfig.json +46 -0
  43. package/package.json +1 -1
  44. package/templates/fumadocs/.env.example +49 -0
  45. package/templates/fumadocs/.github/workflows/deploy.yml +89 -0
  46. package/templates/fumadocs/CLAUDE.md +164 -0
  47. package/templates/fumadocs/LICENSE +21 -0
  48. package/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +269 -0
  49. package/templates/fumadocs/README.md +319 -0
  50. package/templates/fumadocs/biome.json +41 -0
  51. package/templates/fumadocs/bun.lock +883 -0
  52. package/templates/fumadocs/content-template/docs/getting-started/index.mdx +92 -0
  53. package/templates/fumadocs/content-template/docs/getting-started/installation.mdx +168 -0
  54. package/templates/fumadocs/content-template/docs/getting-started/quick-start.mdx +168 -0
  55. package/templates/fumadocs/content-template/docs/index.mdx +70 -0
  56. package/templates/fumadocs/content-template/en/docs/getting-started/index.mdx +92 -0
  57. package/templates/fumadocs/content-template/en/docs/getting-started/installation.mdx +168 -0
  58. package/templates/fumadocs/content-template/en/docs/getting-started/quick-start.mdx +168 -0
  59. package/templates/fumadocs/content-template/en/docs/index.mdx +69 -0
  60. package/templates/fumadocs/messages/en.json +14 -0
  61. package/templates/fumadocs/messages/es.json +14 -0
  62. package/templates/fumadocs/next.config.mjs +35 -0
  63. package/templates/fumadocs/oxlint.json +14 -0
  64. package/templates/fumadocs/package.json +35 -0
  65. package/templates/fumadocs/postcss.config.mjs +5 -0
  66. package/templates/fumadocs/source.config.ts +31 -0
  67. package/templates/fumadocs/src/app/(home)/layout.tsx +6 -0
  68. package/templates/fumadocs/src/app/(home)/page.tsx +132 -0
  69. package/templates/fumadocs/src/app/api/search/route.ts +9 -0
  70. package/templates/fumadocs/src/app/docs/[[...slug]]/page.tsx +62 -0
  71. package/templates/fumadocs/src/app/docs/layout.tsx +11 -0
  72. package/templates/fumadocs/src/app/en/docs/[[...slug]]/page.tsx +61 -0
  73. package/templates/fumadocs/src/app/en/docs/layout.tsx +11 -0
  74. package/templates/fumadocs/src/app/global.css +3 -0
  75. package/templates/fumadocs/src/app/layout.tsx +47 -0
  76. package/templates/fumadocs/src/app/llms-full.txt/route.ts +10 -0
  77. package/templates/fumadocs/src/app/og/docs/[...slug]/route.tsx +27 -0
  78. package/templates/fumadocs/src/components/language-selector.tsx +56 -0
  79. package/templates/fumadocs/src/components/markdown-actions.tsx +61 -0
  80. package/templates/fumadocs/src/config/site.config.ts +115 -0
  81. package/templates/fumadocs/src/lib/layout.shared.tsx +23 -0
  82. package/templates/fumadocs/src/lib/source.ts +91 -0
  83. package/templates/fumadocs/src/mdx-components.tsx +14 -0
  84. 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,3 @@
1
+ @import 'tailwindcss';
2
+ @import 'fumadocs-ui/css/neutral.css';
3
+ @import 'fumadocs-ui/css/preset.css';
@@ -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
+ }