mirror of
https://github.com/longbridge/developers.git
synced 2026-09-19 03:34:09 +08:00
89d39f2be1
## Summary Syncs new SDK interfaces introduced in openapi to the developers documentation site, along with a range of docs, UI, and infrastructure improvements. ## New API Documentation (EN / zh-CN / zh-HK) ### Quote APIs - `option_volume` — option volume query - `option_volume_daily` — daily option volume query - `short_positions` — short position data - `update_pinned` — update pinned securities ### Fundamental APIs - **Calendar**: dividend, earnings, IPO, macro, split calendars - **Fundamental**: company profile, corporate actions, dividends, executives, financial reports, fund holdings, ratings, shareholders, valuations - **Market**: AH premium, broker positions, index components, market status, trading stats, unusual items ### Account APIs - **Alert**: create, delete, list, update alerts - **DCA** (dollar-cost averaging): create, delete, list, update, history - **Portfolio**: capital flow, exchange rates, profit analysis (by market, detail, summary) - **Sharelist**: create, delete, list, update share lists ## UI / Site Improvements - **Pricing page**: new tri-locale pricing comparison page for market data subscription tiers; fixes for responsive layout and static QR code on mobile - **FeaturesMenu**: mega-menu dropdown in main nav with 9 financial data product categories; full i18n support - **Nav**: moved API Reference from top nav to Docs sidebar (opens in new tab) - **Homepage visual polish**: neutral color vars on ArchCanvas, distinct card accent hues, section border delineation, ProductSkill gradient, flat GetStarted background - **Quote permission**: added permission notices across CLI docs, API docs, and API Reference; removed unreliable client-side permission status detection - **Mock login toggle**: dev-only panel to simulate login/logout state; shared reactive `useLoginState` composable ## Docs Updates - CLI v0.19.2 release notes + updated `finance-calendar` subcommand examples - Corrected quote permission names to match Quote Store (pluralization, Nasdaq Basic rename) - Updated skill guide to use CLI-first flow - Fixed missing zh-HK translations ## Bug Fixes - Fixed UnoCSS build error from arbitrary `font-feature-settings` class - Added `result.root` null guard in `postcss.config.mjs` to prevent PostCSS crash - Fixed home page footer rendering - Fixed pricing page build dist 🤖 Auto-generated by Endless. --------- Co-authored-by: 老袁 Yuan Zhanghong <zhanghong.yuan@longbridge-inc.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Jason Lee <huacnlee@gmail.com>
248 lines
9.7 KiB
TypeScript
248 lines
9.7 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>`
|
|
)
|
|
}
|
|
|
|
function replaceCliCommand(src: string, installTitle: string, installUrl: string, usageTitle: string, localeIndex: string): string {
|
|
return src.replace(
|
|
/<CliCommand>([\s\S]*?)<\/CliCommand>/g,
|
|
(_, 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' && token.content.includes('<CliCommand>')) {
|
|
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' &&
|
|
state.tokens[i + 1].content.includes('<CliCommand>') &&
|
|
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++
|
|
}
|
|
})
|
|
}
|