portal/src/lib/marketingContent.ts

313 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'fs'
import path from 'path'
import { readMdxDirectory } from './mdx'
export interface HeroContent {
eyebrow?: string
title: string
subtitle?: string
primaryCtaLabel?: string
primaryCtaHref?: string
secondaryCtaLabel?: string
secondaryCtaHref?: string
highlights: string[]
bodyHtml: string
}
export interface HeroSolution {
slug: string
title: string
tagline?: string
description?: string
features: string[]
bodyHtml: string
primaryCtaLabel?: string
primaryCtaHref?: string
secondaryCtaLabel?: string
secondaryCtaHref?: string
tertiaryCtaLabel?: string
tertiaryCtaHref?: string
}
export interface HomepagePost {
slug: string
title: string
author?: string
date?: string
readingTime?: string
tags: string[]
excerpt: string
content: string
category?: {
key: string
label: string
}
}
export interface SidebarSection {
slug: string
title: string
layout?: string
tags: string[]
bodyHtml: string
ctaLabel?: string
ctaHref?: string
order?: number
}
export interface ContactItemContent {
slug: string
title: string
type?: string
description?: string
bodyHtml: string
qrValue?: string
qrImage?: string
icon?: string
ctaLabel?: string
ctaHref?: string
}
export interface ContactPanelContent {
title: string
subtitle?: string
bodyHtml?: string
items: ContactItemContent[]
}
const HERO_CONTENT: HeroContent = {
eyebrow: '云原生套件',
title: '构建一体化的云原生工具集',
subtitle:
'将资产管理、访问控制、可观测与自动化工作流整合到一个响应迅速的体验里,帮助团队高效落地治理策略。',
primaryCtaLabel: '立即体验',
primaryCtaHref: '/register',
secondaryCtaLabel: '产品文档',
secondaryCtaHref: '/docs',
highlights: [
'跨集群纳管与多云环境统一治理',
'以策略为核心的安全与合规编排',
'数据驱动的可观测与成本分析',
'场景化模板快速对接业务流程',
],
bodyHtml:
'<p>XControl 采用模块化架构设计,可在保持核心稳定的前提下按需引入观测、身份、编排等能力包。通过开放 API 与事件流,您可以轻松连接现有的 DevOps 工具链,让业务交付与平台治理协同运转。</p>',
}
const HERO_SOLUTIONS: HeroSolution[] = [
{
slug: 'xcloudflow',
title: 'XCloudFlow',
tagline: '多云 IaC',
description: '通过声明式模型统一编排多云基础设施,自动化落地资源策略与合规标准。',
features: ['跨云资源蓝图与参数化交付', 'GitOps 工作流驱动基础设施变更', '内置审批、审计保障合规'],
bodyHtml:
'<p>XCloudFlow 将 Terraform、Pulumi 等主流 IaC 模型统一到一个工作台,为多云环境提供自助式交付与集中治理。</p>',
primaryCtaLabel: '立刻体验',
primaryCtaHref: '/demo?product=xcloudflow',
secondaryCtaLabel: '下载链接',
secondaryCtaHref: '/download?product=xcloudflow',
tertiaryCtaLabel: '文档链接',
tertiaryCtaHref: '/docs/xcloudflow',
},
{
slug: 'xscopehub',
title: 'XScopeHub',
tagline: 'AI & 可观察性',
description: '利用 AI 驱动的分析工作台,统一日志、指标与追踪,快速定位异常并推荐修复路径。',
features: ['全栈可观察性数据联邦检索', '智能告警关联与根因分析', '预置 AI 助手生成运维建议'],
bodyHtml:
'<p>XScopeHub 通过语义化检索与时序分析,实现跨环境的可观察性汇聚与智能洞察。</p>',
primaryCtaLabel: '立刻体验',
primaryCtaHref: '/demo?product=xscopehub',
secondaryCtaLabel: '下载链接',
secondaryCtaHref: '/download?product=xscopehub',
tertiaryCtaLabel: '文档链接',
tertiaryCtaHref: '/docs/xscopehub',
},
{
slug: 'xstream',
title: 'XStream',
tagline: '网络加速器',
description: '按需构建全球传输网络,保障跨地域应用与数据同步的稳定低时延体验。',
features: ['动态最优路径与带宽调度', '内置零信任安全与访问控制', '对接主流 CDN 与边缘节点'],
bodyHtml:
'<p>XStream 通过软件定义的网络加速技术,为实时互动、音视频与数据分发提供稳定的全球链路。</p>',
primaryCtaLabel: '立刻体验',
primaryCtaHref: '/demo?product=xstream',
secondaryCtaLabel: '下载链接',
secondaryCtaHref: '/download?product=xstream',
tertiaryCtaLabel: '文档链接',
tertiaryCtaHref: '/docs/xstream',
},
]
const CONTACT_PANEL: ContactPanelContent = {
title: '保持联系',
subtitle: '扫码关注或加入社区,获取最新产品动态与支持。',
items: [
{
slug: 'wechat-official',
title: '微信公众号',
type: 'qr',
description: '了解商业产品和专业支持服务',
bodyHtml: '关注 XControl 官方公众号,解锁上云实践案例与专家分享。',
qrValue: 'https://xcontrol.cloud/contact/wechat-official',
qrImage: 'https://dl.svc.plus/images/contact/wechat-official.jpg',
},
{
slug: 'wechat-group',
title: '加入微信群',
type: 'qr',
description: '与产品团队和同行实时交流',
bodyHtml: '添加 XControl 社区小助手,获取最新活动信息并加入兴趣小组。',
qrValue: 'https://xcontrol.cloud/contact/wechat-community',
qrImage: 'https://dl.svc.plus/images/contact/wechat-group.jpg',
},
{
slug: 'support',
title: '获取支持',
type: 'info',
description: '了解使用反馈与社区支持',
bodyHtml:
'<p>欢迎提交您的使用反馈或功能建议,我们会尽快回复。<br/>如需寻求技术帮助,可联系:<strong>manbuzhe2008@gmail.com</strong></p>',
icon: 'life-buoy',
ctaLabel: '联系我们',
ctaHref: 'https://github.com/svc-design/XControl/issues',
},
{
slug: 'github-star',
title: '欢迎支持',
type: 'info',
description: '欢迎支持关注 Star',
bodyHtml: '点击链接访问 CloudNativeSuite GitHub欢迎支持关注 Star获取更多项目动态。',
icon: 'star',
ctaLabel: '访问 GitHub',
ctaHref: 'https://github.com/cloud-neutral-toolkit',
},
],
}
const BLOG_CONTENT_ROOT = path.join(process.cwd(), 'src', 'content', 'blogs', 'content')
const CATEGORY_MAP: { key: string; label: string; match: (segments: string[]) => boolean }[] = [
{ key: 'infra-cloud', label: 'Infra & Cloud', match: (segments) => segments[0] === '04-infra-platform' },
{ key: 'observability', label: 'Observability', match: (segments) => segments[0] === '03-observability' },
{ key: 'identity', label: 'ID & Security', match: (segments) => segments[0] === '01-id-security' },
{ key: 'iac-devops', label: 'IaC & DevOps', match: (segments) => segments[0] === '02-iac-devops' },
{ key: 'data-ai', label: 'Data & AI', match: (segments) => segments[0] === '05-data-ai' },
{ key: 'workshops', label: 'Workshops', match: (segments) => segments[0] === '06-workshops' },
{
key: 'insight',
label: '资讯',
match: (segments) => segments[0] === '00-global' && (!segments[1] || segments[1] === 'news' || segments[1] === 'workshops'),
},
{
key: 'essays',
label: '随笔&观察',
match: (segments) => segments[0] === '00-global' && segments[1] === 'essays',
},
]
export function resolveBlogContentRoot(): string {
return BLOG_CONTENT_ROOT
}
function resolveCategory(slug: string): { key: string; label: string } | undefined {
const segments = slug.split('/')
const matched = CATEGORY_MAP.find((category) => category.match(segments))
return matched ? { key: matched.key, label: matched.label } : undefined
}
function extractExcerpt(markdown: string): string {
const cleaned = markdown
.replace(/^\s*import\s+.*$/gm, '')
.replace(/^\s*export\s+const\s+.*$/gm, '')
.trim()
const blocks = cleaned.split(/\r?\n\s*\r?\n/)
for (const block of blocks) {
const trimmed = block.trim()
if (!trimmed) continue
const withoutFormatting = trimmed
.replace(/^#+\s*/g, '')
.replace(/[`*_>\[\]]/g, '')
.replace(/\[(.*?)\]\((.*?)\)/g, '$1')
if (withoutFormatting.trim()) {
return withoutFormatting.trim()
}
}
return ''
}
export async function getHomepageHero(): Promise<HeroContent> {
return HERO_CONTENT
}
export async function getHeroSolutions(): Promise<HeroSolution[]> {
return HERO_SOLUTIONS
}
export async function getHomepagePosts(): Promise<HomepagePost[]> {
let posts: HomepagePost[] = []
try {
const contentRoot = resolveBlogContentRoot()
const files = await readMdxDirectory('', { baseDir: contentRoot, recursive: true })
posts = files.map((file) => {
const title = typeof file.metadata.title === 'string' ? file.metadata.title : file.slug
const author = typeof file.metadata.author === 'string' ? file.metadata.author : undefined
const date = typeof file.metadata.date === 'string' ? file.metadata.date : undefined
const readingTime =
typeof file.metadata.readingTime === 'string' ? file.metadata.readingTime : undefined
const tags = Array.isArray(file.metadata.tags)
? file.metadata.tags.filter((tag): tag is string => typeof tag === 'string')
: []
const excerptMetadata = typeof file.metadata.excerpt === 'string' ? file.metadata.excerpt : undefined
const excerpt = excerptMetadata ?? extractExcerpt(file.content)
const category = resolveCategory(file.slug)
return {
slug: file.slug,
title,
author,
date,
readingTime,
tags,
excerpt,
content: file.content,
category,
}
})
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error
}
}
const withParsedDates = posts.map((post) => ({
...post,
dateValue: post.date ? new Date(post.date) : undefined,
}))
withParsedDates.sort((a, b) => {
if (a.dateValue && b.dateValue) {
return b.dateValue.getTime() - a.dateValue.getTime()
}
if (a.dateValue) return -1
if (b.dateValue) return 1
return a.title.localeCompare(b.title)
})
return withParsedDates.map(({ dateValue: _dateValue, ...post }) => post)
}
export async function getContactPanelContent(): Promise<ContactPanelContent | undefined> {
if (!CONTACT_PANEL.items.length) {
return undefined
}
return CONTACT_PANEL
}