react-docs-ui 0.7.5 → 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 (78) hide show
  1. package/dist/AIChatDialog-C2mEAX2T.js +407 -0
  2. package/dist/{AISelectionTrigger-SLyb8Vep.js → AISelectionTrigger-CWTJRvHo.js} +1 -1
  3. package/dist/{AISettingsPanel-Bsd_wLP4.js → AISettingsPanel-C9Bb_QtE.js} +1 -1
  4. package/dist/{DocsApp-B-VaP4ms.js → DocsApp-CTh7Pa9A.js} +3867 -3616
  5. package/dist/{GlobalContextMenu-BtVjQk0v.js → GlobalContextMenu-DAIKsl1l.js} +1 -1
  6. package/dist/{MdxContent-CZLqZlNp.js → MdxContent-BAm4izu3.js} +305 -317
  7. package/dist/{MdxContent.lazy-i9phqEH3.js → MdxContent.lazy-vS9NrZj4.js} +1 -1
  8. package/dist/{SearchDialog-DWVre6R9.js → SearchDialog-CLbtpf7I.js} +172 -127
  9. package/dist/{SearchRuntime-D0rSMKV3.js → SearchRuntime--tjedqS6.js} +5 -4
  10. package/dist/{architectureDiagram-2XIMDMQ5-DmhcSwl7.js → architectureDiagram-2XIMDMQ5-D5B3Bc8w.js} +1 -1
  11. package/dist/{chunk-GLR3WWYH-ShSppUzV.js → chunk-GLR3WWYH-C7t8eN-i.js} +1 -1
  12. package/dist/{chunk-NQ4KR5QH-BWRGvKed.js → chunk-NQ4KR5QH-ygKrPhgw.js} +1 -1
  13. package/dist/{chunk-WL4C6EOR-DLbU3P3W.js → chunk-WL4C6EOR-BX4P121X.js} +1 -1
  14. package/dist/{classDiagram-VBA2DB6C-D4y64v83.js → classDiagram-VBA2DB6C-CWWKW2ZN.js} +2 -2
  15. package/dist/{classDiagram-v2-RAHNMMFH-DXkt0Wd4.js → classDiagram-v2-RAHNMMFH-9SKKaVzh.js} +2 -2
  16. package/dist/{context-menu-BIQsQup7.js → context-menu-DwILPvwC.js} +1 -1
  17. package/dist/{cose-bilkent-S5V4N54A-BX63fiTJ.js → cose-bilkent-S5V4N54A-DCXUWrka.js} +1 -1
  18. package/dist/{dialog-D8otbqQL.js → dialog-D68sEJBe.js} +1 -1
  19. package/dist/docs-app.es.js +1 -1
  20. package/dist/{erDiagram-INFDFZHY-DjNq2UqZ.js → erDiagram-INFDFZHY-DdivnAXH.js} +1 -1
  21. package/dist/{flowDiagram-PKNHOUZH-AUeVhXRc.js → flowDiagram-PKNHOUZH-CgCGjn11.js} +1 -1
  22. package/dist/{mdx-components-m1cGPhc8.js → mdx-components-6beJPZgM.js} +3 -3
  23. package/dist/{mermaid.core-CtkJeRVj.js → mermaid.core-Dg_svxvF.js} +12 -12
  24. package/dist/{mindmap-definition-YRQLILUH-Cnl-qPDR.js → mindmap-definition-YRQLILUH-3st_5tCe.js} +1 -1
  25. package/dist/react-docs-ui.css +1 -1
  26. package/dist/react-docs-ui.es.js +10 -10
  27. package/dist/{requirementDiagram-Z7DCOOCP-Bx6NAgqy.js → requirementDiagram-Z7DCOOCP-ClKKAQ0C.js} +1 -1
  28. package/dist/{stateDiagram-RAJIS63D-DFmbSHPX.js → stateDiagram-RAJIS63D-C8Ab__vp.js} +2 -2
  29. package/dist/{stateDiagram-v2-FVOUBMTO-TjYtTjLB.js → stateDiagram-v2-FVOUBMTO-BhQBkYI5.js} +2 -2
  30. package/dist/types/components/Breadcrumb.d.ts +14 -0
  31. package/dist/types/components/Breadcrumb.d.ts.map +1 -0
  32. package/dist/types/components/DocsLayout.d.ts.map +1 -1
  33. package/dist/types/components/ExportToolbar.d.ts +8 -1
  34. package/dist/types/components/ExportToolbar.d.ts.map +1 -1
  35. package/dist/types/components/HeaderNav.d.ts.map +1 -1
  36. package/dist/types/components/MdxContent.d.ts.map +1 -1
  37. package/dist/types/components/MobileSidebar.d.ts +2 -7
  38. package/dist/types/components/MobileSidebar.d.ts.map +1 -1
  39. package/dist/types/components/ReadingProgressBar.d.ts +4 -0
  40. package/dist/types/components/ReadingProgressBar.d.ts.map +1 -0
  41. package/dist/types/components/ReleaseMetaBar.d.ts +1 -1
  42. package/dist/types/components/ReleaseMetaBar.d.ts.map +1 -1
  43. package/dist/types/components/SearchLauncher.d.ts +2 -1
  44. package/dist/types/components/SearchLauncher.d.ts.map +1 -1
  45. package/dist/types/components/ai/AIChatMessage.d.ts.map +1 -1
  46. package/dist/types/components/search/SearchItem.d.ts.map +1 -1
  47. package/dist/types/components/search/SearchProvider.d.ts +2 -1
  48. package/dist/types/components/search/SearchProvider.d.ts.map +1 -1
  49. package/dist/types/components/search/SearchRuntime.d.ts +2 -1
  50. package/dist/types/components/search/SearchRuntime.d.ts.map +1 -1
  51. package/dist/types/hooks/useScrollPosition.d.ts.map +1 -1
  52. package/dist/types/lib/changelog.d.ts +1 -1
  53. package/dist/types/lib/changelog.d.ts.map +1 -1
  54. package/dist/types/lib/config.d.ts +21 -0
  55. package/dist/types/lib/config.d.ts.map +1 -1
  56. package/dist/types/lib/reading-time.d.ts +7 -0
  57. package/dist/types/lib/reading-time.d.ts.map +1 -0
  58. package/dist/types/lib/search/index.d.ts +2 -1
  59. package/dist/types/lib/search/index.d.ts.map +1 -1
  60. package/dist/types/lib/search/runtime/highlighter.d.ts +10 -0
  61. package/dist/types/lib/search/runtime/highlighter.d.ts.map +1 -1
  62. package/dist/types/lib/search/runtime/search-engine.d.ts.map +1 -1
  63. package/dist/types/lib/search/runtime/types.d.ts +1 -0
  64. package/dist/types/lib/search/runtime/types.d.ts.map +1 -1
  65. package/dist/types/lib/utils.d.ts +2 -0
  66. package/dist/types/lib/utils.d.ts.map +1 -1
  67. package/dist/utils-KFBtT4Mx.js +25 -0
  68. package/package.json +16 -5
  69. package/scripts/cli.mjs +20 -0
  70. package/scripts/generate-changelog-index.mjs +89 -0
  71. package/scripts/generate-doc-git-meta.mjs +63 -0
  72. package/scripts/generate-feed.mjs +122 -0
  73. package/scripts/generate-llms-files.mjs +34 -0
  74. package/scripts/generate-search-index.mjs +236 -0
  75. package/scripts/generate-shiki-bundle.mjs +80 -0
  76. package/scripts/generate-sitemap.mjs +186 -0
  77. package/dist/AIChatDialog-BnAX3dRn.js +0 -418
  78. package/dist/utils-Ct96Mtjw.js +0 -8
@@ -0,0 +1,63 @@
1
+ import { promisify } from "node:util"
2
+ import { execFile } from "node:child_process"
3
+ import { glob } from "glob"
4
+ import path from "node:path"
5
+ import fs from "node:fs/promises"
6
+
7
+ const execFileAsync = promisify(execFile)
8
+ const rootDir = process.cwd()
9
+ const publicDir = path.join(rootDir, "public")
10
+ const outputPath = path.join(publicDir, "doc-git-meta.json")
11
+
12
+ async function getGitValue(args) {
13
+ try {
14
+ const { stdout } = await execFileAsync("git", args, { cwd: rootDir })
15
+ return stdout.trim()
16
+ } catch {
17
+ return ""
18
+ }
19
+ }
20
+
21
+ async function main() {
22
+ const repoRoot = (await getGitValue(["rev-parse", "--show-toplevel"])) || rootDir
23
+ const files = await glob("public/docs/**/*.{md,mdx}", {
24
+ cwd: rootDir,
25
+ absolute: true,
26
+ nodir: true,
27
+ })
28
+
29
+ const entries = []
30
+ for (const file of files) {
31
+ const relativePath = path.relative(repoRoot, file).replace(/\\/g, "/")
32
+ const [lastUpdated, author] = await Promise.all([
33
+ getGitValue(["log", "-1", "--format=%cI", "--", relativePath]),
34
+ getGitValue(["log", "-1", "--format=%an", "--", relativePath]),
35
+ ])
36
+ entries.push({
37
+ relativePath: path.relative(rootDir, file).replace(/\\/g, "/"),
38
+ meta: {
39
+ lastUpdated: lastUpdated || undefined,
40
+ author: author || undefined,
41
+ },
42
+ })
43
+ }
44
+ const docMeta = Object.fromEntries(
45
+ entries
46
+ .filter(({ meta }) => meta.lastUpdated || meta.author)
47
+ .map(({ relativePath, meta }) => [relativePath, meta])
48
+ )
49
+
50
+ const payload = {
51
+ generatedAt: new Date().toISOString(),
52
+ files: docMeta,
53
+ }
54
+
55
+ await fs.mkdir(publicDir, { recursive: true })
56
+ await fs.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8")
57
+ console.log(`[doc-git-meta] Wrote ${Object.keys(docMeta).length} entries to public/doc-git-meta.json`)
58
+ }
59
+
60
+ main().catch((error) => {
61
+ console.error("[doc-git-meta] Failed:", error)
62
+ process.exitCode = 1
63
+ })
@@ -0,0 +1,122 @@
1
+ import fs from "node:fs/promises"
2
+ import path from "node:path"
3
+ import yaml from "js-yaml"
4
+
5
+ const rootDir = process.cwd()
6
+ const publicDir = path.join(rootDir, "public")
7
+
8
+ function escapeXml(str) {
9
+ return String(str || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;")
10
+ }
11
+
12
+ async function loadSiteConfig() {
13
+ const configPath = path.join(publicDir, "config", "site.yaml")
14
+ try {
15
+ const content = await fs.readFile(configPath, "utf8")
16
+ return yaml.load(content)
17
+ } catch {
18
+ return {}
19
+ }
20
+ }
21
+
22
+ async function loadChangelogIndex(lang) {
23
+ const indexPath = path.join(publicDir, `changelog-index-${lang}.json`)
24
+ try {
25
+ const content = await fs.readFile(indexPath, "utf8")
26
+ return JSON.parse(content)
27
+ } catch {
28
+ return { lang, items: [] }
29
+ }
30
+ }
31
+
32
+ async function main() {
33
+ const config = await loadSiteConfig()
34
+ const siteUrl = config?.site?.url
35
+ if (!siteUrl) {
36
+ console.warn("[feed] Skipping: site.url is not configured in site.yaml")
37
+ return
38
+ }
39
+
40
+ const feedConfig = config?.feed || {}
41
+ if (feedConfig.enabled === false) {
42
+ console.log("[feed] Disabled in config")
43
+ return
44
+ }
45
+
46
+ const siteTitle = feedConfig.title || config?.site?.title || "Documentation"
47
+ const siteDescription = feedConfig.description || config?.site?.description || ""
48
+ const limit = feedConfig.limit || 20
49
+ const baseUrl = siteUrl.replace(/\/+$/, "")
50
+
51
+ // 收集所有语言的 changelog
52
+ const docsRoot = path.join(publicDir, "docs")
53
+ const entries = await fs.readdir(docsRoot, { withFileTypes: true })
54
+ const langs = entries.filter(entry => entry.isDirectory()).map(entry => entry.name)
55
+
56
+ const allItems = []
57
+ for (const lang of langs) {
58
+ const index = await loadChangelogIndex(lang)
59
+ for (const item of index.items || []) {
60
+ allItems.push({ ...item, lang })
61
+ }
62
+ }
63
+
64
+ // 按日期降序排序
65
+ allItems.sort((a, b) => {
66
+ const dateA = Date.parse(a.date || "")
67
+ const dateB = Date.parse(b.date || "")
68
+ if (Number.isNaN(dateA) && Number.isNaN(dateB)) return 0
69
+ if (Number.isNaN(dateA)) return 1
70
+ if (Number.isNaN(dateB)) return -1
71
+ return dateB - dateA
72
+ })
73
+
74
+ const items = allItems.slice(0, limit)
75
+
76
+ if (items.length === 0) {
77
+ console.log("[feed] No changelog items found")
78
+ return
79
+ }
80
+
81
+ const lastBuildDate = new Date().toUTCString()
82
+
83
+ const itemsXml = items.map(item => {
84
+ const link = `${baseUrl}${item.path}`
85
+ const pubDate = item.date ? new Date(item.date).toUTCString() : lastBuildDate
86
+ const tags = []
87
+ if (item.type) tags.push(`<category>${escapeXml(item.type)}</category>`)
88
+ if (item.breaking) tags.push(`<category>breaking</category>`)
89
+
90
+ return ` <item>
91
+ <title>${escapeXml(item.title)}</title>
92
+ <link>${escapeXml(link)}</link>
93
+ <guid isPermaLink="true">${escapeXml(link)}</guid>
94
+ <description>${escapeXml(item.summary || "")}</description>
95
+ <pubDate>${pubDate}</pubDate>${tags.length > 0 ? "\n " + tags.join("\n ") : ""}
96
+ </item>`
97
+ }).join("\n")
98
+
99
+ const rss = `<?xml version="1.0" encoding="UTF-8"?>
100
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
101
+ <channel>
102
+ <title>${escapeXml(siteTitle)}</title>
103
+ <description>${escapeXml(siteDescription)}</description>
104
+ <link>${escapeXml(baseUrl)}</link>
105
+ <atom:link href="${escapeXml(baseUrl)}/feed.xml" rel="self" type="application/rss+xml"/>
106
+ <lastBuildDate>${lastBuildDate}</lastBuildDate>
107
+ <language>zh-cn</language>
108
+ <generator>react-docs-ui</generator>
109
+ ${itemsXml}
110
+ </channel>
111
+ </rss>
112
+ `
113
+
114
+ const outputPath = path.join(publicDir, "feed.xml")
115
+ await fs.writeFile(outputPath, rss, "utf8")
116
+ console.log(`[feed] Generated ${items.length} items to feed.xml`)
117
+ }
118
+
119
+ main().catch((error) => {
120
+ console.error("[feed] Failed:", error)
121
+ process.exitCode = 1
122
+ })
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs/promises"
2
+ import path from "node:path"
3
+ import { glob } from "glob"
4
+
5
+ const rootDir = process.cwd()
6
+ const publicDir = path.join(rootDir, "public")
7
+
8
+ function stripFrontmatter(source) {
9
+ if (!source.startsWith("---\n")) return source
10
+ const endIndex = source.indexOf("\n---\n", 4)
11
+ return endIndex === -1 ? source : source.slice(endIndex + 5)
12
+ }
13
+
14
+ async function main() {
15
+ const files = await glob("public/docs/**/*.{md,mdx}", { cwd: rootDir, absolute: true, nodir: true })
16
+ const docs = await Promise.all(files.map(async (file) => {
17
+ const relative = path.relative(rootDir, file).replace(/\\/g, "/")
18
+ const urlPath = relative.replace(/^public\/docs\//, "").replace(/\.(md|mdx)$/i, "")
19
+ const content = stripFrontmatter(await fs.readFile(file, "utf8")).trim()
20
+ return { relative, urlPath, content }
21
+ }))
22
+
23
+ const llms = docs.map((doc) => `- /${doc.urlPath} | ${doc.relative}`).join("\n")
24
+ const llmsFull = docs.map((doc) => `# /${doc.urlPath}\n\n${doc.content}`).join("\n\n")
25
+
26
+ await fs.writeFile(path.join(publicDir, "llms.txt"), `${llms}\n`, "utf8")
27
+ await fs.writeFile(path.join(publicDir, "llms-full.txt"), `${llmsFull}\n`, "utf8")
28
+ console.log(`[llms] Wrote ${docs.length} docs to llms.txt and llms-full.txt`)
29
+ }
30
+
31
+ main().catch((error) => {
32
+ console.error("[llms] Failed:", error)
33
+ process.exitCode = 1
34
+ })
@@ -0,0 +1,236 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ const ROOT_DIR = process.cwd()
5
+ const PUBLIC_DIR = path.join(ROOT_DIR, 'public')
6
+ const DOCS_DIR = path.join(PUBLIC_DIR, 'docs')
7
+
8
+ const SEARCH_INDEX_VERSION = '2.0.0'
9
+ const MAX_CONTENT_LENGTH = 300
10
+
11
+ function isChinese(text) {
12
+ return /[\u4e00-\u9fa5]/.test(text)
13
+ }
14
+
15
+ function tokenize(text) {
16
+ if (!text) return []
17
+ const tokens = []
18
+ const parts = text.split(/(\s+|[,。!?、;:""''()【】《》\n\r]+)/)
19
+ for (const part of parts) {
20
+ if (!part.trim()) continue
21
+ if (isChinese(part)) {
22
+ const chars = part.split('')
23
+ for (let i = 0; i < chars.length - 1; i++) {
24
+ tokens.push(chars[i] + chars[i + 1])
25
+ }
26
+ tokens.push(...chars.filter(c => c.trim()))
27
+ } else {
28
+ const words = part.toLowerCase().split(/[^a-zA-Z0-9]+/).filter(w => w.length > 0)
29
+ tokens.push(...words)
30
+ }
31
+ }
32
+ return [...new Set(tokens)]
33
+ }
34
+
35
+ function parseFrontmatter(content) {
36
+ const normalized = content.replace(/^\uFEFF/, '')
37
+ const lines = normalized.split(/\r?\n/)
38
+ const data = {}
39
+ let contentStart = 0
40
+ if (lines[0]?.startsWith('---')) {
41
+ let i = 1
42
+ while (i < lines.length && !lines[i]?.startsWith('---')) {
43
+ const line = lines[i]
44
+ const colonIndex = line?.indexOf(':') ?? -1
45
+ if (colonIndex > 0 && line) {
46
+ const key = line.substring(0, colonIndex).trim()
47
+ const value = line.substring(colonIndex + 1).trim()
48
+ data[key] = value
49
+ }
50
+ i++
51
+ }
52
+ if (i < lines.length && lines[i]?.startsWith('---')) {
53
+ contentStart = i + 1
54
+ }
55
+ }
56
+ return { data, content: lines.slice(contentStart).join('\n') }
57
+ }
58
+
59
+ function cleanContent(text) {
60
+ return text
61
+ .replace(/\s+/g, ' ')
62
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
63
+ .replace(/[#*`_~>|]/g, '')
64
+ .replace(/\$\$?[^$]+\$\$?/g, '')
65
+ .trim()
66
+ }
67
+
68
+ function extractTextContent(node) {
69
+ if (!node || typeof node !== 'object') return ''
70
+ if (node.type === 'text') return node.value || ''
71
+ if (node.type === 'inlineCode') return `\`${node.value || ''}\``
72
+ if (node.type === 'code') return node.value || ''
73
+ if ('children' in node && Array.isArray(node.children)) {
74
+ return node.children.map(extractTextContent).join(' ')
75
+ }
76
+ return ''
77
+ }
78
+
79
+ async function parseMarkdown(content) {
80
+ const { unified } = await import('unified')
81
+ const remarkParse = (await import('remark-parse')).default
82
+ const remarkGfm = (await import('remark-gfm')).default
83
+ const remarkMath = (await import('remark-math')).default
84
+
85
+ const { data: frontmatter, content: markdownContent } = parseFrontmatter(content)
86
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMath)
87
+ const tree = processor.parse(markdownContent)
88
+
89
+ const title = frontmatter.title || ''
90
+ const sections = []
91
+ let currentSection = null
92
+ let contentParts = []
93
+
94
+ const saveCurrentSection = () => {
95
+ if (currentSection && contentParts.length > 0) {
96
+ currentSection.content = cleanContent(contentParts.join(' '))
97
+ sections.push(currentSection)
98
+ }
99
+ contentParts = []
100
+ }
101
+
102
+ for (const node of tree.children) {
103
+ if (node.type === 'heading') {
104
+ saveCurrentSection()
105
+ currentSection = {
106
+ title: extractTextContent(node),
107
+ content: '',
108
+ level: node.depth,
109
+ }
110
+ } else if (currentSection) {
111
+ const text = extractTextContent(node)
112
+ if (text) contentParts.push(text)
113
+ }
114
+ }
115
+
116
+ saveCurrentSection()
117
+
118
+ if (sections.length === 0 && title) {
119
+ const allContent = []
120
+ for (const node of tree.children) {
121
+ const text = extractTextContent(node)
122
+ if (text) allContent.push(text)
123
+ }
124
+ if (allContent.length > 0) {
125
+ sections.push({
126
+ title,
127
+ content: cleanContent(allContent.join(' ')),
128
+ level: 1,
129
+ })
130
+ }
131
+ }
132
+
133
+ return { title, sections }
134
+ }
135
+
136
+ function scanDocsDirectory(docsDir, baseDir, lang, results = []) {
137
+ if (!fs.existsSync(docsDir)) return results
138
+ const entries = fs.readdirSync(docsDir, { withFileTypes: true })
139
+ for (const entry of entries) {
140
+ const fullPath = path.join(docsDir, entry.name)
141
+ if (entry.isDirectory()) {
142
+ scanDocsDirectory(fullPath, baseDir, lang, results)
143
+ } else if (entry.isFile() && ['.md', '.mdx'].includes(path.extname(entry.name))) {
144
+ results.push({ filePath: fullPath, relativePath: path.relative(baseDir, fullPath), lang })
145
+ }
146
+ }
147
+ return results
148
+ }
149
+
150
+ function generateId(docPath, sectionTitle) {
151
+ const base = docPath.replace(/[\/\\]/g, '-')
152
+ const anchor = sectionTitle.toLowerCase().replace(/[^\w\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, '')
153
+ return anchor ? `${base}--${anchor}` : base
154
+ }
155
+
156
+ function buildUrl(lang, docPath, sectionTitle) {
157
+ const anchor = sectionTitle.toLowerCase().replace(/[^\w\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, '')
158
+ let url = `/${lang}/${docPath}`
159
+ if (anchor) url += `#${anchor}`
160
+ return url
161
+ }
162
+
163
+ function truncateContent(content, maxLength) {
164
+ if (content.length <= maxLength) return content
165
+ return content.slice(0, maxLength)
166
+ }
167
+
168
+ async function generateSearchIndex(lang) {
169
+ console.log(`Generating search index for language: ${lang}`)
170
+ const docsLangDir = path.join(DOCS_DIR, lang)
171
+ const files = scanDocsDirectory(docsLangDir, path.join(PUBLIC_DIR, 'docs'), lang)
172
+ const sections = []
173
+
174
+ for (const file of files) {
175
+ try {
176
+ const content = fs.readFileSync(file.filePath, 'utf-8')
177
+ const docPath = file.relativePath
178
+ .replace(new RegExp(`^${lang}[/\\\\]`), '')
179
+ .replace(/\.(md|mdx)$/, '')
180
+ .replace(/[/\\]index$/, '')
181
+
182
+ const parsed = await parseMarkdown(content)
183
+
184
+ for (const section of parsed.sections) {
185
+ const id = generateId(docPath, section.title)
186
+ const url = buildUrl(lang, docPath, section.title)
187
+ const fullText = section.title + ' ' + section.content
188
+ const tokens = await tokenize(fullText)
189
+
190
+ sections.push({
191
+ id,
192
+ pageTitle: parsed.title,
193
+ sectionTitle: section.title,
194
+ content: truncateContent(section.content, MAX_CONTENT_LENGTH),
195
+ url,
196
+ lang,
197
+ tokens,
198
+ })
199
+ }
200
+ } catch (error) {
201
+ console.warn(`Failed to process ${file.filePath}:`, error.message)
202
+ }
203
+ }
204
+
205
+ return {
206
+ version: SEARCH_INDEX_VERSION,
207
+ generatedAt: Date.now(),
208
+ lang,
209
+ sections,
210
+ }
211
+ }
212
+
213
+ async function main() {
214
+ console.log('Starting search index generation...')
215
+ if (!fs.existsSync(DOCS_DIR)) {
216
+ console.error('Docs directory not found:', DOCS_DIR)
217
+ process.exit(1)
218
+ }
219
+
220
+ const entries = fs.readdirSync(DOCS_DIR, { withFileTypes: true })
221
+ const langs = entries.filter(e => e.isDirectory()).map(e => e.name)
222
+ console.log('Found languages:', langs.join(', '))
223
+
224
+ for (const lang of langs) {
225
+ const index = await generateSearchIndex(lang)
226
+ const outputPath = path.join(PUBLIC_DIR, `search-index-${lang}.json`)
227
+ fs.writeFileSync(outputPath, JSON.stringify(index))
228
+ console.log(`Generated: ${outputPath} (${index.sections.length} sections)`)
229
+ }
230
+ console.log('Search index generation complete!')
231
+ }
232
+
233
+ main().catch(error => {
234
+ console.error('Error generating search index:', error)
235
+ process.exit(1)
236
+ })
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import yaml from "js-yaml"
4
+
5
+ const rootDir = process.cwd()
6
+ const configDir = path.join(rootDir, "public", "config")
7
+ const outputFile = path.join(rootDir, "src", "generated", "shiki-bundle.ts")
8
+ const defaultLangs = [
9
+ "javascript", "typescript", "jsx", "tsx", "bash", "shell",
10
+ "python", "java", "c", "cpp", "csharp", "go", "rust", "ruby",
11
+ "php", "swift", "kotlin", "sql", "json", "yaml", "toml",
12
+ "markdown", "html", "css", "scss", "less", "vue", "svelte",
13
+ "docker", "nginx", "xml", "diff", "regex",
14
+ ]
15
+ const langAliasMap = {
16
+ js: "javascript",
17
+ ts: "typescript",
18
+ sh: "shell",
19
+ shell: "bash",
20
+ yml: "yaml",
21
+ md: "markdown",
22
+ rb: "ruby",
23
+ py: "python",
24
+ dockerfile: "docker",
25
+ conf: "nginx",
26
+ }
27
+
28
+ function resolveLang(lang) {
29
+ const normalized = String(lang || "").trim().toLowerCase()
30
+ return normalized ? langAliasMap[normalized] || normalized : null
31
+ }
32
+
33
+ function readYamlConfig(fileName) {
34
+ const filePath = path.join(configDir, fileName)
35
+ return fs.existsSync(filePath) ? yaml.load(fs.readFileSync(filePath, "utf8")) : null
36
+ }
37
+
38
+ function collectBundleConfig() {
39
+ const configs = ["site.yaml", "site.en.yaml"].map(readYamlConfig).filter(Boolean)
40
+ const langs = new Set()
41
+ const themes = new Set()
42
+ for (const config of configs) {
43
+ const configuredLangs = config?.codeHighlight?.langs
44
+ const sourceLangs = Array.isArray(configuredLangs) && configuredLangs.length > 0 ? configuredLangs : defaultLangs
45
+ sourceLangs.forEach(lang => {
46
+ const resolved = resolveLang(lang)
47
+ if (resolved) langs.add(resolved)
48
+ })
49
+ themes.add(String(config?.codeHighlight?.lightTheme || "github-light").trim())
50
+ themes.add(String(config?.codeHighlight?.darkTheme || "github-dark").trim())
51
+ }
52
+ if (langs.size === 0) defaultLangs.forEach(lang => langs.add(resolveLang(lang)))
53
+ if (themes.size === 0) {
54
+ themes.add("github-light")
55
+ themes.add("github-dark")
56
+ }
57
+ return { langs: Array.from(langs).sort(), themes: Array.from(themes).sort() }
58
+ }
59
+
60
+ function toObjectEntries(values, basePath) {
61
+ return values.map(value => ` ${JSON.stringify(value)}: () => import(${JSON.stringify(`${basePath}/${value}`)}),`).join("\n")
62
+ }
63
+
64
+ function main() {
65
+ const bundleConfig = collectBundleConfig()
66
+ const fileContent = `export const siteShikiBundle = {
67
+ langs: {
68
+ ${toObjectEntries(bundleConfig.langs, "shiki/langs")}
69
+ },
70
+ themes: {
71
+ ${toObjectEntries(bundleConfig.themes, "shiki/themes")}
72
+ },
73
+ }
74
+ `
75
+ fs.mkdirSync(path.dirname(outputFile), { recursive: true })
76
+ fs.writeFileSync(outputFile, fileContent, "utf8")
77
+ console.log(`[shiki-bundle] generated ${path.relative(rootDir, outputFile)} with ${bundleConfig.langs.length} languages and ${bundleConfig.themes.length} themes`)
78
+ }
79
+
80
+ main()