feat: copy markdown files to output directory for download feature

Add build step to copy all .md files to dist directory to support direct markdown file access for download and copy features. Update sitemap generation to reflect recent changes and improve code formatting. The copy operation excludes special directories like .vitepress, public, and node_modules to ensure only documentation content is available for download.
This commit is contained in:
sanbuphy
2026-03-23 21:24:51 +08:00
parent e2796ea75d
commit 679dc7ea90
4 changed files with 67 additions and 15 deletions
+28
View File
@@ -1239,6 +1239,34 @@ Sitemap: ${siteUrl}/sitemap.xml
'✓ Generated robots.txt with sitemap URL:', '✓ Generated robots.txt with sitemap URL:',
`${siteUrl}/sitemap.xml` `${siteUrl}/sitemap.xml`
) )
// Copy all .md files to dist for download/copy features
const srcDir = siteConfig.srcDir || path.resolve(outDir, '../../')
function copyMdFiles(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true })
}
const entries = fs.readdirSync(src, { withFileTypes: true })
for (const entry of entries) {
const srcPath = path.join(src, entry.name)
const destPath = path.join(dest, entry.name)
if (entry.isDirectory()) {
if (
entry.name === '.vitepress' ||
entry.name === 'public' ||
entry.name === 'node_modules'
)
continue
copyMdFiles(srcPath, destPath)
} else if (entry.isFile() && entry.name.endsWith('.md')) {
fs.copyFileSync(srcPath, destPath)
}
}
}
console.log(
'✓ Copying markdown files to output directory for download feature...'
)
copyMdFiles(srcDir, outDir)
}, },
// 多语言配置 - 使用 cn/en-us/ja 结构 // 多语言配置 - 使用 cn/en-us/ja 结构
@@ -50,7 +50,8 @@
</template> </template>
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue' import { onMounted, onUnmounted, ref, computed } from 'vue'
import { useData } from 'vitepress'
import iconChatGPT from './icons/chatgpt.svg?raw' import iconChatGPT from './icons/chatgpt.svg?raw'
import iconCheck from './icons/check.svg?raw' import iconCheck from './icons/check.svg?raw'
@@ -61,7 +62,16 @@ import iconDownload from './icons/download.svg?raw'
import iconExternal from './icons/external.svg?raw' import iconExternal from './icons/external.svg?raw'
import iconMarkdown from './icons/markdown.svg?raw' import iconMarkdown from './icons/markdown.svg?raw'
import { downloadFile, resolveMarkdownPageURL } from './utils' import { downloadFile } from './utils'
const { page, site } = useData()
const getMarkdownUrl = () => {
const origin = window.location.origin
let base = site.value.base || '/'
if (!base.endsWith('/')) base += '/'
return `${origin}${base}${page.value.filePath}`
}
const aiProviders = [ const aiProviders = [
{ name: 'ChatGPT', icon: iconChatGPT, url: 'https://chatgpt.com/?hints=search&prompt=' }, { name: 'ChatGPT', icon: iconChatGPT, url: 'https://chatgpt.com/?hints=search&prompt=' },
@@ -98,10 +108,8 @@ function toggleDropdown() {
} }
} }
const currentURL = window.location.origin + window.location.pathname
function copyAsMarkdown() { function copyAsMarkdown() {
fetch(resolveMarkdownPageURL(currentURL)) fetch(getMarkdownUrl())
.then((r) => r.text()) .then((r) => r.text())
.then((text) => navigator.clipboard.writeText(text)) .then((text) => navigator.clipboard.writeText(text))
.then(() => { .then(() => {
@@ -116,22 +124,22 @@ function copyAsMarkdown() {
} }
function viewAsMarkdown() { function viewAsMarkdown() {
window.open(resolveMarkdownPageURL(currentURL), '_blank') window.open(getMarkdownUrl(), '_blank')
isOpen.value = false isOpen.value = false
} }
function openInAI(provider) { function openInAI(provider) {
const markdownUrl = resolveMarkdownPageURL(currentURL) const markdownUrl = getMarkdownUrl()
const prompt = `Read from ${markdownUrl} so I can ask questions about it.` const prompt = `Read from ${markdownUrl} so I can ask questions about it.`
window.open(provider.url + encodeURIComponent(prompt), '_blank') window.open(provider.url + encodeURIComponent(prompt), '_blank')
isOpen.value = false isOpen.value = false
} }
function downloadMarkdown() { function downloadMarkdown() {
fetch(resolveMarkdownPageURL(currentURL)) fetch(getMarkdownUrl())
.then((r) => r.text()) .then((r) => r.text())
.then((text) => { .then((text) => {
const filename = resolveMarkdownPageURL(currentURL).split('/').pop() || 'page.md' const filename = page.value.filePath.split('/').pop() || 'page.md'
downloadFile(filename, text, 'text/markdown') downloadFile(filename, text, 'text/markdown')
downloaded.value = true downloaded.value = true
setTimeout(() => { setTimeout(() => {
+2 -2
View File
@@ -949,7 +949,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/</loc>
<lastmod>2026-03-18T07:59:13-05:00</lastmod> <lastmod>2026-03-23T17:36:13+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/"/>
@@ -1056,7 +1056,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/basics/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/basics/</loc>
<lastmod>2026-03-02T12:42:07+08:00</lastmod> <lastmod>2026-03-23T17:36:13+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/basics/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/basics/"/>
+20 -4
View File
@@ -16,7 +16,18 @@ const docsDir = path.resolve(__dirname, '../docs')
const publicDir = path.resolve(__dirname, '../docs/public') const publicDir = path.resolve(__dirname, '../docs/public')
// 支持的语言 // 支持的语言
const locales = ['zh-cn', 'en', 'zh-tw', 'ja-jp', 'ko-kr', 'es-es', 'fr-fr', 'de-de', 'ar-sa', 'vi-vn'] const locales = [
'zh-cn',
'en',
'zh-tw',
'ja-jp',
'ko-kr',
'es-es',
'fr-fr',
'de-de',
'ar-sa',
'vi-vn'
]
// 基础 URL (根据部署环境动态确定) // 基础 URL (根据部署环境动态确定)
const getBaseUrl = () => { const getBaseUrl = () => {
@@ -46,7 +57,12 @@ function scanMarkdownFiles(dir, basePath = '') {
if (entry.isDirectory()) { if (entry.isDirectory()) {
// 跳过特殊目录 // 跳过特殊目录
if (entry.name === '.vitepress' || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'public') { if (
entry.name === '.vitepress' ||
entry.name === 'node_modules' ||
entry.name === 'dist' ||
entry.name === 'public'
) {
continue continue
} }
files.push(...scanMarkdownFiles(fullPath, relativePath)) files.push(...scanMarkdownFiles(fullPath, relativePath))
@@ -152,7 +168,7 @@ function main() {
baseFiles = scanMarkdownFiles(zhCnDir) baseFiles = scanMarkdownFiles(zhCnDir)
} else { } else {
// 如果没有 zh-cn 目录,扫描 docs 根目录 // 如果没有 zh-cn 目录,扫描 docs 根目录
baseFiles = scanMarkdownFiles(docsDir).filter(f => !f.includes('/')) baseFiles = scanMarkdownFiles(docsDir).filter((f) => !f.includes('/'))
} }
console.log(`📄 Found ${baseFiles} base pages`) console.log(`📄 Found ${baseFiles} base pages`)
@@ -263,7 +279,7 @@ function getPriority(filePath) {
function getHreflangCode(locale) { function getHreflangCode(locale) {
const map = { const map = {
'zh-cn': 'zh-CN', 'zh-cn': 'zh-CN',
'en': 'en', en: 'en',
'zh-tw': 'zh-TW', 'zh-tw': 'zh-TW',
'ja-jp': 'ja', 'ja-jp': 'ja',
'ko-kr': 'ko', 'ko-kr': 'ko',