docs-i18n 0.6.3 → 0.7.0
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/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
- package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
- package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
- package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
- package/admin/app/routeTree.gen.ts +68 -0
- package/admin/app/router.tsx +23 -0
- package/admin/app/routes/__root.tsx +55 -0
- package/admin/app/routes/index.tsx +416 -0
- package/{src/admin/ui → admin/app}/styles.css +36 -3
- package/admin/package.json +27 -0
- package/admin/server/functions/jobs.ts +53 -0
- package/admin/server/functions/misc.ts +84 -0
- package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
- package/admin/server/functions/status.ts +61 -0
- package/admin/server/index.ts +35 -0
- package/admin/server/init.ts +46 -0
- package/{src/admin → admin}/server/services/job-manager.ts +39 -10
- package/{src/admin → admin}/server/services/status.ts +6 -6
- package/admin/tsconfig.json +19 -0
- package/{src/admin → admin}/vite.config.ts +8 -2
- package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
- package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
- package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
- package/dist/chunk-L64GJ4OB.js +32 -0
- package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
- package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
- package/dist/chunk-TRURQFP4.js +31 -0
- package/dist/cli.js +108 -7
- package/dist/index.d.ts +41 -1
- package/dist/index.js +92 -3
- package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
- package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
- package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
- package/dist/upload-XL6KG6S2.js +132 -0
- package/package.json +17 -15
- package/template/app/components/BlogArticle.tsx +159 -0
- package/template/app/components/BlogList.tsx +88 -0
- package/template/app/components/Breadcrumbs.tsx +81 -0
- package/template/app/components/Card.tsx +31 -0
- package/template/app/components/Doc.tsx +191 -0
- package/template/app/components/DocBreadcrumb.tsx +60 -0
- package/template/app/components/DocContainer.tsx +13 -0
- package/template/app/components/DocTitle.tsx +11 -0
- package/template/app/components/DocsLayout.tsx +715 -0
- package/template/app/components/Dropdown.tsx +116 -0
- package/template/app/components/FallbackBanner.tsx +36 -0
- package/template/app/components/Footer.tsx +29 -0
- package/template/app/components/FrameworkSelect.tsx +150 -0
- package/template/app/components/LibraryCard.tsx +178 -0
- package/template/app/components/LocaleSwitcher.tsx +43 -0
- package/template/app/components/Navbar.tsx +430 -0
- package/template/app/components/PostNotFound.tsx +20 -0
- package/template/app/components/SearchButton.tsx +32 -0
- package/template/app/components/Select.tsx +103 -0
- package/template/app/components/Spinner.tsx +18 -0
- package/template/app/components/ThemeProvider.tsx +141 -0
- package/template/app/components/ThemeToggle.tsx +31 -0
- package/template/app/components/Toc.tsx +86 -0
- package/template/app/components/VersionSelect.tsx +118 -0
- package/template/app/components/icons/BSkyIcon.tsx +27 -0
- package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
- package/template/app/components/icons/BrandXIcon.tsx +28 -0
- package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
- package/template/app/components/icons/CogsIcon.tsx +25 -0
- package/template/app/components/icons/DiscordIcon.tsx +24 -0
- package/template/app/components/icons/GithubIcon.tsx +24 -0
- package/template/app/components/icons/GoogleIcon.tsx +24 -0
- package/template/app/components/icons/InstagramIcon.tsx +24 -0
- package/template/app/components/icons/NpmIcon.tsx +26 -0
- package/template/app/components/icons/YinYangIcon.tsx +26 -0
- package/template/app/components/icons/YouTubeIcon.tsx +24 -0
- package/template/app/components/markdown/CodeBlock.tsx +254 -0
- package/template/app/components/markdown/FileTabs.tsx +58 -0
- package/template/app/components/markdown/FrameworkContent.tsx +76 -0
- package/template/app/components/markdown/Markdown.tsx +216 -0
- package/template/app/components/markdown/MarkdownContent.tsx +89 -0
- package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
- package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
- package/template/app/components/markdown/MarkdownLink.tsx +46 -0
- package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
- package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
- package/template/app/components/markdown/Tabs.tsx +139 -0
- package/template/app/components/markdown/index.ts +15 -0
- package/template/app/components/ui/Button.tsx +141 -0
- package/template/app/components/ui/InlineCode.tsx +16 -0
- package/template/app/components/ui/MarkdownImg.tsx +21 -0
- package/template/app/config/frameworks.ts +93 -0
- package/template/app/contexts/SearchContext.tsx +36 -0
- package/template/app/db/index.ts +17 -0
- package/template/app/db/schema.ts +74 -0
- package/template/app/hooks/useClickOutside.ts +106 -0
- package/template/app/routeTree.gen.ts +584 -0
- package/template/app/router.tsx +29 -0
- package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
- package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
- package/template/app/routes/$lang.$project.$version.tsx +69 -0
- package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
- package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
- package/template/app/routes/$lang.$project.docs.tsx +79 -0
- package/template/app/routes/$lang.$project.tsx +89 -0
- package/template/app/routes/$lang.blog.$.tsx +82 -0
- package/template/app/routes/$lang.blog.index.tsx +56 -0
- package/template/app/routes/$lang.blog.tsx +26 -0
- package/template/app/routes/$lang.docs.$.tsx +100 -0
- package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
- package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
- package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
- package/template/app/routes/$lang.docs.index.tsx +20 -0
- package/template/app/routes/$lang.docs.tsx +90 -0
- package/template/app/routes/$lang.tsx +16 -0
- package/template/app/routes/__root.tsx +180 -0
- package/template/app/routes/index.tsx +89 -0
- package/template/app/site.config.ts +182 -0
- package/template/app/styles/app.css +1029 -0
- package/template/app/types/index.ts +77 -0
- package/template/app/utils/blog.server.ts +193 -0
- package/template/app/utils/blog.ts +42 -0
- package/template/app/utils/config.ts +120 -0
- package/template/app/utils/content-loader.ts +400 -0
- package/template/app/utils/dates.ts +29 -0
- package/template/app/utils/docs.server.ts +150 -0
- package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
- package/template/app/utils/markdown/index.ts +2 -0
- package/template/app/utils/markdown/installCommand.ts +143 -0
- package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
- package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
- package/template/app/utils/markdown/plugins/helpers.ts +33 -0
- package/template/app/utils/markdown/plugins/index.ts +8 -0
- package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
- package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
- package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
- package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
- package/template/app/utils/markdown/processor.ts +75 -0
- package/template/app/utils/site-config.tsx +11 -0
- package/template/app/utils/upload.ts +232 -0
- package/template/app/utils/useLocalStorage.ts +65 -0
- package/template/app/utils/utils.ts +23 -0
- package/template/package.json +54 -0
- package/template/public/favicon.svg +1 -0
- package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
- package/template/public/fonts/Inter-latin.woff2 +0 -0
- package/template/public/images/frameworks/angular-logo.svg +1 -0
- package/template/public/images/frameworks/js-logo.svg +1 -0
- package/template/public/images/frameworks/lit-logo.svg +1 -0
- package/template/public/images/frameworks/preact-logo.svg +6 -0
- package/template/public/images/frameworks/qwik-logo.svg +1 -0
- package/template/public/images/frameworks/react-logo.svg +1 -0
- package/template/public/images/frameworks/solid-logo.svg +1 -0
- package/template/public/images/frameworks/svelte-logo.svg +1 -0
- package/template/public/images/frameworks/vue-logo.svg +4 -0
- package/template/tsconfig.json +24 -0
- package/template/vite.config.ts +43 -0
- package/template/wrangler.jsonc +16 -0
- package/README.md +0 -161
- package/dist/server-73AVSOL5.js +0 -598
- package/src/admin/index.html +0 -13
- package/src/admin/server/index.ts +0 -138
- package/src/admin/server/routes/jobs.ts +0 -113
- package/src/admin/server/routes/status.ts +0 -57
- package/src/admin/ui/App.tsx +0 -332
- package/src/admin/ui/main.tsx +0 -19
- /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
- /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { notFound, Outlet, useParams } from '@tanstack/react-router'
|
|
3
|
+
import { findProject, siteConfig } from '~/site.config'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/$lang/$project')({
|
|
6
|
+
beforeLoad: (ctx) => {
|
|
7
|
+
const project = findProject(ctx.params.project)
|
|
8
|
+
|
|
9
|
+
if (!project) {
|
|
10
|
+
throw notFound()
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
loader: (ctx) => {
|
|
14
|
+
const project = findProject(ctx.params.project)
|
|
15
|
+
|
|
16
|
+
if (!project) {
|
|
17
|
+
throw notFound()
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
head: (ctx) => {
|
|
21
|
+
const project = findProject(ctx.params.project)
|
|
22
|
+
|
|
23
|
+
if (!project) {
|
|
24
|
+
return {
|
|
25
|
+
meta: [{ title: '404 Not Found' }],
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const frameworkTitle = project.frameworks?.length
|
|
30
|
+
? ` | ${project.frameworks
|
|
31
|
+
.map(
|
|
32
|
+
(framework) =>
|
|
33
|
+
`${framework.charAt(0).toUpperCase()}${framework.slice(1)} ${project.name}`,
|
|
34
|
+
)
|
|
35
|
+
.join(', ')}`
|
|
36
|
+
: ''
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
meta: [
|
|
40
|
+
{
|
|
41
|
+
title: `${project.name}${frameworkTitle}`,
|
|
42
|
+
},
|
|
43
|
+
...(project.description
|
|
44
|
+
? [{ name: 'description', content: project.description }]
|
|
45
|
+
: []),
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
component: ProjectLayout,
|
|
50
|
+
staticData: {
|
|
51
|
+
Title: () => {
|
|
52
|
+
const { project: projectId } = Route.useParams()
|
|
53
|
+
const { version } = useParams({ strict: false })
|
|
54
|
+
const project = findProject(projectId)
|
|
55
|
+
|
|
56
|
+
if (!project) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projectName = project.name
|
|
61
|
+
const resolvedVersion =
|
|
62
|
+
version === 'latest' ? project.latestVersion : version!
|
|
63
|
+
const gradientText = `inline-block text-transparent bg-clip-text bg-linear-to-r ${project.colorFrom} ${project.colorTo}`
|
|
64
|
+
return (
|
|
65
|
+
<Link
|
|
66
|
+
to={`/$lang/$project`}
|
|
67
|
+
params={{ lang: siteConfig.defaultLocale, project: projectId }}
|
|
68
|
+
className="relative whitespace-nowrap"
|
|
69
|
+
>
|
|
70
|
+
<span className={`${gradientText}`}>{projectName}</span>{' '}
|
|
71
|
+
{resolvedVersion && resolvedVersion !== 'latest' && (
|
|
72
|
+
<>
|
|
73
|
+
<span className="text-sm absolute right-0 top-0 font-normal normal-case">
|
|
74
|
+
{resolvedVersion}
|
|
75
|
+
</span>
|
|
76
|
+
<span className="text-sm opacity-0 normal-case">
|
|
77
|
+
{resolvedVersion}
|
|
78
|
+
</span>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
</Link>
|
|
82
|
+
)
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
function ProjectLayout() {
|
|
88
|
+
return <Outlet />
|
|
89
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog article route — renders a single blog post.
|
|
3
|
+
*
|
|
4
|
+
* Loads post content via server function with i18n fallback.
|
|
5
|
+
* Uses splat route to support any slug depth (e.g., /en/blog/my-post).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { notFound, createFileRoute } from '@tanstack/react-router'
|
|
9
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
10
|
+
import { siteConfig } from '~/site.config'
|
|
11
|
+
import { formatAuthors } from '~/utils/blog'
|
|
12
|
+
import { BlogArticle } from '~/components/BlogArticle'
|
|
13
|
+
import { PostNotFound } from '~/components/PostNotFound'
|
|
14
|
+
|
|
15
|
+
const fetchBlogPost = createServerFn({ method: 'GET' })
|
|
16
|
+
.inputValidator((data: { lang: string; slug: string }) => data)
|
|
17
|
+
.handler(async ({ data }) => {
|
|
18
|
+
const { loadBlogPost } = await import('~/utils/blog.server')
|
|
19
|
+
const post = loadBlogPost(data.lang, data.slug)
|
|
20
|
+
|
|
21
|
+
if (!post) {
|
|
22
|
+
throw notFound()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Hide drafts and future-dated posts
|
|
26
|
+
const now = new Date()
|
|
27
|
+
const publishDate = new Date(post.published)
|
|
28
|
+
const isUnpublished = post.draft || publishDate > now
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...post,
|
|
32
|
+
isUnpublished,
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export const Route = createFileRoute('/$lang/blog/$')({
|
|
38
|
+
staleTime: Infinity,
|
|
39
|
+
loader: ({ params }) =>
|
|
40
|
+
fetchBlogPost({
|
|
41
|
+
data: {
|
|
42
|
+
lang: params.lang,
|
|
43
|
+
slug: params._splat || '',
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
head: ({ loaderData }) => ({
|
|
47
|
+
meta: loaderData
|
|
48
|
+
? [
|
|
49
|
+
{
|
|
50
|
+
title: `${loaderData.title} | ${siteConfig.name} Blog`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'description',
|
|
54
|
+
content: loaderData.excerpt,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'author',
|
|
58
|
+
content: `${
|
|
59
|
+
loaderData.authors.length > 1 ? 'co-authored by ' : ''
|
|
60
|
+
}${formatAuthors(loaderData.authors)}`,
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
: [],
|
|
64
|
+
}),
|
|
65
|
+
notFoundComponent: () => <PostNotFound />,
|
|
66
|
+
component: BlogPostPage,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function BlogPostPage() {
|
|
70
|
+
const post = Route.useLoaderData()
|
|
71
|
+
const { lang } = Route.useParams()
|
|
72
|
+
const localeName =
|
|
73
|
+
siteConfig.supportedLocales[lang] || lang
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<BlogArticle
|
|
77
|
+
post={post}
|
|
78
|
+
lang={lang}
|
|
79
|
+
locale={localeName}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog index route — lists all published blog posts.
|
|
3
|
+
*
|
|
4
|
+
* Loads blog post metadata via server function, renders using BlogList component.
|
|
5
|
+
* Posts are sorted by published date (newest first), drafts are excluded.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
9
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
10
|
+
import { siteConfig } from '~/site.config'
|
|
11
|
+
import { BlogList } from '~/components/BlogList'
|
|
12
|
+
import { PostNotFound } from '~/components/PostNotFound'
|
|
13
|
+
|
|
14
|
+
const fetchBlogPosts = createServerFn({ method: 'GET' })
|
|
15
|
+
.inputValidator((data: { lang: string }) => data)
|
|
16
|
+
.handler(async ({ data }) => {
|
|
17
|
+
const { loadBlogPosts } = await import('~/utils/blog.server')
|
|
18
|
+
return loadBlogPosts(data.lang)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/$lang/blog/')({
|
|
22
|
+
staleTime: Infinity,
|
|
23
|
+
loader: ({ params }) => fetchBlogPosts({ data: { lang: params.lang } }),
|
|
24
|
+
notFoundComponent: () => <PostNotFound />,
|
|
25
|
+
head: () => ({
|
|
26
|
+
meta: [
|
|
27
|
+
{
|
|
28
|
+
title: `Blog | ${siteConfig.name}`,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
}),
|
|
32
|
+
component: BlogIndex,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function BlogIndex() {
|
|
36
|
+
const posts = Route.useLoaderData()
|
|
37
|
+
const { lang } = Route.useParams()
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex flex-col max-w-full min-h-screen gap-12 p-4 md:p-8 pb-0">
|
|
41
|
+
<div className="flex-1 w-full max-w-[1400px] mx-auto">
|
|
42
|
+
<div className="flex gap-8 items-start">
|
|
43
|
+
<div className="flex-1 space-y-12 min-w-0">
|
|
44
|
+
<header>
|
|
45
|
+
<h1 className="text-3xl font-black">Blog</h1>
|
|
46
|
+
<p className="text-lg mt-4 text-gray-600 dark:text-gray-400">
|
|
47
|
+
The latest news and blog posts
|
|
48
|
+
</p>
|
|
49
|
+
</header>
|
|
50
|
+
<BlogList posts={posts} lang={lang} />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog layout route.
|
|
3
|
+
*
|
|
4
|
+
* Simple wrapper — no sidebar, just navbar + content.
|
|
5
|
+
* The navbar is already rendered by __root.tsx.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
|
9
|
+
import { siteConfig } from '~/site.config'
|
|
10
|
+
import { PostNotFound } from '~/components/PostNotFound'
|
|
11
|
+
|
|
12
|
+
export const Route = createFileRoute('/$lang/blog')({
|
|
13
|
+
head: () => ({
|
|
14
|
+
meta: [
|
|
15
|
+
{
|
|
16
|
+
title: `Blog | ${siteConfig.name}`,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
}),
|
|
20
|
+
component: BlogLayout,
|
|
21
|
+
notFoundComponent: PostNotFound,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function BlogLayout() {
|
|
25
|
+
return <Outlet />
|
|
26
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFileRoute,
|
|
3
|
+
notFound,
|
|
4
|
+
} from '@tanstack/react-router'
|
|
5
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
6
|
+
import { siteConfig, isSingleProject, getSingleProject } from '~/site.config'
|
|
7
|
+
import { Doc } from '~/components/Doc'
|
|
8
|
+
|
|
9
|
+
const fetchDoc = createServerFn({ method: 'GET' })
|
|
10
|
+
.inputValidator(
|
|
11
|
+
(data: { project: string; version: string; lang: string; slug: string }) =>
|
|
12
|
+
data,
|
|
13
|
+
)
|
|
14
|
+
.handler(async ({ data }) => {
|
|
15
|
+
const { loadDoc } = await import('~/utils/docs.server')
|
|
16
|
+
return loadDoc(data.project, data.version, data.lang, data.slug)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const Route = createFileRoute('/$lang/docs/$')({
|
|
20
|
+
staleTime: 1000 * 60 * 5,
|
|
21
|
+
beforeLoad: () => {
|
|
22
|
+
if (!isSingleProject()) {
|
|
23
|
+
throw notFound()
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
loader: async ({ params }) => {
|
|
27
|
+
const { lang, _splat: slug } = params
|
|
28
|
+
if (!slug) throw notFound()
|
|
29
|
+
|
|
30
|
+
const project = getSingleProject()
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
return await fetchDoc({
|
|
34
|
+
data: {
|
|
35
|
+
project: project.id,
|
|
36
|
+
version: project.latestVersion,
|
|
37
|
+
lang,
|
|
38
|
+
slug,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw notFound()
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
head: ({ loaderData }) => {
|
|
46
|
+
const project = getSingleProject()
|
|
47
|
+
const title = loaderData?.meta?.title
|
|
48
|
+
const projectName = project?.name || siteConfig.name
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
meta: [
|
|
52
|
+
{
|
|
53
|
+
title: title ? `${title} | ${projectName} Docs` : `${projectName} Docs`,
|
|
54
|
+
},
|
|
55
|
+
...(loaderData?.meta?.description
|
|
56
|
+
? [{ name: 'description', content: loaderData.meta.description }]
|
|
57
|
+
: []),
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
headers: () => {
|
|
62
|
+
return {
|
|
63
|
+
'cache-control': 'public, max-age=60, must-revalidate',
|
|
64
|
+
'cdn-cache-control':
|
|
65
|
+
'max-age=600, stale-while-revalidate=3600, durable',
|
|
66
|
+
vary: 'Accept-Encoding',
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
component: SingleProjectDocsPage,
|
|
70
|
+
errorComponent: DocNotFound,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function SingleProjectDocsPage() {
|
|
74
|
+
const loaderData = Route.useLoaderData()
|
|
75
|
+
const project = getSingleProject()
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Doc
|
|
79
|
+
title={loaderData.meta?.title || ''}
|
|
80
|
+
content={loaderData.content}
|
|
81
|
+
isFallback={loaderData.isFallback}
|
|
82
|
+
filePath={loaderData.filePath}
|
|
83
|
+
repo={project.repo}
|
|
84
|
+
branch={project.latestBranch}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function DocNotFound() {
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex-1 flex items-center justify-center py-20">
|
|
92
|
+
<div className="text-center">
|
|
93
|
+
<h1 className="text-2xl font-bold mb-2">Page Not Found</h1>
|
|
94
|
+
<p className="text-gray-500 dark:text-gray-400">
|
|
95
|
+
The document you are looking for does not exist.
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFileRoute,
|
|
3
|
+
notFound,
|
|
4
|
+
redirect,
|
|
5
|
+
} from '@tanstack/react-router'
|
|
6
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
7
|
+
import { siteConfig, isSingleProject, getSingleProject } from '~/site.config'
|
|
8
|
+
import { Doc } from '~/components/Doc'
|
|
9
|
+
|
|
10
|
+
const fetchFrameworkDoc = createServerFn({ method: 'GET' })
|
|
11
|
+
.inputValidator(
|
|
12
|
+
(data: {
|
|
13
|
+
project: string
|
|
14
|
+
version: string
|
|
15
|
+
lang: string
|
|
16
|
+
framework: string
|
|
17
|
+
slug: string
|
|
18
|
+
}) => data,
|
|
19
|
+
)
|
|
20
|
+
.handler(async ({ data }) => {
|
|
21
|
+
const { loadDoc } = await import('~/utils/docs.server')
|
|
22
|
+
return loadDoc(
|
|
23
|
+
data.project,
|
|
24
|
+
data.version,
|
|
25
|
+
data.lang,
|
|
26
|
+
`framework/${data.framework}/${data.slug}`,
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const Route = createFileRoute(
|
|
31
|
+
'/$lang/docs/framework/$framework/$',
|
|
32
|
+
)({
|
|
33
|
+
staleTime: 1000 * 60 * 5,
|
|
34
|
+
beforeLoad: () => {
|
|
35
|
+
if (!isSingleProject()) {
|
|
36
|
+
throw notFound()
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
loader: async ({ params }) => {
|
|
40
|
+
const { lang, framework, _splat: slug } = params
|
|
41
|
+
const project = getSingleProject()
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return await fetchFrameworkDoc({
|
|
45
|
+
data: {
|
|
46
|
+
project: project.id,
|
|
47
|
+
version: project.latestVersion,
|
|
48
|
+
lang,
|
|
49
|
+
framework,
|
|
50
|
+
slug: slug || '',
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw redirect({
|
|
55
|
+
to: '/$lang/docs/framework/$framework',
|
|
56
|
+
params: { lang, framework },
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
head: ({ loaderData, params }) => {
|
|
61
|
+
const project = getSingleProject()
|
|
62
|
+
const projectName = project?.name || siteConfig.name
|
|
63
|
+
const framework =
|
|
64
|
+
params.framework.charAt(0).toUpperCase() + params.framework.slice(1)
|
|
65
|
+
const title = loaderData?.meta?.title
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
meta: [
|
|
69
|
+
{
|
|
70
|
+
title: title
|
|
71
|
+
? `${title} | ${projectName} ${framework} Docs`
|
|
72
|
+
: `${projectName} ${framework} Docs`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
headers: () => {
|
|
78
|
+
return {
|
|
79
|
+
'cache-control': 'public, max-age=60, must-revalidate',
|
|
80
|
+
'cdn-cache-control':
|
|
81
|
+
'max-age=600, stale-while-revalidate=3600, durable',
|
|
82
|
+
vary: 'Accept-Encoding',
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
component: FrameworkDocsPage,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
function FrameworkDocsPage() {
|
|
89
|
+
const loaderData = Route.useLoaderData()
|
|
90
|
+
const { framework } = Route.useParams()
|
|
91
|
+
const project = getSingleProject()
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Doc
|
|
95
|
+
title={loaderData.meta?.title || ''}
|
|
96
|
+
content={loaderData.content}
|
|
97
|
+
isFallback={loaderData.isFallback}
|
|
98
|
+
filePath={loaderData.filePath}
|
|
99
|
+
repo={project.repo}
|
|
100
|
+
branch={project.latestBranch}
|
|
101
|
+
framework={framework}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createFileRoute, notFound } from '@tanstack/react-router'
|
|
2
|
+
import { isSingleProject, getSingleProject } from '~/site.config'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute(
|
|
5
|
+
'/$lang/docs/framework/$framework/',
|
|
6
|
+
)({
|
|
7
|
+
beforeLoad: () => {
|
|
8
|
+
if (!isSingleProject()) {
|
|
9
|
+
throw notFound()
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
component: FrameworkIndex,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function FrameworkIndex() {
|
|
16
|
+
const { framework } = Route.useParams()
|
|
17
|
+
const project = getSingleProject()
|
|
18
|
+
const frameworkLabel =
|
|
19
|
+
framework.charAt(0).toUpperCase() + framework.slice(1)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="py-8 max-w-3xl mx-auto">
|
|
23
|
+
<h1 className="text-3xl font-bold mb-4">
|
|
24
|
+
{project?.name} {frameworkLabel} Documentation
|
|
25
|
+
</h1>
|
|
26
|
+
<div className="h-px bg-gray-200 dark:bg-gray-700 mb-6" />
|
|
27
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
28
|
+
Use the sidebar to select a documentation page.
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createFileRoute, Link, notFound } from '@tanstack/react-router'
|
|
2
|
+
import { isSingleProject, getSingleProject } from '~/site.config'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/$lang/docs/framework/')({
|
|
5
|
+
beforeLoad: () => {
|
|
6
|
+
if (!isSingleProject()) {
|
|
7
|
+
throw notFound()
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
component: FrameworkSelectionPage,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
function FrameworkSelectionPage() {
|
|
14
|
+
const { lang } = Route.useParams()
|
|
15
|
+
const project = getSingleProject()
|
|
16
|
+
const frameworks = project?.frameworks || []
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="py-8 max-w-3xl mx-auto">
|
|
20
|
+
<h1 className="text-3xl font-bold mb-4">
|
|
21
|
+
{project?.name} Frameworks
|
|
22
|
+
</h1>
|
|
23
|
+
<div className="h-px bg-gray-200 dark:bg-gray-700 mb-6" />
|
|
24
|
+
|
|
25
|
+
{frameworks.length > 0 ? (
|
|
26
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
27
|
+
{frameworks.map((fw) => (
|
|
28
|
+
<Link
|
|
29
|
+
key={fw}
|
|
30
|
+
to="/$lang/docs/framework/$framework"
|
|
31
|
+
params={{ lang, framework: fw }}
|
|
32
|
+
className="p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
33
|
+
>
|
|
34
|
+
<span className="text-lg font-medium">
|
|
35
|
+
{fw.charAt(0).toUpperCase() + fw.slice(1)}
|
|
36
|
+
</span>
|
|
37
|
+
</Link>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
) : (
|
|
41
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
42
|
+
No frameworks are configured for this project.
|
|
43
|
+
</p>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createFileRoute, notFound, redirect } from '@tanstack/react-router'
|
|
2
|
+
import { isSingleProject, getSingleProject } from '~/site.config'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/$lang/docs/')({
|
|
5
|
+
beforeLoad: () => {
|
|
6
|
+
if (!isSingleProject()) {
|
|
7
|
+
throw notFound()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const project = getSingleProject()
|
|
11
|
+
|
|
12
|
+
throw redirect({
|
|
13
|
+
from: '/$lang/docs',
|
|
14
|
+
to: './$',
|
|
15
|
+
params: {
|
|
16
|
+
_splat: project.defaultDocs || 'overview',
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
},
|
|
20
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
createFileRoute,
|
|
4
|
+
notFound,
|
|
5
|
+
Outlet,
|
|
6
|
+
Link,
|
|
7
|
+
} from '@tanstack/react-router'
|
|
8
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
9
|
+
import { siteConfig, findProject, isSingleProject, getSingleProject } from '~/site.config'
|
|
10
|
+
import { DocsLayout } from '~/components/DocsLayout'
|
|
11
|
+
import { frameworkOptions } from '~/config/frameworks'
|
|
12
|
+
import type { Framework } from '~/config/frameworks'
|
|
13
|
+
|
|
14
|
+
const fetchDocsConfig = createServerFn({ method: 'GET' })
|
|
15
|
+
.inputValidator((data: { project: string; version: string }) => data)
|
|
16
|
+
.handler(async ({ data }) => {
|
|
17
|
+
const { loadDocsConfig } = await import('~/utils/docs.server')
|
|
18
|
+
return loadDocsConfig(data.project, data.version)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/$lang/docs')({
|
|
22
|
+
staleTime: 1000 * 60 * 5,
|
|
23
|
+
beforeLoad: () => {
|
|
24
|
+
// This route only works for single-project sites
|
|
25
|
+
if (!isSingleProject()) {
|
|
26
|
+
throw notFound()
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
loader: async () => {
|
|
30
|
+
const project = getSingleProject()
|
|
31
|
+
const config = await fetchDocsConfig({
|
|
32
|
+
data: { project: project.id, version: project.latestVersion },
|
|
33
|
+
})
|
|
34
|
+
return { config }
|
|
35
|
+
},
|
|
36
|
+
component: SingleProjectDocsLayout,
|
|
37
|
+
head: () => {
|
|
38
|
+
const project = getSingleProject()
|
|
39
|
+
return {
|
|
40
|
+
meta: [{ title: project ? `${project.name} Docs` : siteConfig.name }],
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
headers: () => ({
|
|
44
|
+
'cache-control': 'public, max-age=0, must-revalidate',
|
|
45
|
+
'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable',
|
|
46
|
+
}),
|
|
47
|
+
staticData: {
|
|
48
|
+
Title: () => {
|
|
49
|
+
const project = getSingleProject()
|
|
50
|
+
const gradientText = `inline-block text-transparent bg-clip-text bg-linear-to-r ${project.colorFrom} ${project.colorTo}`
|
|
51
|
+
return (
|
|
52
|
+
<Link
|
|
53
|
+
to="/$lang/docs"
|
|
54
|
+
params={{ lang: siteConfig.defaultLocale }}
|
|
55
|
+
className="relative whitespace-nowrap"
|
|
56
|
+
>
|
|
57
|
+
<span className={gradientText}>{project.name}</span>
|
|
58
|
+
</Link>
|
|
59
|
+
)
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function SingleProjectDocsLayout() {
|
|
65
|
+
const { config } = Route.useLoaderData()
|
|
66
|
+
const project = getSingleProject()
|
|
67
|
+
|
|
68
|
+
// Map string framework IDs to Framework type
|
|
69
|
+
const frameworks: Framework[] = (project.frameworks ?? []).filter(
|
|
70
|
+
(f): f is Framework =>
|
|
71
|
+
frameworkOptions.some((opt) => opt.value === f),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<DocsLayout
|
|
76
|
+
name={project.name}
|
|
77
|
+
version={project.latestVersion}
|
|
78
|
+
colorFrom={project.colorFrom}
|
|
79
|
+
colorTo={project.colorTo}
|
|
80
|
+
textColor={project.textColor ?? ''}
|
|
81
|
+
config={config}
|
|
82
|
+
frameworks={frameworks}
|
|
83
|
+
versions={project.availableVersions}
|
|
84
|
+
repo={project.repo}
|
|
85
|
+
docsBasePath="/$lang/docs"
|
|
86
|
+
>
|
|
87
|
+
<Outlet />
|
|
88
|
+
</DocsLayout>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createFileRoute, notFound, Outlet } from '@tanstack/react-router'
|
|
2
|
+
import { siteConfig } from '~/site.config'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/$lang')({
|
|
5
|
+
beforeLoad: ({ params }) => {
|
|
6
|
+
// Validate locale against supported locales
|
|
7
|
+
if (!(params.lang in siteConfig.supportedLocales)) {
|
|
8
|
+
throw notFound()
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
component: LangLayout,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
function LangLayout() {
|
|
15
|
+
return <Outlet />
|
|
16
|
+
}
|