mirror of
https://github.com/longbridge/developers.git
synced 2026-09-19 03:34:09 +08:00
2fef8dc564
🤖 Auto-generated by Endless task [#38](https://endless.longbridge-inc.com/projects/developers/tasks/38). Initiated by: huacnlee@longbridge-inc.com Worker agent: Kayak (ri · lboneapi · DeepSeek-V3.2) ## Summary - Longbridge Developers lacked a formal documentation content strategy and writing standard, making it difficult to maintain consistency across multilingual docs - Introduces a comprehensive style guide (inspired by Cloudflare's Product docs content strategy) covering content types, API reference standards, information architecture, writing guidelines, and multilingual rules - Adds a product spec (`specs/2026-07-14-content-strategy.md`) capturing the full content strategy design rationale - Removes obsolete `content/overview.md` pages across all three locales (en, zh-CN, zh-HK) and cleans up the sensors utility - Verify by navigating to the `/style-guide/` section in the local dev server and confirming all five guide pages render correctly with sidebar navigation - Cross-check that existing pages (getting-started, CLI, quote APIs) still render without broken links after the nav config updates ## Changes | File | Change | |---|---| | `docs/en/docs/style-guide/index.md` | New style guide landing page (EN) | | `docs/en/docs/style-guide/content-types.md` | New content-types guide defining tutorials, how-tos, reference, and concept pages | | `docs/en/docs/style-guide/api-reference.md` | New API reference writing standard | | `docs/en/docs/style-guide/information-architecture.md` | New IA guidelines for doc structure and navigation | | `docs/en/docs/style-guide/writing-guidelines.md` | New writing style and tone guidelines | | `docs/en/docs/style-guide/multilingual.md` | New multilingual documentation rules | | `docs/en/docs/style-guide/_category_.json` | Sidebar category metadata for style guide section | | `specs/2026-07-14-content-strategy.md` | Full product spec for the content strategy initiative | | `docs/.vitepress/config.mts` | Added style guide section to VitePress site config | | `docs/.vitepress/locales/en/index.ts` | Updated EN locale nav/sidebar to include style guide | | `docs/.vitepress/locales/zh-CN/index.ts` | Updated zh-CN locale nav/sidebar | | `docs/.vitepress/locales/zh-HK/index.ts` | Updated zh-HK locale nav/sidebar | | `docs/.vitepress/theme/utils/sensors.ts` | Removed unused analytics sensors utility | | `docs/.vitepress/theme/index.ts` | Cleaned up theme entry, removed sensors import | | `docs/.vitepress/theme/components/NewHomePage/index.vue` | Minor homepage component cleanup | | `docs/.vitepress/theme/components/Pricing.vue` | Minor pricing component cleanup | | `docs/.vitepress/theme/components/Skill.vue` | Minor skill component cleanup | | `docs/en/docs/content/overview.md` | Removed obsolete content overview page (EN) | | `docs/zh-CN/docs/content/overview.md` | Removed obsolete content overview page (zh-CN) | | `docs/zh-HK/docs/content/overview.md` | Removed obsolete content overview page (zh-HK) | | `docs/en/docs/quote/stocks/history-candlestick.md` | Updated candlestick doc formatting/content | | `docs/zh-CN/docs/quote/stocks/history-candlestick.md` | Updated candlestick doc formatting/content (zh-CN) | | `docs/zh-HK/docs/quote/stocks/history-candlestick.md` | Updated candlestick doc formatting/content (zh-HK) | | `docs/en/docs/quote/stocks/quote.md` | Minor fix to quote doc | | `docs/zh-CN/docs/quote/stocks/quote.md` | Minor fix to quote doc (zh-CN) | | `docs/zh-HK/docs/quote/stocks/quote.md` | Minor fix to quote doc (zh-HK) | | `docs/en/docs/getting-started.md` | Minor fix to getting started doc --------- Co-authored-by: Huacnlee Li Huashun <huacnlee@longbridge-inc.com> Co-authored-by: Jason Lee <huacnlee@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
257 lines
10 KiB
TypeScript
257 lines
10 KiB
TypeScript
import type MarkdownIt from 'markdown-it'
|
|
|
|
function escapeHtml(str: string): string {
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
}
|
|
|
|
// Map CLI subcommand names to their doc page paths (locale-independent)
|
|
const CLI_COMMAND_MAP: Record<string, string> = {
|
|
// Market Data
|
|
quote: '/docs/cli/market-data/quote',
|
|
depth: '/docs/cli/market-data/depth',
|
|
kline: '/docs/cli/market-data/kline',
|
|
trades: '/docs/cli/market-data/trades',
|
|
brokers: '/docs/cli/market-data/brokers',
|
|
intraday: '/docs/cli/market-data/intraday',
|
|
static: '/docs/cli/market-data/static',
|
|
'market-temp': '/docs/cli/market-data/market-temp',
|
|
'calc-index': '/docs/cli/market-data/calc-index',
|
|
capital: '/docs/cli/market-data/capital',
|
|
trading: '/docs/cli/market-data/trading',
|
|
participants: '/docs/cli/market-data/participants',
|
|
'security-list': '/docs/cli/market-data/security-list',
|
|
subscriptions: '/docs/cli/market-data/subscriptions',
|
|
'ah-premium': '/docs/cli/market-data/ah-premium',
|
|
anomaly: '/docs/cli/market-data/anomaly',
|
|
'broker-holding': '/docs/cli/market-data/broker-holding',
|
|
constituent: '/docs/cli/market-data/constituent',
|
|
'market-status': '/docs/cli/market-data/market-status',
|
|
'trade-stats': '/docs/cli/market-data/trade-stats',
|
|
// Derivatives
|
|
option: '/docs/cli/derivatives/option',
|
|
warrant: '/docs/cli/derivatives/warrant',
|
|
'short-positions': '/docs/cli/derivatives/short-positions',
|
|
// Fundamentals
|
|
company: '/docs/cli/fundamentals/company',
|
|
consensus: '/docs/cli/fundamentals/consensus',
|
|
'corp-action': '/docs/cli/fundamentals/corp-action',
|
|
dividend: '/docs/cli/fundamentals/dividend',
|
|
executive: '/docs/cli/fundamentals/executive',
|
|
'finance-calendar': '/docs/cli/fundamentals/finance-calendar',
|
|
'financial-report': '/docs/cli/fundamentals/financial-report',
|
|
'forecast-eps': '/docs/cli/fundamentals/forecast-eps',
|
|
'industry-valuation': '/docs/cli/fundamentals/industry-valuation',
|
|
'institution-rating': '/docs/cli/fundamentals/institution-rating',
|
|
'invest-relation': '/docs/cli/fundamentals/invest-relation',
|
|
operating: '/docs/cli/fundamentals/operating',
|
|
valuation: '/docs/cli/fundamentals/valuation',
|
|
// Research
|
|
shareholder: '/docs/cli/research/shareholder',
|
|
// Content
|
|
news: '/docs/cli/content/news',
|
|
topic: '/docs/cli/content/topic',
|
|
topics: '/docs/cli/content/topic',
|
|
filing: '/docs/cli/content/filing',
|
|
// Watchlist
|
|
watchlist: '/docs/cli/watchlist/watchlist',
|
|
sharelist: '/docs/cli/watchlist/sharelist',
|
|
// Account
|
|
assets: '/docs/cli/account/assets',
|
|
positions: '/docs/cli/account/positions',
|
|
'fund-positions': '/docs/cli/account/fund-positions',
|
|
'fund-holder': '/docs/cli/account/fund-holder',
|
|
'cash-flow': '/docs/cli/account/cash-flow',
|
|
portfolio: '/docs/cli/account/portfolio',
|
|
alert: '/docs/cli/account/alert',
|
|
dca: '/docs/cli/account/dca',
|
|
'profit-analysis': '/docs/cli/account/profit-analysis',
|
|
// Orders
|
|
order: '/docs/cli/orders/order',
|
|
orders: '/docs/cli/orders/order',
|
|
buy: '/docs/cli/orders/order',
|
|
sell: '/docs/cli/orders/order',
|
|
cancel: '/docs/cli/orders/order',
|
|
replace: '/docs/cli/orders/order',
|
|
executions: '/docs/cli/orders/order',
|
|
'max-qty': '/docs/cli/orders/max-qty',
|
|
'margin-ratio': '/docs/cli/orders/margin-ratio',
|
|
'exchange-rate': '/docs/cli/orders/exchange-rate',
|
|
}
|
|
|
|
function getLocalePrefix(localeIndex: string): string {
|
|
switch (localeIndex) {
|
|
case 'zh-CN': return '/zh-CN'
|
|
case 'zh-HK': return '/zh-HK'
|
|
default: return ''
|
|
}
|
|
}
|
|
|
|
function getInstallTitle(localeIndex: string): string {
|
|
switch (localeIndex) {
|
|
case 'zh-CN': return '安装 CLI'
|
|
case 'zh-HK': return '安裝 CLI'
|
|
default: return 'Install CLI'
|
|
}
|
|
}
|
|
|
|
function getUsageTitle(localeIndex: string): string {
|
|
switch (localeIndex) {
|
|
case 'zh-CN': return 'CLI 使用文档'
|
|
case 'zh-HK': return 'CLI 使用文件'
|
|
default: return 'CLI Usage Docs'
|
|
}
|
|
}
|
|
|
|
function getInstallUrl(localeIndex: string): string {
|
|
return `${getLocalePrefix(localeIndex)}/docs/cli/install`
|
|
}
|
|
|
|
function extractFirstCommand(content: string): string | null {
|
|
for (const line of content.split('\n')) {
|
|
const trimmed = line.trim()
|
|
if (!trimmed || trimmed.startsWith('#')) continue
|
|
const parts = trimmed.split(/\s+/)
|
|
if (parts[0] === 'longbridge' && parts[1]) return parts[1]
|
|
}
|
|
return null
|
|
}
|
|
|
|
function getUsageUrl(cmd: string): string | null {
|
|
return CLI_COMMAND_MAP[cmd] ?? null
|
|
}
|
|
|
|
// Colors matching github-light / github-dark shiki themes (auto-switch via CSS vars)
|
|
const C = {
|
|
binary: 'style="--shiki-light:#6f42c1;--shiki-dark:#b392f0"', // purple
|
|
subcmd: 'style="--shiki-light:#005cc5;--shiki-dark:#79b8ff"', // blue
|
|
args: 'style="--shiki-light:#24292e;--shiki-dark:#e1e4e8"', // default text
|
|
} as const
|
|
|
|
// Colors for inline comments (the # ... part at end of a command line)
|
|
const C_comment = 'style="--shiki-light:#6a737d;--shiki-dark:#6a737d"' // gray
|
|
|
|
function renderLine(line: string): string {
|
|
const trimmed = line.trim()
|
|
if (!trimmed) return ''
|
|
|
|
// Pure comment lines (start with #)
|
|
if (trimmed.startsWith('#')) {
|
|
return `<span class="line"><span ${C_comment}>${escapeHtml(trimmed)}</span></span>`
|
|
}
|
|
|
|
// Split command from inline comment (# ...)
|
|
const commentIdx = trimmed.indexOf(' #')
|
|
const command = commentIdx >= 0 ? trimmed.slice(0, commentIdx).trimEnd() : trimmed
|
|
const comment = commentIdx >= 0 ? trimmed.slice(commentIdx) : ''
|
|
|
|
const parts = command.split(/\s+/)
|
|
if (parts[0] !== 'longbridge') {
|
|
return `<span class="line"><span ${C.args}>${escapeHtml(command)}</span>${comment ? `<span ${C_comment}>${escapeHtml(comment)}</span>` : ''}</span>`
|
|
}
|
|
|
|
let html = `<span class="line"><span ${C.binary}>${escapeHtml(parts[0])}</span>`
|
|
if (parts.length > 1) {
|
|
html += ` <span ${C.subcmd}>${escapeHtml(parts[1])}</span>`
|
|
if (parts.length > 2) {
|
|
html += ` <span ${C.args}>${escapeHtml(parts.slice(2).join(' '))}</span>`
|
|
}
|
|
}
|
|
if (comment) {
|
|
html += `<span ${C_comment}>${escapeHtml(comment)}</span>`
|
|
}
|
|
return html + '</span>'
|
|
}
|
|
|
|
// Inline SVG icons for the CLI header
|
|
const ICON_TERMINAL = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`
|
|
const ICON_BOOK = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>`
|
|
const ICON_DOWNLOAD = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`
|
|
|
|
function generateCliBlock(content: string, installTitle: string, installUrl: string, usageTitle: string, usageUrl: string | null): string {
|
|
const lines = content
|
|
.split('\n')
|
|
.map(renderLine)
|
|
.filter(Boolean)
|
|
.join('\n')
|
|
|
|
const usageBtn = usageUrl
|
|
? `<a href="${usageUrl}" class="vp-cli-action" title="${escapeHtml(usageTitle)}" aria-label="${escapeHtml(usageTitle)}">${ICON_BOOK}</a>`
|
|
: ''
|
|
const installBtn = `<a href="${installUrl}" class="vp-cli-action" title="${escapeHtml(installTitle)}" aria-label="${escapeHtml(installTitle)}">${ICON_DOWNLOAD}</a>`
|
|
|
|
return (
|
|
`<div class="vp-cli-command">` +
|
|
`<div class="vp-cli-header">` +
|
|
`<span class="vp-cli-badge">${ICON_TERMINAL}<span>CLI</span></span>` +
|
|
`<div class="vp-cli-actions">${usageBtn}${installBtn}</div>` +
|
|
`</div>` +
|
|
`<div class="language-bash vp-adaptive-theme">` +
|
|
`<pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0">` +
|
|
`<code>${lines}</code></pre></div>` +
|
|
`</div>`
|
|
)
|
|
}
|
|
|
|
// Matches a complete <CliCommand>...</CliCommand> block. A bare `<CliCommand>`
|
|
// mention (e.g. inside inline code in the style guide) must not be treated as one.
|
|
const CLI_COMMAND_RE = /<CliCommand>([\s\S]*?)<\/CliCommand>/g
|
|
|
|
function hasCliCommand(src: string): boolean {
|
|
CLI_COMMAND_RE.lastIndex = 0
|
|
return CLI_COMMAND_RE.test(src)
|
|
}
|
|
|
|
function replaceCliCommand(src: string, installTitle: string, installUrl: string, usageTitle: string, localeIndex: string): string {
|
|
return src.replace(
|
|
CLI_COMMAND_RE,
|
|
(_, content: string) => {
|
|
const trimmed = content.trim()
|
|
const cmd = extractFirstCommand(trimmed)
|
|
const usageUrl = cmd ? getUsageUrl(cmd) : null
|
|
return generateCliBlock(trimmed, installTitle, installUrl, usageTitle, usageUrl)
|
|
},
|
|
)
|
|
}
|
|
|
|
export function CliCommandPlugin(md: MarkdownIt) {
|
|
md.core.ruler.push('cli_command', (state) => {
|
|
const localeIndex: string = state.env?.localeIndex ?? 'root'
|
|
const installTitle = getInstallTitle(localeIndex)
|
|
const installUrl = getInstallUrl(localeIndex)
|
|
const usageTitle = getUsageTitle(localeIndex)
|
|
|
|
let i = 0
|
|
while (i < state.tokens.length) {
|
|
const token = state.tokens[i]
|
|
|
|
// Case 1: already an html_block (e.g. multiline <CliCommand>)
|
|
if (token.type === 'html_block' && hasCliCommand(token.content)) {
|
|
token.content = replaceCliCommand(token.content, installTitle, installUrl, usageTitle, localeIndex)
|
|
i++
|
|
continue
|
|
}
|
|
|
|
// Case 2: single-line <CliCommand> parsed as paragraph > inline
|
|
if (
|
|
token.type === 'paragraph_open' &&
|
|
state.tokens[i + 1]?.type === 'inline' &&
|
|
hasCliCommand(state.tokens[i + 1].content) &&
|
|
state.tokens[i + 2]?.type === 'paragraph_close'
|
|
) {
|
|
const replaced = replaceCliCommand(state.tokens[i + 1].content, installTitle, installUrl, usageTitle, localeIndex)
|
|
const htmlToken = new state.Token('html_block', '', 0)
|
|
htmlToken.content = replaced
|
|
state.tokens.splice(i, 3, htmlToken)
|
|
i++
|
|
continue
|
|
}
|
|
|
|
i++
|
|
}
|
|
})
|
|
}
|