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.
- package/admin/dist/server/assets/chunk-CNvmzFzq.js +35 -0
- package/admin/dist/server/assets/{init-AJSQ7K_l.js → init-DJr2glb3.js} +5 -38
- package/admin/dist/server/assets/{jobs-CwDb0Zyp.js → jobs-FXffC7LH.js} +2 -2
- package/admin/dist/server/assets/{misc-CqYhnW23.js → misc-y6t3-UOP.js} +3 -3
- package/admin/dist/server/assets/{models-D9Sd95EX.js → models-YNa3F3nn.js} +1 -1
- package/admin/dist/server/assets/react-dom-BryASgrS.js +2159 -0
- package/admin/dist/server/assets/redirect-BHRifpCK.js +51 -0
- package/admin/dist/server/assets/router-CAX08MEI.js +897 -0
- package/admin/dist/server/assets/routes-Bk6XCM2I.js +2139 -0
- package/admin/dist/server/assets/routes-CMOVc2RM.js +2132 -0
- package/admin/dist/server/assets/{status-D48jcwYI.js → status-CM7Azp4n.js} +2 -2
- package/admin/dist/server/server.js +15789 -4447
- package/admin/vite.config.ts +13 -0
- package/dist/cli.js +1 -1
- package/dist/{upload-XL6KG6S2.js → upload-KYKJVERO.js} +1 -1
- package/package.json +1 -1
- package/template/app/components/BlogArticle.tsx +3 -0
- package/template/app/components/Doc.tsx +4 -0
- package/template/app/components/markdown/MarkdownContent.tsx +6 -2
- package/template/app/site.config.ts +2 -0
- package/template/app/types/index.ts +4 -0
- package/template/app/utils/content-loader.ts +85 -32
- package/template/app/utils/docs.server.ts +38 -6
- package/template/app/utils/markdown/plugins/index.ts +1 -0
- package/template/app/utils/markdown/plugins/mdxJsxToRaw.ts +127 -0
- package/template/app/utils/markdown/processor.ts +14 -1
- package/template/app/utils/sidebar-generator.ts +185 -0
- package/template/app/utils/url-mapper.ts +22 -0
- package/template/package.json +2 -1
- package/admin/dist/server/assets/router-D00bP5CU.js +0 -67
- package/admin/dist/server/assets/routes-C2UFxDWZ.js +0 -24
- package/admin/dist/server/assets/routes-vEKXnl0r.js +0 -1574
- /package/admin/dist/server/assets/{_tanstack-start-manifest_v-sC90W3ET.js → _tanstack-start-manifest_v-mK4S3Lga.js} +0 -0
- /package/admin/dist/server/assets/{createServerRpc-CMjjCE8A.js → createServerRpc-C3JHS5ky.js} +0 -0
- /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
|
+
}
|
package/template/package.json
CHANGED
|
@@ -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 };
|