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:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,148 @@
export type LanguageIconResolver = (lang: string) => string | undefined | null
const DEFAULT_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M14 3h5a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5" />
<path d="M8 11l-3 3l3 3" />
<path d="M16 11l3 3l-3 3" />
<path d="M13 9l-2 10" />
</svg>`
const MERMAID_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="4" width="7" height="5" rx="1.5" />
<rect x="14" y="4" width="7" height="5" rx="1.5" />
<rect x="8.5" y="15" width="7" height="5" rx="1.5" />
<path d="M10 6.5h4" />
<path d="M12 9v2.5" />
<path d="M12 11.5l-3.5 3.5" />
<path d="M12 11.5l3.5 3.5" />
</svg>`
const HTML_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M8 7l-4 5l4 5" />
<path d="M16 7l4 5l-4 5" />
<path d="M14 4l-4 16" />
</svg>`
const MARKDOWN_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="4" width="18" height="16" rx="2" />
<path d="M7 15V9l3 3l3-3v6" />
<path d="M16 9v6" />
<path d="M14 13l2 2l2-2" />
</svg>`
const JSON_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M9 4c-2 0-3 1.5-3 3.5v2c0 1.1-.4 1.8-1.5 2.5C5.6 12.7 6 13.4 6 14.5v2C6 18.5 7 20 9 20" />
<path d="M15 4c2 0 3 1.5 3 3.5v2c0 1.1.4 1.8 1.5 2.5c-1.1.7-1.5 1.4-1.5 2.5v2c0 2-1 3.5-3 3.5" />
</svg>`
const TEXT_ICON = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 6h14" />
<path d="M12 6v12" />
<path d="M8 18h8" />
</svg>`
const LANGUAGE_ALIAS_MAP: Record<string, string> = {
'': '',
'cjs': 'javascript',
'd2lang': 'd2',
'js': 'javascript',
'jsx': 'jsx',
'md': 'markdown',
'mjs': 'javascript',
'plaintext': 'plain',
'py': 'python',
'sh': 'shell',
'shellscript': 'shell',
'text': 'plain',
'ts': 'typescript',
'tsx': 'tsx',
}
const LANGUAGE_LABEL_MAP: Record<string, string> = {
'': 'Text',
'css': 'CSS',
'd2': 'D2',
'html': 'HTML',
'javascript': 'JavaScript',
'json': 'JSON',
'jsx': 'JSX',
'markdown': 'Markdown',
'mermaid': 'Mermaid',
'plain': 'Text',
'plaintext': 'Text',
'python': 'Python',
'shell': 'Shell',
'svg': 'SVG',
'tsx': 'TSX',
'typescript': 'TypeScript',
'vue': 'Vue',
}
const LANGUAGE_ICON_MAP: Record<string, string> = {
'': TEXT_ICON,
'html': HTML_ICON,
'javascript': DEFAULT_ICON,
'json': JSON_ICON,
'jsx': DEFAULT_ICON,
'markdown': MARKDOWN_ICON,
'mermaid': MERMAID_ICON,
'plain': TEXT_ICON,
'python': DEFAULT_ICON,
'shell': DEFAULT_ICON,
'svg': HTML_ICON,
'tsx': DEFAULT_ICON,
'typescript': DEFAULT_ICON,
}
let userLanguageIconResolver: LanguageIconResolver | null = null
function extractLanguageToken(lang?: string | null) {
if (!lang)
return ''
const trimmed = lang.trim()
if (!trimmed)
return ''
const [firstToken] = trimmed.split(/\s+/)
const [base] = firstToken.split(':')
return base.toLowerCase()
}
export function normalizeLanguageIdentifier(lang?: string | null): string {
const token = extractLanguageToken(lang)
return LANGUAGE_ALIAS_MAP[token] ?? token
}
export function resolveMonacoLanguageId(lang?: string | null): string {
const canonical = normalizeLanguageIdentifier(lang)
if (!canonical)
return 'plaintext'
if (canonical === 'plain')
return 'plaintext'
if (canonical === 'jsx')
return 'javascript'
if (canonical === 'tsx')
return 'typescript'
return canonical
}
export function setLanguageIconResolver(resolver?: LanguageIconResolver | null) {
userLanguageIconResolver = resolver ?? null
}
export function getLanguageIcon(lang: string): string {
if (userLanguageIconResolver) {
const hit = userLanguageIconResolver(lang)
if (hit != null && hit !== '')
return hit
}
const normalized = normalizeLanguageIdentifier(lang)
return LANGUAGE_ICON_MAP[normalized] || DEFAULT_ICON
}
export const languageMap = LANGUAGE_LABEL_MAP
@@ -0,0 +1,8 @@
export function normalizeKaTeXRenderInput(content: string) {
if (!content)
return ''
return content
.replace(/·/g, '⋅')
.replace(/℃/g, '°C')
}