docs-i18n 0.6.3 → 0.7.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 (169) hide show
  1. package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
  2. package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
  3. package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
  4. package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
  5. package/admin/app/routeTree.gen.ts +68 -0
  6. package/admin/app/router.tsx +23 -0
  7. package/admin/app/routes/__root.tsx +55 -0
  8. package/admin/app/routes/index.tsx +416 -0
  9. package/{src/admin/ui → admin/app}/styles.css +36 -3
  10. package/admin/package.json +26 -0
  11. package/admin/server/functions/jobs.ts +53 -0
  12. package/admin/server/functions/misc.ts +84 -0
  13. package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
  14. package/admin/server/functions/status.ts +61 -0
  15. package/admin/server/index.ts +35 -0
  16. package/admin/server/init.ts +46 -0
  17. package/{src/admin → admin}/server/services/job-manager.ts +39 -10
  18. package/{src/admin → admin}/server/services/status.ts +6 -6
  19. package/admin/tsconfig.json +19 -0
  20. package/{src/admin → admin}/vite.config.ts +8 -2
  21. package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
  22. package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
  23. package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
  24. package/dist/chunk-L64GJ4OB.js +32 -0
  25. package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
  26. package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
  27. package/dist/chunk-TRURQFP4.js +31 -0
  28. package/dist/cli.js +108 -7
  29. package/dist/index.d.ts +41 -1
  30. package/dist/index.js +92 -3
  31. package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
  32. package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
  33. package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
  34. package/dist/upload-XL6KG6S2.js +132 -0
  35. package/package.json +17 -15
  36. package/template/app/components/BlogArticle.tsx +159 -0
  37. package/template/app/components/BlogList.tsx +88 -0
  38. package/template/app/components/Breadcrumbs.tsx +81 -0
  39. package/template/app/components/Card.tsx +31 -0
  40. package/template/app/components/Doc.tsx +191 -0
  41. package/template/app/components/DocBreadcrumb.tsx +60 -0
  42. package/template/app/components/DocContainer.tsx +13 -0
  43. package/template/app/components/DocTitle.tsx +11 -0
  44. package/template/app/components/DocsLayout.tsx +715 -0
  45. package/template/app/components/Dropdown.tsx +116 -0
  46. package/template/app/components/FallbackBanner.tsx +36 -0
  47. package/template/app/components/Footer.tsx +29 -0
  48. package/template/app/components/FrameworkSelect.tsx +150 -0
  49. package/template/app/components/LibraryCard.tsx +178 -0
  50. package/template/app/components/LocaleSwitcher.tsx +43 -0
  51. package/template/app/components/Navbar.tsx +430 -0
  52. package/template/app/components/PostNotFound.tsx +20 -0
  53. package/template/app/components/SearchButton.tsx +32 -0
  54. package/template/app/components/Select.tsx +103 -0
  55. package/template/app/components/Spinner.tsx +18 -0
  56. package/template/app/components/ThemeProvider.tsx +141 -0
  57. package/template/app/components/ThemeToggle.tsx +31 -0
  58. package/template/app/components/Toc.tsx +86 -0
  59. package/template/app/components/VersionSelect.tsx +118 -0
  60. package/template/app/components/icons/BSkyIcon.tsx +27 -0
  61. package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
  62. package/template/app/components/icons/BrandXIcon.tsx +28 -0
  63. package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
  64. package/template/app/components/icons/CogsIcon.tsx +25 -0
  65. package/template/app/components/icons/DiscordIcon.tsx +24 -0
  66. package/template/app/components/icons/GithubIcon.tsx +24 -0
  67. package/template/app/components/icons/GoogleIcon.tsx +24 -0
  68. package/template/app/components/icons/InstagramIcon.tsx +24 -0
  69. package/template/app/components/icons/NpmIcon.tsx +26 -0
  70. package/template/app/components/icons/YinYangIcon.tsx +26 -0
  71. package/template/app/components/icons/YouTubeIcon.tsx +24 -0
  72. package/template/app/components/markdown/CodeBlock.tsx +254 -0
  73. package/template/app/components/markdown/FileTabs.tsx +58 -0
  74. package/template/app/components/markdown/FrameworkContent.tsx +76 -0
  75. package/template/app/components/markdown/Markdown.tsx +216 -0
  76. package/template/app/components/markdown/MarkdownContent.tsx +89 -0
  77. package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
  78. package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
  79. package/template/app/components/markdown/MarkdownLink.tsx +46 -0
  80. package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
  81. package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
  82. package/template/app/components/markdown/Tabs.tsx +139 -0
  83. package/template/app/components/markdown/index.ts +15 -0
  84. package/template/app/components/ui/Button.tsx +141 -0
  85. package/template/app/components/ui/InlineCode.tsx +16 -0
  86. package/template/app/components/ui/MarkdownImg.tsx +21 -0
  87. package/template/app/config/frameworks.ts +93 -0
  88. package/template/app/contexts/SearchContext.tsx +36 -0
  89. package/template/app/db/index.ts +17 -0
  90. package/template/app/db/schema.ts +74 -0
  91. package/template/app/hooks/useClickOutside.ts +106 -0
  92. package/template/app/routeTree.gen.ts +584 -0
  93. package/template/app/router.tsx +29 -0
  94. package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
  95. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
  96. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
  97. package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
  98. package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
  99. package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
  100. package/template/app/routes/$lang.$project.$version.tsx +69 -0
  101. package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
  102. package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
  103. package/template/app/routes/$lang.$project.docs.tsx +79 -0
  104. package/template/app/routes/$lang.$project.tsx +89 -0
  105. package/template/app/routes/$lang.blog.$.tsx +82 -0
  106. package/template/app/routes/$lang.blog.index.tsx +56 -0
  107. package/template/app/routes/$lang.blog.tsx +26 -0
  108. package/template/app/routes/$lang.docs.$.tsx +100 -0
  109. package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
  110. package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
  111. package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
  112. package/template/app/routes/$lang.docs.index.tsx +20 -0
  113. package/template/app/routes/$lang.docs.tsx +90 -0
  114. package/template/app/routes/$lang.tsx +16 -0
  115. package/template/app/routes/__root.tsx +180 -0
  116. package/template/app/routes/index.tsx +89 -0
  117. package/template/app/site.config.ts +182 -0
  118. package/template/app/styles/app.css +1029 -0
  119. package/template/app/types/index.ts +77 -0
  120. package/template/app/utils/blog.server.ts +193 -0
  121. package/template/app/utils/blog.ts +42 -0
  122. package/template/app/utils/config.ts +120 -0
  123. package/template/app/utils/content-loader.ts +400 -0
  124. package/template/app/utils/dates.ts +29 -0
  125. package/template/app/utils/docs.server.ts +150 -0
  126. package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
  127. package/template/app/utils/markdown/index.ts +2 -0
  128. package/template/app/utils/markdown/installCommand.ts +143 -0
  129. package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
  130. package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
  131. package/template/app/utils/markdown/plugins/helpers.ts +33 -0
  132. package/template/app/utils/markdown/plugins/index.ts +8 -0
  133. package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
  134. package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
  135. package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
  136. package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
  137. package/template/app/utils/markdown/processor.ts +75 -0
  138. package/template/app/utils/site-config.tsx +11 -0
  139. package/template/app/utils/upload.ts +232 -0
  140. package/template/app/utils/useLocalStorage.ts +65 -0
  141. package/template/app/utils/utils.ts +23 -0
  142. package/template/package.json +53 -0
  143. package/template/public/favicon.svg +1 -0
  144. package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
  145. package/template/public/fonts/Inter-latin.woff2 +0 -0
  146. package/template/public/images/frameworks/angular-logo.svg +1 -0
  147. package/template/public/images/frameworks/js-logo.svg +1 -0
  148. package/template/public/images/frameworks/lit-logo.svg +1 -0
  149. package/template/public/images/frameworks/preact-logo.svg +6 -0
  150. package/template/public/images/frameworks/qwik-logo.svg +1 -0
  151. package/template/public/images/frameworks/react-logo.svg +1 -0
  152. package/template/public/images/frameworks/solid-logo.svg +1 -0
  153. package/template/public/images/frameworks/svelte-logo.svg +1 -0
  154. package/template/public/images/frameworks/vue-logo.svg +4 -0
  155. package/template/tsconfig.json +24 -0
  156. package/template/vite.config.ts +43 -0
  157. package/template/wrangler.jsonc +16 -0
  158. package/README.md +0 -161
  159. package/dist/server-73AVSOL5.js +0 -598
  160. package/src/admin/index.html +0 -13
  161. package/src/admin/server/index.ts +0 -138
  162. package/src/admin/server/routes/jobs.ts +0 -113
  163. package/src/admin/server/routes/status.ts +0 -57
  164. package/src/admin/ui/App.tsx +0 -332
  165. package/src/admin/ui/main.tsx +0 -19
  166. /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
  167. /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
  168. /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
  169. /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
@@ -0,0 +1,128 @@
1
+ import {
2
+ createFileRoute,
3
+ notFound,
4
+ redirect,
5
+ useLocation,
6
+ } from '@tanstack/react-router'
7
+ import { createServerFn } from '@tanstack/react-start'
8
+ import { siteConfig, findProject, isSingleProject } from '~/site.config'
9
+ import { Doc } from '~/components/Doc'
10
+
11
+ const fetchDoc = createServerFn({ method: 'GET' })
12
+ .inputValidator(
13
+ (data: { project: string; version: string; lang: string; slug: string }) =>
14
+ data,
15
+ )
16
+ .handler(async ({ data }) => {
17
+ const { loadDoc } = await import('~/utils/docs.server')
18
+ return loadDoc(data.project, data.version, data.lang, data.slug)
19
+ })
20
+
21
+ export const Route = createFileRoute('/$lang/$project/$version/docs/$')({
22
+ staleTime: 1000 * 60 * 5,
23
+ beforeLoad: ({ params }) => {
24
+ // Redirect full URLs to simplified URLs for single-project sites
25
+ if (isSingleProject()) {
26
+ const { lang, _splat } = params
27
+ throw redirect({
28
+ to: '/$lang/docs/$',
29
+ params: { lang, _splat: _splat || '' },
30
+ })
31
+ }
32
+
33
+ // When hideLatestVersion is enabled, redirect latest version URLs to versionless
34
+ if (siteConfig.hideLatestVersion) {
35
+ const { lang, project: projectId, version, _splat } = params
36
+ const project = findProject(projectId)
37
+ if (project && (version === project.latestVersion || version === 'latest')) {
38
+ throw redirect({
39
+ to: '/$lang/$project/docs/$',
40
+ params: { lang, project: projectId, _splat: _splat || '' },
41
+ })
42
+ }
43
+ }
44
+ },
45
+ loader: async ({ params }) => {
46
+ const { lang, project, version, _splat: slug } = params
47
+ if (!slug) throw notFound()
48
+
49
+ try {
50
+ return await fetchDoc({ data: { project, version, lang, slug } })
51
+ } catch (error) {
52
+ throw notFound()
53
+ }
54
+ },
55
+ head: ({ loaderData, params }) => {
56
+ const project = findProject(params.project)
57
+ const title = loaderData?.meta?.title
58
+ const projectName = project?.name || siteConfig.name
59
+
60
+ return {
61
+ meta: [
62
+ {
63
+ title: title ? `${title} | ${projectName} Docs` : `${projectName} Docs`,
64
+ },
65
+ ...(loaderData?.meta?.description
66
+ ? [{ name: 'description', content: loaderData.meta.description }]
67
+ : []),
68
+ ],
69
+ }
70
+ },
71
+ headers: ({ params }) => {
72
+ const { version, project: projectId } = params
73
+ const project = findProject(projectId)
74
+
75
+ const isLatestVersion =
76
+ project &&
77
+ (version === 'latest' || version === project.latestVersion)
78
+
79
+ if (isLatestVersion) {
80
+ return {
81
+ 'cache-control': 'public, max-age=60, must-revalidate',
82
+ 'cdn-cache-control':
83
+ 'max-age=600, stale-while-revalidate=3600, durable',
84
+ vary: 'Accept-Encoding',
85
+ }
86
+ } else {
87
+ return {
88
+ 'cache-control': 'public, max-age=3600, must-revalidate',
89
+ 'cdn-cache-control':
90
+ 'max-age=86400, stale-while-revalidate=604800, durable',
91
+ vary: 'Accept-Encoding',
92
+ }
93
+ }
94
+ },
95
+ component: DocsPage,
96
+ errorComponent: DocNotFound,
97
+ })
98
+
99
+ function DocsPage() {
100
+ const loaderData = Route.useLoaderData()
101
+ const { project: projectId } = Route.useParams()
102
+ const project = findProject(projectId)
103
+ const location = useLocation()
104
+
105
+ return (
106
+ <Doc
107
+ title={loaderData.meta?.title || ''}
108
+ content={loaderData.content}
109
+ isFallback={loaderData.isFallback}
110
+ filePath={loaderData.filePath}
111
+ repo={project?.repo}
112
+ pagePath={location.pathname}
113
+ />
114
+ )
115
+ }
116
+
117
+ function DocNotFound() {
118
+ return (
119
+ <div className="flex-1 flex items-center justify-center py-20">
120
+ <div className="text-center">
121
+ <h1 className="text-2xl font-bold mb-2">Page Not Found</h1>
122
+ <p className="text-gray-500 dark:text-gray-400">
123
+ The document you are looking for does not exist.
124
+ </p>
125
+ </div>
126
+ </div>
127
+ )
128
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ createFileRoute,
3
+ redirect,
4
+ useLocation,
5
+ } from '@tanstack/react-router'
6
+ import { createServerFn } from '@tanstack/react-start'
7
+ import { siteConfig, findProject } 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/$project/$version/docs/framework/$framework/$',
32
+ )({
33
+ staleTime: 1000 * 60 * 5,
34
+ loader: async ({ params }) => {
35
+ const { lang, project, version, framework, _splat: slug } = params
36
+
37
+ try {
38
+ return await fetchFrameworkDoc({
39
+ data: { project, version, lang, framework, slug: slug || '' },
40
+ })
41
+ } catch (error) {
42
+ // If doc not found, redirect to framework index
43
+ throw redirect({
44
+ to: '/$lang/$project/$version/docs/framework/$framework',
45
+ params: { lang, project, version, framework },
46
+ })
47
+ }
48
+ },
49
+ head: ({ loaderData, params }) => {
50
+ const project = findProject(params.project)
51
+ const projectName = project?.name || siteConfig.name
52
+ const framework =
53
+ params.framework.charAt(0).toUpperCase() + params.framework.slice(1)
54
+ const title = loaderData?.meta?.title
55
+
56
+ return {
57
+ meta: [
58
+ {
59
+ title: title
60
+ ? `${title} | ${projectName} ${framework} Docs`
61
+ : `${projectName} ${framework} Docs`,
62
+ },
63
+ ],
64
+ }
65
+ },
66
+ headers: ({ params }) => {
67
+ const project = findProject(params.project)
68
+ const isLatest =
69
+ project &&
70
+ (params.version === 'latest' || params.version === project.latestVersion)
71
+
72
+ return isLatest
73
+ ? {
74
+ 'cache-control': 'public, max-age=60, must-revalidate',
75
+ 'cdn-cache-control':
76
+ 'max-age=600, stale-while-revalidate=3600, durable',
77
+ vary: 'Accept-Encoding',
78
+ }
79
+ : {
80
+ 'cache-control': 'public, max-age=3600, must-revalidate',
81
+ 'cdn-cache-control':
82
+ 'max-age=86400, stale-while-revalidate=604800, durable',
83
+ vary: 'Accept-Encoding',
84
+ }
85
+ },
86
+ component: FrameworkDocsPage,
87
+ })
88
+
89
+ function FrameworkDocsPage() {
90
+ const loaderData = Route.useLoaderData()
91
+ const { project: projectId, framework } = Route.useParams()
92
+ const project = findProject(projectId)
93
+ const location = useLocation()
94
+
95
+ return (
96
+ <Doc
97
+ title={loaderData.meta?.title || ''}
98
+ content={loaderData.content}
99
+ isFallback={loaderData.isFallback}
100
+ filePath={loaderData.filePath}
101
+ repo={project?.repo}
102
+ pagePath={location.pathname}
103
+ framework={framework}
104
+ />
105
+ )
106
+ }
@@ -0,0 +1,27 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { findProject } from '~/site.config'
3
+
4
+ export const Route = createFileRoute(
5
+ '/$lang/$project/$version/docs/framework/$framework/',
6
+ )({
7
+ component: FrameworkIndex,
8
+ })
9
+
10
+ function FrameworkIndex() {
11
+ const { project: projectId, framework } = Route.useParams()
12
+ const project = findProject(projectId)
13
+ const frameworkLabel =
14
+ framework.charAt(0).toUpperCase() + framework.slice(1)
15
+
16
+ return (
17
+ <div className="py-8 max-w-3xl mx-auto">
18
+ <h1 className="text-3xl font-bold mb-4">
19
+ {project?.name} {frameworkLabel} Documentation
20
+ </h1>
21
+ <div className="h-px bg-gray-200 dark:bg-gray-700 mb-6" />
22
+ <p className="text-gray-600 dark:text-gray-400">
23
+ Use the sidebar to select a documentation page.
24
+ </p>
25
+ </div>
26
+ )
27
+ }
@@ -0,0 +1,44 @@
1
+ import { createFileRoute, Link } from '@tanstack/react-router'
2
+ import { findProject } from '~/site.config'
3
+
4
+ export const Route = createFileRoute(
5
+ '/$lang/$project/$version/docs/framework/',
6
+ )({
7
+ component: FrameworkSelectionPage,
8
+ })
9
+
10
+ function FrameworkSelectionPage() {
11
+ const { lang, project: projectId, version } = Route.useParams()
12
+ const project = findProject(projectId)
13
+ const frameworks = project?.frameworks || []
14
+
15
+ return (
16
+ <div className="py-8 max-w-3xl mx-auto">
17
+ <h1 className="text-3xl font-bold mb-4">
18
+ {project?.name} Frameworks
19
+ </h1>
20
+ <div className="h-px bg-gray-200 dark:bg-gray-700 mb-6" />
21
+
22
+ {frameworks.length > 0 ? (
23
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
24
+ {frameworks.map((fw) => (
25
+ <Link
26
+ key={fw}
27
+ to="/$lang/$project/$version/docs/framework/$framework"
28
+ params={{ lang, project: projectId, version, framework: fw }}
29
+ className="p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
30
+ >
31
+ <span className="text-lg font-medium">
32
+ {fw.charAt(0).toUpperCase() + fw.slice(1)}
33
+ </span>
34
+ </Link>
35
+ ))}
36
+ </div>
37
+ ) : (
38
+ <p className="text-gray-600 dark:text-gray-400">
39
+ No frameworks are configured for this project.
40
+ </p>
41
+ )}
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1,27 @@
1
+ import { createFileRoute, redirect } from '@tanstack/react-router'
2
+ import { getProject, isSingleProject } from '~/site.config'
3
+
4
+ export const Route = createFileRoute('/$lang/$project/$version/docs/')({
5
+ beforeLoad: ({ params }) => {
6
+ const project = getProject(params.project)
7
+
8
+ // Redirect to simplified URL for single-project sites
9
+ if (isSingleProject()) {
10
+ throw redirect({
11
+ to: '/$lang/docs/$',
12
+ params: {
13
+ lang: params.lang,
14
+ _splat: project.defaultDocs || 'overview',
15
+ },
16
+ })
17
+ }
18
+
19
+ throw redirect({
20
+ from: '/$lang/$project/$version/docs',
21
+ to: './$',
22
+ params: {
23
+ _splat: project.defaultDocs || 'overview',
24
+ },
25
+ })
26
+ },
27
+ })
@@ -0,0 +1,70 @@
1
+ import * as React from 'react'
2
+ import {
3
+ createFileRoute,
4
+ Outlet,
5
+ } from '@tanstack/react-router'
6
+ import { createServerFn } from '@tanstack/react-start'
7
+ import { siteConfig, findProject } from '~/site.config'
8
+ import { DocsLayout } from '~/components/DocsLayout'
9
+ import { frameworkOptions } from '~/config/frameworks'
10
+ import type { Framework } from '~/config/frameworks'
11
+
12
+ const fetchDocsConfig = createServerFn({ method: 'GET' })
13
+ .inputValidator((data: { project: string; version: string }) => data)
14
+ .handler(async ({ data }) => {
15
+ const { loadDocsConfig } = await import('~/utils/docs.server')
16
+ return loadDocsConfig(data.project, data.version)
17
+ })
18
+
19
+ export const Route = createFileRoute('/$lang/$project/$version/docs')({
20
+ staleTime: 1000 * 60 * 5,
21
+ loader: async ({ params }) => {
22
+ const config = await fetchDocsConfig({
23
+ data: { project: params.project, version: params.version },
24
+ })
25
+ return { config }
26
+ },
27
+ component: DocsLayoutRoute,
28
+ head: ({ params }) => {
29
+ const project = findProject(params.project)
30
+ return {
31
+ meta: [{ title: project ? `${project.name} Docs` : siteConfig.name }],
32
+ }
33
+ },
34
+ headers: () => ({
35
+ 'cache-control': 'public, max-age=0, must-revalidate',
36
+ 'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable',
37
+ }),
38
+ })
39
+
40
+ function DocsLayoutRoute() {
41
+ const { project: projectId, version } = Route.useParams()
42
+ const { config } = Route.useLoaderData()
43
+ const project = findProject(projectId)
44
+
45
+ if (!project) {
46
+ return <div>Project not found: {projectId}</div>
47
+ }
48
+
49
+ // Map string framework IDs to Framework type
50
+ const frameworks: Framework[] = (project.frameworks ?? []).filter(
51
+ (f): f is Framework =>
52
+ frameworkOptions.some((opt) => opt.value === f),
53
+ )
54
+
55
+ return (
56
+ <DocsLayout
57
+ name={project.name}
58
+ version={version}
59
+ colorFrom={project.colorFrom}
60
+ colorTo={project.colorTo}
61
+ textColor={project.textColor ?? ''}
62
+ config={config}
63
+ frameworks={frameworks}
64
+ versions={project.availableVersions}
65
+ repo={project.repo}
66
+ >
67
+ <Outlet />
68
+ </DocsLayout>
69
+ )
70
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ createFileRoute,
3
+ notFound,
4
+ redirect,
5
+ Outlet,
6
+ Link,
7
+ } from '@tanstack/react-router'
8
+ import { findProject, siteConfig, isSingleProject } from '~/site.config'
9
+
10
+ export const Route = createFileRoute('/$lang/$project/$version')({
11
+ staleTime: 1000 * 60 * 5,
12
+ beforeLoad: ({ params }) => {
13
+ const { lang, project: projectId, version } = params
14
+ const project = findProject(projectId)
15
+
16
+ if (!project) {
17
+ throw notFound()
18
+ }
19
+
20
+ // Redirect "latest" to actual latest version (skip if latestVersion is literally 'latest')
21
+ if (version === 'latest' && project.latestVersion !== 'latest') {
22
+ throw redirect({
23
+ params: { ...params, version: project.latestVersion } as never,
24
+ })
25
+ }
26
+
27
+ // Validate version
28
+ if (version !== 'latest' && !project.availableVersions.includes(version)) {
29
+ throw redirect({
30
+ params: { ...params, version: project.latestVersion } as never,
31
+ })
32
+ }
33
+ },
34
+ component: () => <Outlet />,
35
+ staticData: {
36
+ Title: () => {
37
+ const { project: projectId, version, lang } = Route.useParams()
38
+ const project = findProject(projectId)
39
+
40
+ if (!project) {
41
+ return null
42
+ }
43
+
44
+ const projectName = project.name
45
+ const resolvedVersion =
46
+ version === 'latest' ? project.latestVersion : version!
47
+ const gradientText = `inline-block text-transparent bg-clip-text bg-linear-to-r ${project.colorFrom} ${project.colorTo}`
48
+ return (
49
+ <Link
50
+ to={`/$lang/$project`}
51
+ params={{ lang, project: projectId }}
52
+ className="relative whitespace-nowrap"
53
+ >
54
+ <span className={`${gradientText}`}>{projectName}</span>{' '}
55
+ {resolvedVersion && resolvedVersion !== 'latest' && (
56
+ <>
57
+ <span className="text-sm absolute right-0 top-0 font-normal normal-case">
58
+ {resolvedVersion}
59
+ </span>
60
+ <span className="text-sm opacity-0 normal-case">
61
+ {resolvedVersion}
62
+ </span>
63
+ </>
64
+ )}
65
+ </Link>
66
+ )
67
+ },
68
+ },
69
+ })
@@ -0,0 +1,104 @@
1
+ import {
2
+ createFileRoute,
3
+ notFound,
4
+ } from '@tanstack/react-router'
5
+ import { createServerFn } from '@tanstack/react-start'
6
+ import { siteConfig, findProject } 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/$project/docs/$')({
20
+ staleTime: 1000 * 60 * 5,
21
+ beforeLoad: ({ params }) => {
22
+ const project = findProject(params.project)
23
+
24
+ // This route only works when hideLatestVersion is enabled
25
+ if (!project || !siteConfig.hideLatestVersion) {
26
+ throw notFound()
27
+ }
28
+ },
29
+ loader: async ({ params }) => {
30
+ const { lang, project: projectId, _splat: slug } = params
31
+ if (!slug) throw notFound()
32
+
33
+ const project = findProject(projectId)!
34
+
35
+ try {
36
+ return await fetchDoc({
37
+ data: {
38
+ project: projectId,
39
+ version: project.latestVersion,
40
+ lang,
41
+ slug,
42
+ },
43
+ })
44
+ } catch (error) {
45
+ throw notFound()
46
+ }
47
+ },
48
+ head: ({ loaderData, params }) => {
49
+ const project = findProject(params.project)
50
+ const title = loaderData?.meta?.title
51
+ const projectName = project?.name || siteConfig.name
52
+
53
+ return {
54
+ meta: [
55
+ {
56
+ title: title ? `${title} | ${projectName} Docs` : `${projectName} Docs`,
57
+ },
58
+ ...(loaderData?.meta?.description
59
+ ? [{ name: 'description', content: loaderData.meta.description }]
60
+ : []),
61
+ ],
62
+ }
63
+ },
64
+ headers: () => {
65
+ return {
66
+ 'cache-control': 'public, max-age=60, must-revalidate',
67
+ 'cdn-cache-control':
68
+ 'max-age=600, stale-while-revalidate=3600, durable',
69
+ vary: 'Accept-Encoding',
70
+ }
71
+ },
72
+ component: VersionlessDocsPage,
73
+ errorComponent: DocNotFound,
74
+ })
75
+
76
+ function VersionlessDocsPage() {
77
+ const loaderData = Route.useLoaderData()
78
+ const { project: projectId } = Route.useParams()
79
+ const project = findProject(projectId)!
80
+
81
+ return (
82
+ <Doc
83
+ title={loaderData.meta?.title || ''}
84
+ content={loaderData.content}
85
+ isFallback={loaderData.isFallback}
86
+ filePath={loaderData.filePath}
87
+ repo={project.repo}
88
+ branch={project.latestBranch}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DocNotFound() {
94
+ return (
95
+ <div className="flex-1 flex items-center justify-center py-20">
96
+ <div className="text-center">
97
+ <h1 className="text-2xl font-bold mb-2">Page Not Found</h1>
98
+ <p className="text-gray-500 dark:text-gray-400">
99
+ The document you are looking for does not exist.
100
+ </p>
101
+ </div>
102
+ </div>
103
+ )
104
+ }
@@ -0,0 +1,20 @@
1
+ import { createFileRoute, notFound, redirect } from '@tanstack/react-router'
2
+ import { siteConfig, findProject } from '~/site.config'
3
+
4
+ export const Route = createFileRoute('/$lang/$project/docs/')({
5
+ beforeLoad: ({ params }) => {
6
+ const project = findProject(params.project)
7
+
8
+ if (!project || !siteConfig.hideLatestVersion) {
9
+ throw notFound()
10
+ }
11
+
12
+ throw redirect({
13
+ from: '/$lang/$project/docs',
14
+ to: './$',
15
+ params: {
16
+ _splat: project.defaultDocs || 'overview',
17
+ },
18
+ })
19
+ },
20
+ })
@@ -0,0 +1,79 @@
1
+ import {
2
+ createFileRoute,
3
+ notFound,
4
+ Outlet,
5
+ } from '@tanstack/react-router'
6
+ import { createServerFn } from '@tanstack/react-start'
7
+ import { siteConfig, findProject } from '~/site.config'
8
+ import { DocsLayout } from '~/components/DocsLayout'
9
+ import { frameworkOptions } from '~/config/frameworks'
10
+ import type { Framework } from '~/config/frameworks'
11
+
12
+ const fetchDocsConfig = createServerFn({ method: 'GET' })
13
+ .inputValidator((data: { project: string; version: string }) => data)
14
+ .handler(async ({ data }) => {
15
+ const { loadDocsConfig } = await import('~/utils/docs.server')
16
+ return loadDocsConfig(data.project, data.version)
17
+ })
18
+
19
+ export const Route = createFileRoute('/$lang/$project/docs')({
20
+ staleTime: 1000 * 60 * 5,
21
+ beforeLoad: ({ params }) => {
22
+ const project = findProject(params.project)
23
+
24
+ // This route only works when hideLatestVersion is enabled
25
+ if (!project || !siteConfig.hideLatestVersion) {
26
+ throw notFound()
27
+ }
28
+ },
29
+ loader: async ({ params }) => {
30
+ const project = findProject(params.project)!
31
+ const config = await fetchDocsConfig({
32
+ data: { project: project.id, version: project.latestVersion },
33
+ })
34
+ return { config }
35
+ },
36
+ component: VersionlessDocsLayout,
37
+ head: ({ params }) => {
38
+ const project = findProject(params.project)
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
+ })
48
+
49
+ function VersionlessDocsLayout() {
50
+ const { project: projectId } = Route.useParams()
51
+ const { config } = Route.useLoaderData()
52
+ const project = findProject(projectId)
53
+
54
+ if (!project) {
55
+ return <div>Project not found: {projectId}</div>
56
+ }
57
+
58
+ const frameworks: Framework[] = (project.frameworks ?? []).filter(
59
+ (f): f is Framework =>
60
+ frameworkOptions.some((opt) => opt.value === f),
61
+ )
62
+
63
+ return (
64
+ <DocsLayout
65
+ name={project.name}
66
+ version={project.latestVersion}
67
+ colorFrom={project.colorFrom}
68
+ colorTo={project.colorTo}
69
+ textColor={project.textColor ?? ''}
70
+ config={config}
71
+ frameworks={frameworks}
72
+ versions={project.availableVersions}
73
+ repo={project.repo}
74
+ docsBasePath="/$lang/$project/docs"
75
+ >
76
+ <Outlet />
77
+ </DocsLayout>
78
+ )
79
+ }