e0a5f57793
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as a monorepo subdirectory under desktop/. Source only — node_modules, dist, Cargo target and the upstream .git history are excluded. desktop/ keeps its own independent Cargo workspace (desktop/src-tauri with crates/*); it is not part of the backend boosapi crate, so a `cargo check` at the metapi root still compiles only the backend. Build the desktop app from desktop/src-tauri per desktop/README.md. Note: app icons are still placeholder assets pending the final BoosAPI logo. The updater signing keys live outside the repo and are not included.
661 lines
23 KiB
TypeScript
661 lines
23 KiB
TypeScript
import { readdirSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
import process from 'node:process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { transformerTwoslash } from '@shikijs/vitepress-twoslash'
|
|
import { defineConfig } from 'vitepress'
|
|
|
|
const docsSiteUrl = 'https://markstream-vue-docs.simonhe.me'
|
|
const docsOgImageUrl = `${docsSiteUrl}/og-image.svg`
|
|
const docsDefaultDescription = 'Streaming-friendly Markdown renderer for Vue 3, Vue 2, React, and Angular'
|
|
const githubRepoUrl = 'https://github.com/Simon-He95/markstream-vue'
|
|
const docsRootDir = fileURLToPath(new URL('..', import.meta.url))
|
|
const workspaceRootDir = fileURLToPath(new URL('../..', import.meta.url))
|
|
|
|
// TypeScript sometimes rejects VitePress site locales on the `Config` type.
|
|
// Cast to `any` to avoid strict type errors in the docs config while keeping intellisense.
|
|
const markdownParserSrc = fileURLToPath(new URL('../../packages/markdown-parser/src/index.ts', import.meta.url))
|
|
const markdownParserSrcDir = path.dirname(markdownParserSrc)
|
|
|
|
const playgroundItems = [
|
|
{ text: 'Vue 3', link: 'https://markstream-vue.simonhe.me/' },
|
|
{ text: 'React', link: 'https://markstream-react.pages.dev/' },
|
|
{ text: 'Nuxt', link: 'https://markstream-nuxt.pages.dev/' },
|
|
{ text: 'Vue 2', link: 'https://markstream-vue2.pages.dev/' },
|
|
{ text: 'Angular', link: 'https://markstream-angular.pages.dev/' },
|
|
]
|
|
|
|
const rootNav = [
|
|
{ text: 'Get started', link: '/guide/installation' },
|
|
{ text: 'Customization', link: '/guide/component-overrides' },
|
|
{ text: 'Migration / AI', link: '/guide/ai-workflows' },
|
|
{ text: 'API Reference', link: '/guide/api' },
|
|
{ text: 'Playground', items: playgroundItems },
|
|
{ text: 'GitHub', link: 'https://github.com/Simon-He95/markstream-vue' },
|
|
]
|
|
|
|
const zhNav = [
|
|
{ text: '快速开始', link: '/zh/guide/installation' },
|
|
{ text: '自定义', link: '/zh/guide/component-overrides' },
|
|
{ text: '接入与迁移', link: '/zh/guide/ai-workflows' },
|
|
{ text: 'API 参考', link: '/zh/guide/api' },
|
|
{ text: '演示', items: playgroundItems },
|
|
{ text: '搜索', link: '/zh/guide/search' },
|
|
{ text: 'GitHub', link: 'https://github.com/Simon-He95/markstream-vue' },
|
|
]
|
|
|
|
const englishGuideSidebar = [
|
|
{
|
|
text: 'Start Here',
|
|
items: [
|
|
{ text: 'Guide Home', link: '/guide/' },
|
|
{ text: 'Installation', link: '/guide/installation' },
|
|
{ text: 'Quick Start', link: '/guide/quick-start' },
|
|
{ text: 'Usage & Streaming', link: '/guide/usage' },
|
|
{ text: 'Docs Site & VitePress', link: '/guide/vitepress-docs-integration' },
|
|
{ text: 'AI Chat & Streaming', link: '/guide/ai-chat-streaming' },
|
|
{ text: 'Troubleshooting by Symptom', link: '/guide/troubleshooting-path' },
|
|
{ text: 'Props & Options', link: '/guide/props' },
|
|
{ text: 'Features Overview', link: '/guide/features' },
|
|
{ text: 'Examples', link: '/guide/examples' },
|
|
{ text: 'Playground', link: '/guide/playground' },
|
|
],
|
|
},
|
|
{
|
|
text: 'Customization',
|
|
items: [
|
|
{ text: 'Renderer & Node Components', link: '/guide/components' },
|
|
{ text: 'Override Built-in Components', link: '/guide/component-overrides' },
|
|
{ text: 'Custom Tags & Advanced Components', link: '/guide/custom-components' },
|
|
{ text: 'Advanced Parser Hooks', link: '/guide/advanced' },
|
|
{ text: 'Parser Overview', link: '/guide/parser' },
|
|
{ text: 'Parser API', link: '/guide/parser-api' },
|
|
{ text: 'Tailwind & Styling', link: '/guide/tailwind' },
|
|
{ text: 'Troubleshooting', link: '/guide/troubleshooting' },
|
|
],
|
|
},
|
|
{
|
|
text: 'Feature Guides',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: 'Code Blocks', link: '/guide/code-blocks' },
|
|
{ text: 'Code Block Header', link: '/guide/codeblock-header' },
|
|
{ text: 'CodeBlockNode', link: '/guide/code-block-node' },
|
|
{ text: 'ImageNode', link: '/guide/image-node' },
|
|
{ text: 'Math', link: '/guide/math' },
|
|
{ text: 'Mermaid', link: '/guide/mermaid' },
|
|
{ text: 'MermaidBlockNode', link: '/guide/mermaid-block-node' },
|
|
{ text: 'MermaidBlockNode (override)', link: '/guide/mermaid-block-node-override' },
|
|
{ text: 'Mermaid export demo', link: '/guide/mermaid-export-demo' },
|
|
{ text: 'D2', link: '/guide/d2' },
|
|
{ text: 'AntV Infographic', link: '/guide/infographic' },
|
|
{ text: 'ECharts', link: '/guide/echarts' },
|
|
{ text: 'Monaco', link: '/guide/monaco' },
|
|
],
|
|
},
|
|
{
|
|
text: 'Frameworks & Migration',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: 'Nuxt SSR', link: '/nuxt-ssr' },
|
|
{ text: 'Vue 2 Quick Start', link: '/guide/vue2-quick-start' },
|
|
{ text: 'Vue 2 Installation', link: '/guide/vue2-installation' },
|
|
{ text: 'Vue 2 Components', link: '/guide/vue2-components' },
|
|
{ text: 'React Quick Start', link: '/guide/react-quick-start' },
|
|
{ text: 'React Installation', link: '/guide/react-installation' },
|
|
{ text: 'React Components', link: '/guide/react-components' },
|
|
{ text: 'Migrate from react-markdown', link: '/guide/react-markdown-migration' },
|
|
{ text: 'Migration Cookbook', link: '/guide/react-markdown-migration-cookbook' },
|
|
{ text: 'Angular Quick Start', link: '/guide/angular-quick-start' },
|
|
{ text: 'Angular Installation', link: '/guide/angular-installation' },
|
|
{ text: 'AI / Skills workflows', link: '/guide/ai-workflows' },
|
|
],
|
|
},
|
|
{
|
|
text: 'Reference',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: 'API Overview', link: '/guide/api' },
|
|
{ text: 'Performance', link: '/guide/performance' },
|
|
{ text: 'Why use it?', link: '/guide/why' },
|
|
{ text: 'Compared', link: '/guide/compared' },
|
|
{ text: 'Monaco Internals', link: '/guide/monaco-internals' },
|
|
{ text: 'Legacy builds & iOS regex compatibility', link: '/guide/legacy-builds' },
|
|
{ text: 'Contributing', link: '/guide/contributing' },
|
|
],
|
|
},
|
|
]
|
|
|
|
const chineseGuideSidebar = [
|
|
{
|
|
text: '开始使用',
|
|
items: [
|
|
{ text: '指南首页', link: '/zh/guide/' },
|
|
{ text: '安装', link: '/zh/guide/installation' },
|
|
{ text: '快速开始', link: '/zh/guide/quick-start' },
|
|
{ text: '使用与流式渲染', link: '/zh/guide/usage' },
|
|
{ text: '文档站与 VitePress 集成', link: '/zh/guide/vitepress-docs-integration' },
|
|
{ text: 'AI 聊天与流式输出', link: '/zh/guide/ai-chat-streaming' },
|
|
{ text: '按症状排查', link: '/zh/guide/troubleshooting-path' },
|
|
{ text: 'Props 与选项', link: '/zh/guide/props' },
|
|
{ text: '功能特性', link: '/zh/guide/features' },
|
|
{ text: '示例', link: '/zh/guide/examples' },
|
|
{ text: 'Playground', link: '/zh/guide/playground' },
|
|
],
|
|
},
|
|
{
|
|
text: '自定义',
|
|
items: [
|
|
{ text: '渲染器与节点组件', link: '/zh/guide/components' },
|
|
{ text: '覆盖内置组件', link: '/zh/guide/component-overrides' },
|
|
{ text: '自定义标签与高级组件', link: '/zh/guide/custom-components' },
|
|
{ text: '高级解析', link: '/zh/guide/advanced' },
|
|
{ text: '解析器概览', link: '/zh/guide/parser' },
|
|
{ text: '解析器 API', link: '/zh/guide/parser-api' },
|
|
{ text: 'Tailwind 与样式', link: '/zh/guide/tailwind' },
|
|
{ text: '故障排除', link: '/zh/guide/troubleshooting' },
|
|
],
|
|
},
|
|
{
|
|
text: '功能专题',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: '代码块', link: '/zh/guide/code-blocks' },
|
|
{ text: '代码块头部', link: '/zh/guide/codeblock-header' },
|
|
{ text: 'CodeBlockNode', link: '/zh/guide/code-block-node' },
|
|
{ text: 'ImageNode', link: '/zh/guide/image-node' },
|
|
{ text: 'Math', link: '/zh/guide/math' },
|
|
{ text: 'Mermaid', link: '/zh/guide/mermaid' },
|
|
{ text: 'MermaidBlockNode', link: '/zh/guide/mermaid-block-node' },
|
|
{ text: '覆盖 MermaidBlockNode', link: '/zh/guide/mermaid-block-node-override' },
|
|
{ text: 'Mermaid 导出示例', link: '/zh/guide/mermaid-export-demo' },
|
|
{ text: 'D2 图表', link: '/zh/guide/d2' },
|
|
{ text: 'AntV Infographic', link: '/zh/guide/infographic' },
|
|
{ text: 'ECharts', link: '/zh/guide/echarts' },
|
|
{ text: 'Monaco', link: '/zh/guide/monaco' },
|
|
],
|
|
},
|
|
{
|
|
text: '框架与迁移',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: 'Nuxt SSR', link: '/zh/nuxt-ssr' },
|
|
{ text: 'Vue 2 快速开始', link: '/zh/guide/vue2-quick-start' },
|
|
{ text: 'Vue 2 安装', link: '/zh/guide/vue2-installation' },
|
|
{ text: 'Vue 2 组件', link: '/zh/guide/vue2-components' },
|
|
{ text: 'React 快速开始', link: '/zh/guide/react-quick-start' },
|
|
{ text: 'React 安装', link: '/zh/guide/react-installation' },
|
|
{ text: 'React 组件', link: '/zh/guide/react-components' },
|
|
{ text: '从 react-markdown 迁移', link: '/zh/guide/react-markdown-migration' },
|
|
{ text: '迁移 Cookbook', link: '/zh/guide/react-markdown-migration-cookbook' },
|
|
{ text: 'Angular 快速开始', link: '/zh/guide/angular-quick-start' },
|
|
{ text: 'Angular 安装', link: '/zh/guide/angular-installation' },
|
|
{ text: 'AI / Skills 工作流', link: '/zh/guide/ai-workflows' },
|
|
],
|
|
},
|
|
{
|
|
text: '参考',
|
|
collapsed: true,
|
|
items: [
|
|
{ text: 'API 总览', link: '/zh/guide/api' },
|
|
{ text: '搜索', link: '/zh/guide/search' },
|
|
{ text: '性能', link: '/zh/guide/performance' },
|
|
{ text: '为什么使用?', link: '/zh/guide/why' },
|
|
{ text: '对比', link: '/zh/guide/compared' },
|
|
{ text: 'Monaco 内部', link: '/zh/guide/monaco-internals' },
|
|
{ text: 'Legacy 构建与 iOS 正则兼容', link: '/zh/guide/legacy-builds' },
|
|
{ text: '贡献指南', link: '/zh/guide/contributing' },
|
|
],
|
|
},
|
|
]
|
|
|
|
const siteHead = [
|
|
['link', { rel: 'icon', href: '/logo.svg', type: 'image/svg+xml' }],
|
|
['meta', { name: 'theme-color', content: '#111827' }],
|
|
['meta', { name: 'robots', content: 'index,follow' }],
|
|
['meta', { property: 'og:type', content: 'website' }],
|
|
['meta', { property: 'og:site_name', content: 'markstream-vue' }],
|
|
['meta', { property: 'og:image', content: docsOgImageUrl }],
|
|
['meta', { property: 'og:image:alt', content: 'markstream-vue documentation and playground previews' }],
|
|
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
|
['meta', { name: 'twitter:image', content: docsOgImageUrl }],
|
|
]
|
|
|
|
const seoExcludedDocsPaths = new Set([
|
|
'/404',
|
|
'/LEGACY-BUILDS',
|
|
'/e2e-testing-report',
|
|
'/zh/e2e-testing-report',
|
|
'/guide/docs-style',
|
|
'/zh/guide/docs-style',
|
|
'/guide/e2e-testing-report',
|
|
'/zh/guide/e2e-testing-report',
|
|
'/guide/katex-worker-performance-analysis',
|
|
'/zh/guide/katex-worker-performance-analysis',
|
|
'/guide/monorepo-migration',
|
|
'/zh/guide/monorepo-migration',
|
|
'/guide/thanks',
|
|
'/zh/guide/thanks',
|
|
'/guide/translation',
|
|
'/zh/guide/translation',
|
|
'/katex-cache-analysis',
|
|
'/zh/katex-cache-analysis',
|
|
'/katex-worker-performance-analysis',
|
|
'/zh/katex-worker-performance-analysis',
|
|
'/llms',
|
|
'/llms.zh-CN',
|
|
'/monorepo-migration',
|
|
'/zh/monorepo-migration',
|
|
'/nuxt-ssr.zh-CN',
|
|
])
|
|
|
|
function normalizeDocsSeoPath(page: string) {
|
|
if (!page || page === '.')
|
|
return '/'
|
|
|
|
let normalized = page
|
|
|
|
if (!normalized.startsWith('/'))
|
|
normalized = `/${normalized}`
|
|
|
|
normalized = normalized.replace(/\/index\.md$/, '/')
|
|
normalized = normalized.replace(/\/index\.html$/, '/')
|
|
normalized = normalized.replace(/\.md$/, '')
|
|
normalized = normalized.replace(/\.html$/, '')
|
|
normalized = normalized.replace(/\/index$/, '/')
|
|
|
|
if (normalized !== '/' && normalized.endsWith('/'))
|
|
normalized = normalized.slice(0, -1)
|
|
|
|
return normalized || '/'
|
|
}
|
|
|
|
function createDocsSeoTitle(pageTitle?: string) {
|
|
if (!pageTitle || pageTitle === 'markstream-vue')
|
|
return 'markstream-vue'
|
|
|
|
return `${pageTitle} | markstream-vue`
|
|
}
|
|
|
|
function isDocsSeoExcluded(path: string) {
|
|
return seoExcludedDocsPaths.has(path) || path.endsWith('.zh-CN')
|
|
}
|
|
|
|
function getDocsPriorityKey(path: string) {
|
|
if (path === '/zh')
|
|
return '/'
|
|
|
|
if (path.startsWith('/zh/'))
|
|
return path.slice(3) || '/'
|
|
|
|
return path
|
|
}
|
|
|
|
function collectDocsRoutePaths(rootDir: string) {
|
|
const routePaths = new Set<string>()
|
|
|
|
function walk(currentDir: string) {
|
|
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
|
|
if (entry.name === '.vitepress' || entry.name === 'node_modules')
|
|
continue
|
|
|
|
const absolutePath = path.join(currentDir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
walk(absolutePath)
|
|
continue
|
|
}
|
|
|
|
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
continue
|
|
|
|
const relativePath = path.relative(rootDir, absolutePath).replace(/\\/g, '/')
|
|
routePaths.add(normalizeDocsSeoPath(`/${relativePath}`))
|
|
}
|
|
}
|
|
|
|
walk(rootDir)
|
|
return routePaths
|
|
}
|
|
|
|
const availableDocsRoutePaths = collectDocsRoutePaths(docsRootDir)
|
|
|
|
const docsPrimaryLandingPaths = new Set([
|
|
'/guide',
|
|
'/guide/installation',
|
|
'/guide/quick-start',
|
|
'/guide/usage',
|
|
'/guide/vitepress-docs-integration',
|
|
'/guide/ai-chat-streaming',
|
|
'/guide/troubleshooting-path',
|
|
'/guide/component-overrides',
|
|
'/guide/custom-components',
|
|
'/guide/api',
|
|
'/guide/components',
|
|
'/guide/props',
|
|
'/guide/performance',
|
|
'/guide/tailwind',
|
|
'/guide/react-markdown-migration',
|
|
'/guide/playground',
|
|
'/nuxt-ssr',
|
|
])
|
|
|
|
const docsSecondaryLandingPaths = new Set([
|
|
'/guide/parser',
|
|
'/guide/parser-api',
|
|
'/guide/features',
|
|
'/guide/examples',
|
|
'/guide/react-markdown-migration-cookbook',
|
|
'/guide/vue2-quick-start',
|
|
'/guide/react-quick-start',
|
|
'/guide/angular-quick-start',
|
|
'/guide/troubleshooting',
|
|
])
|
|
|
|
function getDocsSitemapHints(path: string) {
|
|
const priorityKey = getDocsPriorityKey(path)
|
|
|
|
if (priorityKey === '/')
|
|
return { priority: 1, changefreq: 'weekly' as const }
|
|
|
|
if (priorityKey === '/guide')
|
|
return { priority: 0.95, changefreq: 'weekly' as const }
|
|
|
|
if (docsPrimaryLandingPaths.has(priorityKey))
|
|
return { priority: 0.88, changefreq: 'weekly' as const }
|
|
|
|
if (docsSecondaryLandingPaths.has(priorityKey))
|
|
return { priority: 0.78, changefreq: 'monthly' as const }
|
|
|
|
if (priorityKey.startsWith('/guide/'))
|
|
return { priority: 0.68, changefreq: 'monthly' as const }
|
|
|
|
return { priority: 0.6, changefreq: 'monthly' as const }
|
|
}
|
|
|
|
function getDocsAlternatePaths(path: string) {
|
|
const englishPath = path === '/zh' ? '/' : path.startsWith('/zh/') ? path.slice(3) || '/' : path
|
|
const chinesePath = path === '/' ? '/zh' : path.startsWith('/zh/') ? path : `/zh${path}`
|
|
|
|
const alternates = {
|
|
english: availableDocsRoutePaths.has(englishPath) ? englishPath : null,
|
|
chinese: availableDocsRoutePaths.has(chinesePath) ? chinesePath : null,
|
|
}
|
|
|
|
return alternates
|
|
}
|
|
|
|
function createDocsBreadcrumbItems(path: string, title: string, isChinese: boolean) {
|
|
const items = []
|
|
const homeName = isChinese ? '首页' : 'Home'
|
|
const guideName = isChinese ? '指南' : 'Guide'
|
|
const homePath = isChinese ? '/zh' : '/'
|
|
|
|
items.push({
|
|
'@type': 'ListItem',
|
|
'position': 1,
|
|
'name': homeName,
|
|
'item': `${docsSiteUrl}${homePath === '/' ? '/' : homePath}`,
|
|
})
|
|
|
|
if (path === homePath || (isChinese && path === '/zh') || (!isChinese && path === '/'))
|
|
return items
|
|
|
|
const guideRoot = isChinese ? '/zh/guide' : '/guide'
|
|
if (path === guideRoot || path.startsWith(`${guideRoot}/`)) {
|
|
items.push({
|
|
'@type': 'ListItem',
|
|
'position': 2,
|
|
'name': guideName,
|
|
'item': `${docsSiteUrl}${guideRoot}`,
|
|
})
|
|
|
|
if (path !== guideRoot) {
|
|
items.push({
|
|
'@type': 'ListItem',
|
|
'position': 3,
|
|
'name': title,
|
|
'item': `${docsSiteUrl}${path}`,
|
|
})
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
items.push({
|
|
'@type': 'ListItem',
|
|
'position': 2,
|
|
'name': title,
|
|
'item': `${docsSiteUrl}${path}`,
|
|
})
|
|
|
|
return items
|
|
}
|
|
|
|
function createDocsStructuredData(path: string, title: string, description: string, isChinese: boolean) {
|
|
const graph: Record<string, any>[] = []
|
|
const homePath = isChinese ? '/zh' : '/'
|
|
|
|
if (path === homePath) {
|
|
graph.push({
|
|
'@type': 'WebSite',
|
|
'@id': `${docsSiteUrl}${homePath === '/' ? '/' : homePath}#website`,
|
|
'name': 'markstream-vue Docs',
|
|
'url': `${docsSiteUrl}${homePath === '/' ? '/' : homePath}`,
|
|
'inLanguage': isChinese ? 'zh-CN' : 'en-US',
|
|
description,
|
|
'publisher': {
|
|
'@type': 'Organization',
|
|
'name': 'markstream-vue',
|
|
'url': githubRepoUrl,
|
|
},
|
|
})
|
|
|
|
graph.push({
|
|
'@type': 'SoftwareApplication',
|
|
'name': 'markstream-vue',
|
|
'applicationCategory': 'DeveloperApplication',
|
|
'operatingSystem': 'Web',
|
|
'url': `${docsSiteUrl}${homePath === '/' ? '/' : homePath}`,
|
|
description,
|
|
'offers': {
|
|
'@type': 'Offer',
|
|
'price': '0',
|
|
'priceCurrency': 'USD',
|
|
},
|
|
'sameAs': [
|
|
githubRepoUrl,
|
|
docsSiteUrl,
|
|
'https://markstream-vue.simonhe.me/',
|
|
],
|
|
})
|
|
}
|
|
|
|
const breadcrumbItems = createDocsBreadcrumbItems(path, title, isChinese)
|
|
if (breadcrumbItems.length > 0) {
|
|
graph.push({
|
|
'@type': 'BreadcrumbList',
|
|
'itemListElement': breadcrumbItems,
|
|
})
|
|
}
|
|
|
|
if (graph.length === 0)
|
|
return []
|
|
|
|
return [[
|
|
'script',
|
|
{ type: 'application/ld+json' },
|
|
JSON.stringify({
|
|
'@context': 'https://schema.org',
|
|
'@graph': graph,
|
|
}),
|
|
]] as [string, Record<string, string>, string][]
|
|
}
|
|
|
|
export default defineConfig({
|
|
title: 'markstream-vue',
|
|
description: docsDefaultDescription,
|
|
base: process.env.VITEPRESS_BASE || '/',
|
|
head: siteHead,
|
|
markdown: {
|
|
languages: ['ts', 'tsx', 'js', 'jsx', 'vue'],
|
|
codeTransformers: [
|
|
transformerTwoslash({
|
|
explicitTrigger: true,
|
|
twoslashOptions: {
|
|
compilerOptions: {
|
|
baseUrl: workspaceRootDir,
|
|
paths: {
|
|
'markstream-vue': ['docs/.vitepress/twoslash/markstream-vue.d.ts'],
|
|
'stream-markdown-parser': ['packages/markdown-parser/src/index.ts'],
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
sitemap: {
|
|
hostname: docsSiteUrl,
|
|
transformItems(items) {
|
|
return items
|
|
.map((item) => {
|
|
const url = new URL(item.url, docsSiteUrl)
|
|
const path = normalizeDocsSeoPath(url.pathname)
|
|
const { priority, changefreq } = getDocsSitemapHints(path)
|
|
url.pathname = path === '/' ? '/' : path
|
|
const links = item.links?.map((link) => {
|
|
const alternateUrl = new URL(link.url, docsSiteUrl)
|
|
const alternatePath = normalizeDocsSeoPath(alternateUrl.pathname)
|
|
alternateUrl.pathname = alternatePath === '/' ? '/' : alternatePath
|
|
|
|
return {
|
|
...link,
|
|
url: alternateUrl.toString(),
|
|
}
|
|
})
|
|
|
|
return {
|
|
...item,
|
|
url: url.toString(),
|
|
links,
|
|
priority,
|
|
changefreq,
|
|
fullPrecisionPriority: true,
|
|
}
|
|
})
|
|
.filter(item => !isDocsSeoExcluded(normalizeDocsSeoPath(new URL(item.url, docsSiteUrl).pathname)))
|
|
},
|
|
},
|
|
vite: {
|
|
resolve: {
|
|
// Docs import the built `markstream-vue` package, which keeps
|
|
// `stream-markdown-parser` external. Point VitePress at the workspace
|
|
// source so clean CI checkouts don't need a prebuilt parser dist.
|
|
alias: [
|
|
{
|
|
find: /^stream-markdown-parser$/,
|
|
replacement: markdownParserSrc,
|
|
},
|
|
{
|
|
find: /^stream-markdown-parser\//,
|
|
replacement: `${markdownParserSrcDir}/`,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
locales: {
|
|
root: {
|
|
label: 'English',
|
|
lang: 'en-US',
|
|
title: 'markstream-vue',
|
|
description: docsDefaultDescription,
|
|
link: '/',
|
|
},
|
|
zh: {
|
|
label: '简体中文',
|
|
lang: 'zh-CN',
|
|
title: 'markstream-vue',
|
|
description: '适用于 Vue 3 的流式 Markdown 渲染器',
|
|
link: '/zh/',
|
|
},
|
|
},
|
|
themeConfig: {
|
|
logo: '/logo.svg',
|
|
search: {
|
|
provider: 'local',
|
|
},
|
|
nav: rootNav,
|
|
sidebar: {
|
|
'/guide/': englishGuideSidebar,
|
|
'/zh/guide/': chineseGuideSidebar,
|
|
'/zh/': chineseGuideSidebar,
|
|
'/': englishGuideSidebar,
|
|
},
|
|
locales: {
|
|
root: {
|
|
selectText: 'Languages',
|
|
label: 'English',
|
|
ariaLabel: 'Select language',
|
|
nav: rootNav,
|
|
},
|
|
zh: {
|
|
selectText: '选择语言',
|
|
label: '简体中文',
|
|
ariaLabel: '选择语言',
|
|
nav: zhNav,
|
|
},
|
|
},
|
|
},
|
|
transformHead(ctx) {
|
|
const normalizedPath = normalizeDocsSeoPath(ctx.page)
|
|
const canonicalUrl = `${docsSiteUrl}${normalizedPath}`
|
|
const frontmatter = ctx.pageData.frontmatter ?? {}
|
|
const isChinese = normalizedPath === '/zh' || normalizedPath.startsWith('/zh/')
|
|
const description = frontmatter.description || ctx.description || docsDefaultDescription
|
|
const title = createDocsSeoTitle(frontmatter.title || ctx.pageData.title || ctx.title)
|
|
const shouldIndex = !ctx.pageData.isNotFound && !frontmatter.noindex && !isDocsSeoExcluded(normalizedPath)
|
|
const alternates = getDocsAlternatePaths(normalizedPath)
|
|
const alternateHead = []
|
|
const alternateLocales = []
|
|
|
|
if (alternates.english) {
|
|
alternateHead.push(['link', { rel: 'alternate', hreflang: 'en-US', href: `${docsSiteUrl}${alternates.english}` }])
|
|
alternateLocales.push('en_US')
|
|
}
|
|
|
|
if (alternates.chinese) {
|
|
alternateHead.push(['link', { rel: 'alternate', hreflang: 'zh-CN', href: `${docsSiteUrl}${alternates.chinese}` }])
|
|
alternateLocales.push('zh_CN')
|
|
}
|
|
|
|
const defaultAlternate = alternates.english || alternates.chinese
|
|
if (defaultAlternate)
|
|
alternateHead.push(['link', { rel: 'alternate', hreflang: 'x-default', href: `${docsSiteUrl}${defaultAlternate}` }])
|
|
|
|
const structuredData = shouldIndex
|
|
? createDocsStructuredData(
|
|
normalizedPath,
|
|
frontmatter.title || ctx.pageData.title || ctx.title || 'markstream-vue',
|
|
description,
|
|
isChinese,
|
|
)
|
|
: []
|
|
|
|
return [
|
|
['link', { rel: 'canonical', href: canonicalUrl }],
|
|
...alternateHead,
|
|
['meta', { name: 'description', content: description }],
|
|
['meta', { name: 'robots', content: shouldIndex ? 'index,follow' : 'noindex,nofollow' }],
|
|
['meta', { property: 'og:locale', content: isChinese ? 'zh_CN' : 'en_US' }],
|
|
...alternateLocales
|
|
.filter(locale => locale !== (isChinese ? 'zh_CN' : 'en_US'))
|
|
.map(locale => ['meta', { property: 'og:locale:alternate', content: locale }]),
|
|
['meta', { property: 'og:title', content: title }],
|
|
['meta', { property: 'og:description', content: description }],
|
|
['meta', { property: 'og:url', content: canonicalUrl }],
|
|
['meta', { name: 'twitter:title', content: title }],
|
|
['meta', { name: 'twitter:description', content: description }],
|
|
...structuredData,
|
|
]
|
|
},
|
|
} as any)
|