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.
- package/dist/AIChatDialog-C2mEAX2T.js +407 -0
- package/dist/{AISelectionTrigger-SLyb8Vep.js → AISelectionTrigger-CWTJRvHo.js} +1 -1
- package/dist/{AISettingsPanel-Bsd_wLP4.js → AISettingsPanel-C9Bb_QtE.js} +1 -1
- package/dist/{DocsApp-B-VaP4ms.js → DocsApp-CTh7Pa9A.js} +3867 -3616
- package/dist/{GlobalContextMenu-BtVjQk0v.js → GlobalContextMenu-DAIKsl1l.js} +1 -1
- package/dist/{MdxContent-CZLqZlNp.js → MdxContent-BAm4izu3.js} +305 -317
- package/dist/{MdxContent.lazy-i9phqEH3.js → MdxContent.lazy-vS9NrZj4.js} +1 -1
- package/dist/{SearchDialog-DWVre6R9.js → SearchDialog-CLbtpf7I.js} +172 -127
- package/dist/{SearchRuntime-D0rSMKV3.js → SearchRuntime--tjedqS6.js} +5 -4
- package/dist/{architectureDiagram-2XIMDMQ5-DmhcSwl7.js → architectureDiagram-2XIMDMQ5-D5B3Bc8w.js} +1 -1
- package/dist/{chunk-GLR3WWYH-ShSppUzV.js → chunk-GLR3WWYH-C7t8eN-i.js} +1 -1
- package/dist/{chunk-NQ4KR5QH-BWRGvKed.js → chunk-NQ4KR5QH-ygKrPhgw.js} +1 -1
- package/dist/{chunk-WL4C6EOR-DLbU3P3W.js → chunk-WL4C6EOR-BX4P121X.js} +1 -1
- package/dist/{classDiagram-VBA2DB6C-D4y64v83.js → classDiagram-VBA2DB6C-CWWKW2ZN.js} +2 -2
- package/dist/{classDiagram-v2-RAHNMMFH-DXkt0Wd4.js → classDiagram-v2-RAHNMMFH-9SKKaVzh.js} +2 -2
- package/dist/{context-menu-BIQsQup7.js → context-menu-DwILPvwC.js} +1 -1
- package/dist/{cose-bilkent-S5V4N54A-BX63fiTJ.js → cose-bilkent-S5V4N54A-DCXUWrka.js} +1 -1
- package/dist/{dialog-D8otbqQL.js → dialog-D68sEJBe.js} +1 -1
- package/dist/docs-app.es.js +1 -1
- package/dist/{erDiagram-INFDFZHY-DjNq2UqZ.js → erDiagram-INFDFZHY-DdivnAXH.js} +1 -1
- package/dist/{flowDiagram-PKNHOUZH-AUeVhXRc.js → flowDiagram-PKNHOUZH-CgCGjn11.js} +1 -1
- package/dist/{mdx-components-m1cGPhc8.js → mdx-components-6beJPZgM.js} +3 -3
- package/dist/{mermaid.core-CtkJeRVj.js → mermaid.core-Dg_svxvF.js} +12 -12
- package/dist/{mindmap-definition-YRQLILUH-Cnl-qPDR.js → mindmap-definition-YRQLILUH-3st_5tCe.js} +1 -1
- package/dist/react-docs-ui.css +1 -1
- package/dist/react-docs-ui.es.js +10 -10
- package/dist/{requirementDiagram-Z7DCOOCP-Bx6NAgqy.js → requirementDiagram-Z7DCOOCP-ClKKAQ0C.js} +1 -1
- package/dist/{stateDiagram-RAJIS63D-DFmbSHPX.js → stateDiagram-RAJIS63D-C8Ab__vp.js} +2 -2
- package/dist/{stateDiagram-v2-FVOUBMTO-TjYtTjLB.js → stateDiagram-v2-FVOUBMTO-BhQBkYI5.js} +2 -2
- package/dist/types/components/Breadcrumb.d.ts +14 -0
- package/dist/types/components/Breadcrumb.d.ts.map +1 -0
- package/dist/types/components/DocsLayout.d.ts.map +1 -1
- package/dist/types/components/ExportToolbar.d.ts +8 -1
- package/dist/types/components/ExportToolbar.d.ts.map +1 -1
- package/dist/types/components/HeaderNav.d.ts.map +1 -1
- package/dist/types/components/MdxContent.d.ts.map +1 -1
- package/dist/types/components/MobileSidebar.d.ts +2 -7
- package/dist/types/components/MobileSidebar.d.ts.map +1 -1
- package/dist/types/components/ReadingProgressBar.d.ts +4 -0
- package/dist/types/components/ReadingProgressBar.d.ts.map +1 -0
- package/dist/types/components/ReleaseMetaBar.d.ts +1 -1
- package/dist/types/components/ReleaseMetaBar.d.ts.map +1 -1
- package/dist/types/components/SearchLauncher.d.ts +2 -1
- package/dist/types/components/SearchLauncher.d.ts.map +1 -1
- package/dist/types/components/ai/AIChatMessage.d.ts.map +1 -1
- package/dist/types/components/search/SearchItem.d.ts.map +1 -1
- package/dist/types/components/search/SearchProvider.d.ts +2 -1
- package/dist/types/components/search/SearchProvider.d.ts.map +1 -1
- package/dist/types/components/search/SearchRuntime.d.ts +2 -1
- package/dist/types/components/search/SearchRuntime.d.ts.map +1 -1
- package/dist/types/hooks/useScrollPosition.d.ts.map +1 -1
- package/dist/types/lib/changelog.d.ts +1 -1
- package/dist/types/lib/changelog.d.ts.map +1 -1
- package/dist/types/lib/config.d.ts +21 -0
- package/dist/types/lib/config.d.ts.map +1 -1
- package/dist/types/lib/reading-time.d.ts +7 -0
- package/dist/types/lib/reading-time.d.ts.map +1 -0
- package/dist/types/lib/search/index.d.ts +2 -1
- package/dist/types/lib/search/index.d.ts.map +1 -1
- package/dist/types/lib/search/runtime/highlighter.d.ts +10 -0
- package/dist/types/lib/search/runtime/highlighter.d.ts.map +1 -1
- package/dist/types/lib/search/runtime/search-engine.d.ts.map +1 -1
- package/dist/types/lib/search/runtime/types.d.ts +1 -0
- package/dist/types/lib/search/runtime/types.d.ts.map +1 -1
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/utils-KFBtT4Mx.js +25 -0
- package/package.json +16 -5
- package/scripts/cli.mjs +20 -0
- package/scripts/generate-changelog-index.mjs +89 -0
- package/scripts/generate-doc-git-meta.mjs +63 -0
- package/scripts/generate-feed.mjs +122 -0
- package/scripts/generate-llms-files.mjs +34 -0
- package/scripts/generate-search-index.mjs +236 -0
- package/scripts/generate-shiki-bundle.mjs +80 -0
- package/scripts/generate-sitemap.mjs +186 -0
- package/dist/AIChatDialog-BnAX3dRn.js +0 -418
- 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'")
|
|
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()
|