docs-i18n 0.7.4 → 0.8.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.
Files changed (35) hide show
  1. package/admin/dist/server/assets/chunk-CNvmzFzq.js +35 -0
  2. package/admin/dist/server/assets/{init-AJSQ7K_l.js → init-DJr2glb3.js} +5 -38
  3. package/admin/dist/server/assets/{jobs-CwDb0Zyp.js → jobs-FXffC7LH.js} +2 -2
  4. package/admin/dist/server/assets/{misc-CqYhnW23.js → misc-y6t3-UOP.js} +3 -3
  5. package/admin/dist/server/assets/{models-D9Sd95EX.js → models-YNa3F3nn.js} +1 -1
  6. package/admin/dist/server/assets/react-dom-BryASgrS.js +2159 -0
  7. package/admin/dist/server/assets/redirect-BHRifpCK.js +51 -0
  8. package/admin/dist/server/assets/router-CAX08MEI.js +897 -0
  9. package/admin/dist/server/assets/routes-Bk6XCM2I.js +2139 -0
  10. package/admin/dist/server/assets/routes-CMOVc2RM.js +2132 -0
  11. package/admin/dist/server/assets/{status-D48jcwYI.js → status-CM7Azp4n.js} +2 -2
  12. package/admin/dist/server/server.js +15789 -4447
  13. package/admin/vite.config.ts +13 -0
  14. package/dist/cli.js +1 -1
  15. package/dist/{upload-XL6KG6S2.js → upload-KYKJVERO.js} +1 -1
  16. package/package.json +1 -1
  17. package/template/app/components/BlogArticle.tsx +3 -0
  18. package/template/app/components/Doc.tsx +4 -0
  19. package/template/app/components/markdown/MarkdownContent.tsx +6 -2
  20. package/template/app/site.config.ts +2 -0
  21. package/template/app/types/index.ts +4 -0
  22. package/template/app/utils/content-loader.ts +85 -32
  23. package/template/app/utils/docs.server.ts +38 -6
  24. package/template/app/utils/markdown/plugins/index.ts +1 -0
  25. package/template/app/utils/markdown/plugins/mdxJsxToRaw.ts +127 -0
  26. package/template/app/utils/markdown/processor.ts +14 -1
  27. package/template/app/utils/sidebar-generator.ts +185 -0
  28. package/template/app/utils/url-mapper.ts +22 -0
  29. package/template/package.json +2 -1
  30. package/admin/dist/server/assets/router-D00bP5CU.js +0 -67
  31. package/admin/dist/server/assets/routes-C2UFxDWZ.js +0 -24
  32. package/admin/dist/server/assets/routes-vEKXnl0r.js +0 -1574
  33. /package/admin/dist/server/assets/{_tanstack-start-manifest_v-sC90W3ET.js → _tanstack-start-manifest_v-mK4S3Lga.js} +0 -0
  34. /package/admin/dist/server/assets/{createServerRpc-CMjjCE8A.js → createServerRpc-C3JHS5ky.js} +0 -0
  35. /package/admin/dist/server/assets/{start-BrsoKfWS.js → start-3avuCbOL.js} +0 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Filesystem-based sidebar generation.
3
+ *
4
+ * Walks a content directory tree, sorts entries by numeric prefix,
5
+ * and produces a DocsConfig with sections (directories) and items (files).
6
+ */
7
+
8
+ import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+ import matter from 'gray-matter'
11
+ import type {
12
+ DocsConfig,
13
+ DocsConfigSection,
14
+ DocsConfigItem,
15
+ } from '~/types'
16
+
17
+ /**
18
+ * Convert a kebab-case name to Title Case.
19
+ * 'getting-started' → 'Getting Started'
20
+ */
21
+ function toTitleCase(name: string): string {
22
+ return name
23
+ .split('-')
24
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
25
+ .join(' ')
26
+ }
27
+
28
+ /**
29
+ * Strip a leading numeric prefix from a single name segment.
30
+ * '01-getting-started' → 'getting-started'
31
+ */
32
+ function stripPrefix(name: string): string {
33
+ return name.replace(/^\d+-/, '')
34
+ }
35
+
36
+ /**
37
+ * Extract the numeric prefix for sorting. Returns Infinity if none.
38
+ */
39
+ function sortOrder(name: string): number {
40
+ const match = name.match(/^(\d+)-/)
41
+ return match ? parseInt(match[1], 10) : Infinity
42
+ }
43
+
44
+ /**
45
+ * Try to read the frontmatter title from a markdown file.
46
+ */
47
+ function readTitle(filePath: string): string | null {
48
+ try {
49
+ const raw = readFileSync(filePath, 'utf-8')
50
+ const { data } = matter(raw)
51
+ if (typeof data.title === 'string' && data.title) return data.title
52
+ } catch {
53
+ // ignore
54
+ }
55
+ return null
56
+ }
57
+
58
+ /**
59
+ * Generate a DocsConfig by walking a content directory.
60
+ *
61
+ * @param contentDir Absolute path to the content directory to scan
62
+ * (e.g. `/path/to/content/latest/docs/en`)
63
+ * @param urlMapper Optional function to transform relative file paths to URL slugs
64
+ */
65
+ export function generateSidebarFromFilesystem(
66
+ contentDir: string,
67
+ urlMapper?: (path: string) => string,
68
+ ): DocsConfig {
69
+ if (!existsSync(contentDir) || !statSync(contentDir).isDirectory()) {
70
+ return { sections: [] }
71
+ }
72
+
73
+ const sections = buildSections(contentDir, contentDir, urlMapper)
74
+ return { sections }
75
+ }
76
+
77
+ /**
78
+ * Recursively build sidebar sections from a directory.
79
+ * Directories become sections, .md/.mdx files become items.
80
+ */
81
+ function buildSections(
82
+ dir: string,
83
+ baseDir: string,
84
+ urlMapper?: (path: string) => string,
85
+ ): DocsConfigSection[] {
86
+ const sections: DocsConfigSection[] = []
87
+
88
+ let entries: ReturnType<typeof readdirSync>
89
+ try {
90
+ entries = readdirSync(dir, { withFileTypes: true })
91
+ } catch {
92
+ return sections
93
+ }
94
+
95
+ // Sort by numeric prefix
96
+ const sorted = [...entries].sort(
97
+ (a, b) => sortOrder(a.name) - sortOrder(b.name),
98
+ )
99
+
100
+ // Collect top-level files (items without a section) — only at root level
101
+ const topLevelItems: DocsConfigItem[] = []
102
+
103
+ for (const entry of sorted) {
104
+ if (entry.name.startsWith('.')) continue
105
+
106
+ if (entry.isDirectory()) {
107
+ const subDir = join(dir, entry.name)
108
+ const cleanName = stripPrefix(entry.name)
109
+ const label = toTitleCase(cleanName)
110
+
111
+ // Collect all file items from this directory (recursively flattened into children)
112
+ const children = collectItems(subDir, baseDir, urlMapper)
113
+
114
+ if (children.length > 0) {
115
+ sections.push({ label, children })
116
+ }
117
+ } else if (
118
+ entry.isFile() &&
119
+ (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))
120
+ ) {
121
+ const filePath = join(dir, entry.name)
122
+ const relativePath = filePath
123
+ .slice(baseDir.length + 1) // remove baseDir prefix + leading slash
124
+ const cleanName = stripPrefix(entry.name).replace(/\.mdx?$/, '')
125
+ const slug = urlMapper ? urlMapper(relativePath) : relativePath.replace(/\.mdx?$/, '')
126
+ const title = readTitle(filePath)
127
+ const label = title || toTitleCase(cleanName)
128
+
129
+ topLevelItems.push({ label, to: slug })
130
+ }
131
+ }
132
+
133
+ // If there are top-level files, add them as a section
134
+ if (topLevelItems.length > 0) {
135
+ sections.unshift({
136
+ label: 'Overview',
137
+ children: topLevelItems,
138
+ })
139
+ }
140
+
141
+ return sections
142
+ }
143
+
144
+ /**
145
+ * Collect all file items from a directory, recursing into subdirectories.
146
+ * Subdirectory files are flattened into a single list of items.
147
+ */
148
+ function collectItems(
149
+ dir: string,
150
+ baseDir: string,
151
+ urlMapper?: (path: string) => string,
152
+ ): DocsConfigItem[] {
153
+ const items: DocsConfigItem[] = []
154
+
155
+ let entries: ReturnType<typeof readdirSync>
156
+ try {
157
+ entries = readdirSync(dir, { withFileTypes: true })
158
+ } catch {
159
+ return items
160
+ }
161
+
162
+ const sorted = [...entries].sort(
163
+ (a, b) => sortOrder(a.name) - sortOrder(b.name),
164
+ )
165
+
166
+ for (const entry of sorted) {
167
+ if (entry.name.startsWith('.')) continue
168
+
169
+ if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
170
+ const filePath = join(dir, entry.name)
171
+ const relativePath = filePath.slice(baseDir.length + 1)
172
+ const cleanName = stripPrefix(entry.name).replace(/\.mdx?$/, '')
173
+ const slug = urlMapper ? urlMapper(relativePath) : relativePath.replace(/\.mdx?$/, '')
174
+ const title = readTitle(filePath)
175
+ const label = title || toTitleCase(cleanName)
176
+
177
+ items.push({ label, to: slug })
178
+ } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
179
+ // Recurse into subdirectories
180
+ items.push(...collectItems(join(dir, entry.name), baseDir, urlMapper))
181
+ }
182
+ }
183
+
184
+ return items
185
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Built-in URL mapping helpers for transforming file paths to URL slugs.
3
+ *
4
+ * Projects with numeric-prefixed directories (e.g. nextjs-i18n-docs) can use
5
+ * `stripNumericPrefixes` as their `urlMapper` in ProjectConfig.
6
+ */
7
+
8
+ /**
9
+ * Strip numeric prefixes, file extensions, and trailing /index from a file path.
10
+ *
11
+ * Example:
12
+ * '01-app/01-getting-started/01-installation.mdx'
13
+ * → 'app/getting-started/installation'
14
+ */
15
+ export function stripNumericPrefixes(filePath: string): string {
16
+ return filePath
17
+ .replace(/\.mdx?$/, '') // strip .md or .mdx extension
18
+ .split('/')
19
+ .map((segment) => segment.replace(/^\d+-/, '')) // strip leading numeric prefix per segment
20
+ .join('/')
21
+ .replace(/\/index$/, '') // strip trailing /index
22
+ }
@@ -12,8 +12,8 @@
12
12
  "@shikijs/transformers": "^3.0.0",
13
13
  "@tailwindcss/typography": "^0.5.16",
14
14
  "@tailwindcss/vite": "^4.1.7",
15
- "@tanstack/react-router": "^1.120.3",
16
15
  "@tanstack/react-query": "^5.0.0",
16
+ "@tanstack/react-router": "^1.120.3",
17
17
  "@tanstack/react-start": "^1.120.3",
18
18
  "drizzle-orm": "^0.38.0",
19
19
  "gray-matter": "^4.0.3",
@@ -32,6 +32,7 @@
32
32
  "rehype-slug": "^6.0.0",
33
33
  "rehype-stringify": "^10.0.1",
34
34
  "remark-gfm": "^4.0.1",
35
+ "remark-mdx": "^3.1.1",
35
36
  "remark-parse": "^11.0.0",
36
37
  "remark-rehype": "^11.1.2",
37
38
  "shiki": "^3.0.0",
@@ -1,67 +0,0 @@
1
- import { t as Route$1 } from "./routes-C2UFxDWZ.js";
2
- import "react";
3
- import { HeadContent, Outlet, Scripts, createRootRouteWithContext, createRouter } from "@tanstack/react-router";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6
- //#region app/styles.css?url
7
- var styles_default = "/assets/styles-DJ6QEJmN.css";
8
- //#endregion
9
- //#region app/routes/__root.tsx
10
- var Route = createRootRouteWithContext()({
11
- head: () => ({
12
- meta: [{ charSet: "utf-8" }, {
13
- name: "viewport",
14
- content: "width=device-width, initial-scale=1"
15
- }],
16
- links: [{
17
- rel: "stylesheet",
18
- href: styles_default
19
- }]
20
- }),
21
- component: RootComponent,
22
- notFoundComponent: () => /* @__PURE__ */ jsx("div", {
23
- style: { padding: 20 },
24
- children: "Page not found"
25
- })
26
- });
27
- function RootComponent() {
28
- const { queryClient } = Route.useRouteContext();
29
- return /* @__PURE__ */ jsx(RootDocument, { children: /* @__PURE__ */ jsx(QueryClientProvider, {
30
- client: queryClient,
31
- children: /* @__PURE__ */ jsx(Outlet, {})
32
- }) });
33
- }
34
- function RootDocument({ children }) {
35
- return /* @__PURE__ */ jsxs("html", {
36
- lang: "en",
37
- "data-theme": "dark",
38
- suppressHydrationWarning: true,
39
- children: [/* @__PURE__ */ jsx("head", { children: /* @__PURE__ */ jsx(HeadContent, {}) }), /* @__PURE__ */ jsxs("body", { children: [
40
- /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `document.documentElement.dataset.theme=localStorage.getItem('theme')||'dark'` } }),
41
- children,
42
- /* @__PURE__ */ jsx(Scripts, {})
43
- ] })]
44
- });
45
- }
46
- //#endregion
47
- //#region app/routeTree.gen.ts
48
- var rootRouteChildren = { IndexRoute: Route$1.update({
49
- id: "/",
50
- path: "/",
51
- getParentRoute: () => Route
52
- }) };
53
- var routeTree = Route._addFileChildren(rootRouteChildren)._addFileTypes();
54
- //#endregion
55
- //#region app/router.tsx
56
- function getRouter() {
57
- return createRouter({
58
- routeTree,
59
- context: { queryClient: new QueryClient({ defaultOptions: { queries: {
60
- refetchOnWindowFocus: false,
61
- retry: 1
62
- } } }) },
63
- scrollRestoration: true
64
- });
65
- }
66
- //#endregion
67
- export { getRouter };
@@ -1,24 +0,0 @@
1
- import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";
2
- //#region app/routes/index.tsx
3
- var $$splitComponentImporter = () => import("./routes-vEKXnl0r.js");
4
- /**
5
- * Parse version keys into project/version structure.
6
- * Multi-project keys look like "query/v5", single-project keys look like "v5".
7
- */
8
- var Route = createFileRoute("/")({
9
- validateSearch: (search) => ({
10
- project: search.project || void 0,
11
- v: search.v || void 0,
12
- lang: search.lang || void 0,
13
- file: search.file || void 0,
14
- files: search.files || void 0,
15
- view: search.view || void 0,
16
- toc: search.toc || void 0,
17
- nodes: search.nodes || void 0,
18
- status: search.status || void 0,
19
- section: search.section || void 0
20
- }),
21
- component: lazyRouteComponent($$splitComponentImporter, "component")
22
- });
23
- //#endregion
24
- export { Route as t };