forked from boos/BoosAPI
feat(desktop): embed BoosAPI Desktop client as a subproject
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.
This commit is contained in:
@@ -0,0 +1,660 @@
|
||||
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)
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none">
|
||||
<rect width="120" height="120" rx="24" fill="#0ea5e9"/>
|
||||
<text x="50%" y="58%" dominant-baseline="middle" text-anchor="middle" font-size="48" font-family="Arial" fill="#fff">MD</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
@@ -0,0 +1,35 @@
|
||||
<svg width="1200" height="630" viewBox="0 0 1200 630" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1200" height="630" fill="#081122"/>
|
||||
<rect x="38" y="38" width="1124" height="554" rx="32" fill="url(#panel)"/>
|
||||
<rect x="86" y="96" width="492" height="340" rx="28" fill="#0F172A" stroke="#334155" stroke-width="2"/>
|
||||
<rect x="118" y="132" width="184" height="18" rx="9" fill="#22C55E" fill-opacity="0.9"/>
|
||||
<rect x="118" y="172" width="402" height="20" rx="10" fill="#E2E8F0" fill-opacity="0.92"/>
|
||||
<rect x="118" y="208" width="348" height="20" rx="10" fill="#94A3B8" fill-opacity="0.8"/>
|
||||
<rect x="118" y="258" width="116" height="116" rx="24" fill="#111827"/>
|
||||
<rect x="254" y="258" width="116" height="116" rx="24" fill="#0B1220"/>
|
||||
<rect x="390" y="258" width="156" height="116" rx="24" fill="#111827"/>
|
||||
<path d="M174 319H224" stroke="#38BDF8" stroke-width="18" stroke-linecap="round"/>
|
||||
<path d="M312 289L312 349" stroke="#22C55E" stroke-width="18" stroke-linecap="round"/>
|
||||
<path d="M446 290H486C506.987 290 524 307.013 524 328C524 348.987 506.987 366 486 366H446V290Z" fill="#F59E0B"/>
|
||||
<rect x="608" y="118" width="502" height="394" rx="32" fill="#020617" stroke="#1E293B" stroke-width="2"/>
|
||||
<rect x="652" y="152" width="92" height="34" rx="17" fill="#22C55E" fill-opacity="0.14" stroke="#22C55E" stroke-opacity="0.7"/>
|
||||
<text x="698" y="169" fill="#DCFCE7" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700" text-anchor="middle" dominant-baseline="middle">DOCS</text>
|
||||
<text x="652" y="232" fill="#F8FAFC" font-family="Arial, Helvetica, sans-serif" font-size="46" font-weight="700">markstream-vue</text>
|
||||
<text x="652" y="288" fill="#93C5FD" font-family="Arial, Helvetica, sans-serif" font-size="28" font-weight="700">Guides and API</text>
|
||||
<text x="652" y="342" fill="#CBD5E1" font-family="Arial, Helvetica, sans-serif" font-size="20">Streaming Markdown for Vue</text>
|
||||
<text x="652" y="378" fill="#CBD5E1" font-family="Arial, Helvetica, sans-serif" font-size="20">VitePress, overrides, custom tags</text>
|
||||
<rect x="652" y="416" width="100" height="36" rx="18" fill="#0F1D33" stroke="#334155"/>
|
||||
<text x="702" y="434" fill="#E2E8F0" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700" text-anchor="middle" dominant-baseline="middle">Guides</text>
|
||||
<rect x="764" y="416" width="72" height="36" rx="18" fill="#0F1D33" stroke="#334155"/>
|
||||
<text x="800" y="434" fill="#E2E8F0" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700" text-anchor="middle" dominant-baseline="middle">API</text>
|
||||
<rect x="848" y="416" width="118" height="36" rx="18" fill="#0F1D33" stroke="#334155"/>
|
||||
<text x="907" y="434" fill="#E2E8F0" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700" text-anchor="middle" dominant-baseline="middle">Migration</text>
|
||||
<rect x="652" y="462" width="164" height="42" rx="21" fill="#22C55E" fill-opacity="0.18" stroke="#22C55E"/>
|
||||
<text x="734" y="483" fill="#DCFCE7" font-family="Arial, Helvetica, sans-serif" font-size="22" font-weight="700" text-anchor="middle" dominant-baseline="middle">Read docs</text>
|
||||
<defs>
|
||||
<linearGradient id="panel" x1="78" y1="48" x2="1082" y2="582" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#111827"/>
|
||||
<stop offset="1" stop-color="#0F766E"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://markstream-vue-docs.simonhe.me/sitemap.xml
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 360' width='600' height='360'>
|
||||
<rect width='100%' height='100%' fill='#111827'/>
|
||||
<rect x='56' y='40' width='488' height='280' rx='8' fill='#0f172a'/>
|
||||
<text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-size='22' fill='#9CA3AF' font-family='Courier New, monospace'>Monaco / CodeBlock demo</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 360' width='600' height='360'>
|
||||
<rect width='100%' height='100%' fill='#0ea5e9'/>
|
||||
<rect x='24' y='24' width='552' height='312' rx='12' fill='#fff'/>
|
||||
<text x='50%' y='40%' dominant-baseline='middle' text-anchor='middle' font-size='18' fill='#0ea5e9' font-family='Arial'>Mermaid Diagram Example</text>
|
||||
<text x='50%' y='56%' dominant-baseline='middle' text-anchor='middle' font-size='14' fill='#0ea5e9' font-family='Arial'>Progressive diagram rendering</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 528 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 360' width='600' height='360'>
|
||||
<rect width='100%' height='100%' fill='#0ea5e9'/>
|
||||
<rect x='24' y='24' width='552' height='312' rx='12' fill='#fff'/>
|
||||
<text x='50%' y='40%' dominant-baseline='middle' text-anchor='middle' font-size='28' fill='#0ea5e9' font-family='Arial'>Playground Demo</text>
|
||||
<text x='50%' y='54%' dominant-baseline='middle' text-anchor='middle' font-size='16' fill='#0ea5e9' font-family='Arial'>Live editor + preview</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 512 B |
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
repo: string
|
||||
}>()
|
||||
|
||||
const stars = ref<number | null>(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch(`https://api.github.com/repos/${props.repo}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
stars.value = data.stargazers_count
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Silently fail if API request fails
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${(num / 1000).toFixed(1)}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="`https://github.com/${repo}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="github-star-badge"
|
||||
aria-label="Star on GitHub"
|
||||
>
|
||||
<svg
|
||||
class="star-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"
|
||||
/>
|
||||
</svg>
|
||||
<span v-if="isLoading" class="stars-count">...</span>
|
||||
<span v-else-if="stars !== null" class="stars-count">{{ formatNumber(stars) }}</span>
|
||||
<span v-else class="stars-count">Star</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.github-star-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.4375rem 0.875rem;
|
||||
background: linear-gradient(135deg, #f6f8fa 0%, #f1f3f5 100%);
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 24px;
|
||||
color: #24292f;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f6f8fa 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.github-star-badge:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.github-star-badge {
|
||||
background: linear-gradient(135deg, #161b22 0%, #0d1117 100%);
|
||||
border-color: #30363d;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background: linear-gradient(135deg, #21262d 0%, #161b22 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.github-star-badge:hover .star-icon {
|
||||
transform: scale(1.15) rotate(8deg);
|
||||
}
|
||||
|
||||
.stars-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.025em;
|
||||
min-width: 2ch;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stars-count:empty::before {
|
||||
content: '...';
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stars = ref<number | null>(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/Simon-He95/markstream-vue')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
stars.value = data.stargazers_count
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Silently fail if API request fails
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${(num / 1000).toFixed(1)}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultTheme.Layout>
|
||||
<template #nav-bar-content-after>
|
||||
<a
|
||||
href="https://github.com/Simon-He95/markstream-vue"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="github-star-badge-nav"
|
||||
aria-label="Star on GitHub"
|
||||
>
|
||||
<svg
|
||||
class="star-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"
|
||||
/>
|
||||
</svg>
|
||||
<span v-if="isLoading" class="stars-count">...</span>
|
||||
<span v-else-if="stars !== null" class="stars-count">{{ formatNumber(stars) }}</span>
|
||||
<span v-else class="stars-count">Star</span>
|
||||
</a>
|
||||
</template>
|
||||
</DefaultTheme.Layout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.github-star-badge-nav {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
margin-left: 0.75rem;
|
||||
background: linear-gradient(135deg, #f6f8fa 0%, #f1f3f5 100%);
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 20px;
|
||||
color: #24292f;
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.github-star-badge-nav:hover {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f6f8fa 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.github-star-badge-nav:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .github-star-badge-nav {
|
||||
background: linear-gradient(135deg, #161b22 0%, #0d1117 100%);
|
||||
border-color: #30363d;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
|
||||
.dark .github-star-badge-nav:hover {
|
||||
background: linear-gradient(135deg, #21262d 0%, #161b22 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.github-star-badge-nav .star-icon {
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.github-star-badge-nav:hover .star-icon {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.github-star-badge-nav .stars-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
/* Animation for loading state */
|
||||
.github-star-badge-nav .stars-count {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove animation when loaded */
|
||||
.github-star-badge-nav .stars-count:not(:empty) {
|
||||
animation: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description: string
|
||||
note?: string
|
||||
alipayLabel?: string
|
||||
wechatLabel?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
alipayLabel: 'Alipay',
|
||||
wechatLabel: 'WeChat Pay',
|
||||
})
|
||||
|
||||
const alipaySrc = withBase('/sponsor/zhifubao.jpg')
|
||||
const wechatSrc = withBase('/sponsor/weixin.jpg')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="support-qrs">
|
||||
<div class="support-qrs__copy">
|
||||
<h2 class="support-qrs__title">
|
||||
{{ props.title }}
|
||||
</h2>
|
||||
<p class="support-qrs__description">
|
||||
{{ props.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="support-qrs__grid">
|
||||
<figure class="support-qrs__card">
|
||||
<div class="support-qrs__image-frame">
|
||||
<img
|
||||
class="support-qrs__image"
|
||||
:src="alipaySrc"
|
||||
:alt="`${props.alipayLabel} QR code`"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<figcaption class="support-qrs__label">
|
||||
{{ props.alipayLabel }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="support-qrs__card">
|
||||
<div class="support-qrs__image-frame">
|
||||
<img
|
||||
class="support-qrs__image"
|
||||
:src="wechatSrc"
|
||||
:alt="`${props.wechatLabel} QR code`"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<figcaption class="support-qrs__label">
|
||||
{{ props.wechatLabel }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<p v-if="props.note" class="support-qrs__note">
|
||||
{{ props.note }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.support-qrs {
|
||||
margin: 1.5rem 0 2.25rem;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 20px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(61, 120, 255, 0.12), transparent 34%),
|
||||
linear-gradient(180deg, rgba(248, 250, 252, 0.92), rgba(255, 255, 255, 0.98));
|
||||
}
|
||||
|
||||
.support-qrs__copy {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.support-qrs__title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.support-qrs__description {
|
||||
max-width: 44rem;
|
||||
margin: 0.6rem auto 0;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.support-qrs__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.support-qrs__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.support-qrs__image-frame {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 320px;
|
||||
padding: 0.875rem;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, rgba(248, 250, 252, 0.94), rgba(241, 245, 249, 0.9));
|
||||
}
|
||||
|
||||
.support-qrs__image {
|
||||
width: auto;
|
||||
max-width: min(100%, 240px);
|
||||
max-height: 320px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.support-qrs__label {
|
||||
margin-top: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.support-qrs__note {
|
||||
margin: 1rem auto 0;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.dark .support-qrs {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(122, 162, 255, 0.16), transparent 36%),
|
||||
linear-gradient(180deg, rgba(16, 24, 40, 0.92), rgba(10, 15, 26, 0.98));
|
||||
}
|
||||
|
||||
.dark .support-qrs__card {
|
||||
background: rgba(15, 23, 42, 0.78);
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.dark .support-qrs__image-frame {
|
||||
background: linear-gradient(180deg, rgba(30, 41, 59, 0.82), rgba(15, 23, 42, 0.92));
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.support-qrs {
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.support-qrs__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.support-qrs__card {
|
||||
padding: 0.875rem;
|
||||
}
|
||||
|
||||
.support-qrs__image-frame {
|
||||
min-height: 260px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.support-qrs__image {
|
||||
max-height: 260px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useData } from 'vitepress'
|
||||
|
||||
/**
|
||||
* VitePress theme composable for dark mode
|
||||
* Uses VitePress's built-in isDark from useData()
|
||||
*
|
||||
* @example
|
||||
* ```vue
|
||||
* <script setup>
|
||||
* const isDark = useDark()
|
||||
* </script>
|
||||
*
|
||||
* <template>
|
||||
* <MarkdownRender :is-dark="isDark" />
|
||||
* </template>
|
||||
* ```
|
||||
*/
|
||||
export function useDark() {
|
||||
const { isDark } = useData()
|
||||
return isDark
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { EnhanceAppContext } from 'vitepress'
|
||||
import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'
|
||||
import MarkdownRender from 'markstream-vue'
|
||||
import Theme from 'vitepress/theme'
|
||||
import GitHubStarBadge from './GitHubStarBadge.vue'
|
||||
import Layout from './Layout.vue'
|
||||
import SupportQRCodes from './SupportQRCodes.vue'
|
||||
import '@shikijs/vitepress-twoslash/style.css'
|
||||
import 'markstream-vue/index.css'
|
||||
import './style.css'
|
||||
|
||||
export default {
|
||||
extends: Theme,
|
||||
Layout,
|
||||
enhanceApp({ app }: EnhanceAppContext) {
|
||||
app.use(TwoslashFloatingVue)
|
||||
app.component('GitHubStarBadge', GitHubStarBadge)
|
||||
app.component('MarkdownRender', MarkdownRender)
|
||||
app.component('SupportQRCodes', SupportQRCodes)
|
||||
},
|
||||
}
|
||||
|
||||
// Export useDark for use in VitePress markdown files and components
|
||||
export { useDark } from './composables/useDark'
|
||||
@@ -0,0 +1 @@
|
||||
/* Custom styles for markstream-vue theme */
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { DefineComponent } from 'vue'
|
||||
import type { BaseNode } from 'stream-markdown-parser'
|
||||
import type {
|
||||
CodeBlockDiffHunkActionContext,
|
||||
CodeBlockMonacoOptions,
|
||||
CodeBlockMonacoTheme,
|
||||
CodeBlockNodeProps,
|
||||
CustomComponents,
|
||||
D2BlockNodeProps,
|
||||
ImageNodeProps,
|
||||
MermaidBlockEvent,
|
||||
MermaidBlockNodeProps,
|
||||
PreCodeNodeProps,
|
||||
} from '../../../src/exports'
|
||||
import type { NodeRendererProps } from '../../../src/types/node-renderer-props'
|
||||
|
||||
export type {
|
||||
BaseNode,
|
||||
CodeBlockDiffHunkActionContext,
|
||||
CodeBlockMonacoOptions,
|
||||
CodeBlockMonacoTheme,
|
||||
CodeBlockNodeProps,
|
||||
CustomComponents,
|
||||
D2BlockNodeProps,
|
||||
ImageNodeProps,
|
||||
MermaidBlockEvent,
|
||||
MermaidBlockNodeProps,
|
||||
NodeRendererProps,
|
||||
PreCodeNodeProps,
|
||||
}
|
||||
|
||||
export {
|
||||
CodeBlockNode,
|
||||
D2BlockNode,
|
||||
ImageNode,
|
||||
MarkdownCodeBlockNode,
|
||||
MermaidBlockNode,
|
||||
PreCodeNode,
|
||||
createKaTeXWorkerFromCDN,
|
||||
createMermaidWorkerFromCDN,
|
||||
enableD2,
|
||||
enableKatex,
|
||||
enableMermaid,
|
||||
getMarkdown,
|
||||
getUseMonaco,
|
||||
parseMarkdownToStructure,
|
||||
preloadExtendedLanguageIcons,
|
||||
removeCustomComponents,
|
||||
setCustomComponents,
|
||||
setD2Loader,
|
||||
setDefaultI18nMap,
|
||||
setDefaultMathOptions,
|
||||
setKaTeXWorker,
|
||||
setKatexLoader,
|
||||
setMermaidLoader,
|
||||
setMermaidWorker,
|
||||
} from '../../../src/exports'
|
||||
|
||||
export declare const MarkdownRender: DefineComponent<NodeRendererProps>
|
||||
|
||||
export default MarkdownRender
|
||||
Reference in New Issue
Block a user