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,28 @@
import type { ComputedRef } from 'vue'
import type { CodeBlockMonacoOptions } from '../types/component-props'
import type { NodeRendererCodeBlockProps } from '../types/node-renderer-props'
import { inject, provide } from 'vue'
export interface MarkstreamRendererContext {
customId: ComputedRef<string | undefined>
isDark: ComputedRef<boolean | undefined>
themes: ComputedRef<string[] | undefined>
renderCodeBlocksAsPre: ComputedRef<boolean | undefined>
codeBlockStream: ComputedRef<boolean | undefined>
codeBlockDarkTheme: ComputedRef<any | undefined>
codeBlockLightTheme: ComputedRef<any | undefined>
codeBlockMonacoOptions: ComputedRef<CodeBlockMonacoOptions | undefined>
codeBlockMinWidth: ComputedRef<string | number | undefined>
codeBlockMaxWidth: ComputedRef<string | number | undefined>
codeBlockProps: ComputedRef<NodeRendererCodeBlockProps | undefined>
}
export const MARKSTREAM_RENDERER_CONTEXT = Symbol('MarkstreamRendererContext')
export function provideMarkstreamRendererContext(ctx: MarkstreamRendererContext) {
provide(MARKSTREAM_RENDERER_CONTEXT, ctx)
}
export function useMarkstreamRendererContext() {
return inject<MarkstreamRendererContext | null>(MARKSTREAM_RENDERER_CONTEXT, null)
}
@@ -0,0 +1,18 @@
import { readonly, shallowRef } from 'vue'
import { getKatex } from '../components/MathInlineNode/katex'
const katexReadyState = shallowRef(false)
let initPromise: Promise<void> | null = null
export function useKatexReady() {
if (!initPromise) {
initPromise = getKatex()
.then((k) => {
katexReadyState.value = !!k
})
.catch(() => {
katexReadyState.value = false
})
}
return readonly(katexReadyState)
}
@@ -0,0 +1,116 @@
import { getCurrentInstance } from 'vue'
function humanizeKey(key: string) {
const s = key.split('.').pop() || key
return s
.replace(/[_-]/g, ' ')
.replace(/([A-Z])/g, ' $1')
.replace(/\s+/g, ' ')
.replace(/\b\w/g, c => c.toUpperCase())
.trim()
}
const defaultMap: Record<string, string> = {
'common.copy': 'Copy',
'common.copied': 'Copied',
'common.decrease': 'Decrease',
'common.reset': 'Reset',
'common.increase': 'Increase',
'common.expand': 'Expand',
'common.collapse': 'Collapse',
'common.preview': 'Preview',
'common.source': 'Source',
'common.export': 'Export',
'common.open': 'Open',
'common.minimize': 'Minimize',
'common.zoomIn': 'Zoom in',
'common.zoomOut': 'Zoom out',
'common.resetZoom': 'Reset zoom',
'image.loadError': 'Image failed to load',
'image.loading': 'Loading image...',
}
/**
* Replace default fallback translations.
* Consumers can call this to provide their own fallback translations (e.g. Chinese).
*/
export function setDefaultI18nMap(map: Partial<Record<keyof typeof defaultMap, string>>) {
Object.assign(defaultMap, map)
}
interface Translator {
t: (key: string) => string
te?: (key: string) => boolean
}
function resolveComponentTranslator() {
try {
const instance = getCurrentInstance()
const proxy = instance?.proxy as unknown as Record<string, unknown> | null | undefined
const proxyT = proxy?.$t
if (typeof proxyT === 'function') {
const proxyTe = proxy?.$te
return {
t: proxyT.bind(proxy),
te: typeof proxyTe === 'function' ? proxyTe.bind(proxy) : undefined,
}
}
const globalProperties = instance?.appContext?.config?.globalProperties as Record<string, unknown> | undefined
const appT = globalProperties?.$t
if (typeof appT === 'function') {
const appTe = globalProperties?.$te
return {
t: appT.bind(globalProperties),
te: typeof appTe === 'function' ? appTe.bind(globalProperties) : undefined,
}
}
}
catch {}
return null
}
const fallbackT = (key: string) => defaultMap[key] ?? humanizeKey(key)
function withFallback(translator: Translator) {
return {
t(key: string) {
if (translator.te && defaultMap[key] && !translator.te(key))
return fallbackT(key)
const translated = translator.t(key)
return translated === key && defaultMap[key] ? fallbackT(key) : translated
},
}
}
export function useSafeI18n() {
const translator = resolveComponentTranslator()
if (translator)
return withFallback(translator)
// Synchronous fallback in case `vue-i18n` is not installed.
try {
// Try to use global installed vue-i18n if available synchronously via composition API
// This will work when the consumer has set up vue-i18n and the bundler left
// the runtime entry available. We access it via (global) require-ish path.
// Keep this non-throwing.
const possible = (globalThis as any).$vueI18nUse || null
if (possible && typeof possible === 'function') {
try {
const i18n = possible()
if (i18n && typeof i18n.t === 'function') {
return withFallback({
t: (i18n.t as any).bind(i18n),
te: typeof i18n.te === 'function' ? (i18n.te as any).bind(i18n) : undefined,
})
}
}
catch {}
}
}
catch {}
return { t: fallbackT }
}
@@ -0,0 +1,120 @@
import { createApp, h, ref } from 'vue'
import Tooltip from '../components/Tooltip/Tooltip.vue'
const visible = ref(false)
const content = ref('')
const placement = ref<'top' | 'bottom' | 'left' | 'right'>('top')
const anchorEl = ref<HTMLElement | null>(null)
const tooltipId = ref<string | null>(null)
const originX = ref<number | null>(null)
const originY = ref<number | null>(null)
const tooltipIsDark = ref<boolean | null>(null)
let showTimer: ReturnType<typeof setTimeout> | null = null
let hideTimer: ReturnType<typeof setTimeout> | null = null
function clearTimers() {
if (showTimer) {
clearTimeout(showTimer)
showTimer = null
}
if (hideTimer) {
clearTimeout(hideTimer)
hideTimer = null
}
}
// Mount singleton Tooltip once
let mounted = false
function ensureMounted() {
if (mounted)
return
if (typeof document === 'undefined')
return
try {
mounted = true
const container = document.createElement('div')
container.setAttribute('data-singleton-tooltip', '1')
document.body.appendChild(container)
const App = {
setup() {
return () => h(Tooltip as any, {
'visible': visible.value,
'anchor-el': anchorEl.value,
'content': content.value,
'placement': placement.value,
'id': tooltipId.value,
'originX': originX.value,
'originY': originY.value,
'isDark': tooltipIsDark.value ?? undefined,
})
},
}
createApp(App).mount(container)
}
catch (err) {
mounted = false
console.warn('[markstream-vue] Failed to mount Tooltip component. Tooltips will be disabled.', err)
}
}
export function showTooltipForAnchor(
el: HTMLElement | null,
text: string,
place: typeof placement.value = 'top',
immediate = false,
origin?: { x: number, y: number } | undefined,
isDark?: boolean | null,
) {
if (!el)
return
ensureMounted()
clearTimers()
const doShow = () => {
tooltipId.value = `tooltip-${Date.now()}-${Math.floor(Math.random() * 1000)}`
anchorEl.value = el
content.value = text
placement.value = place
// expose origin coordinates for initial animation
originX.value = origin?.x ?? null
originY.value = origin?.y ?? null
// allow caller to hint dark mode; if omitted, remain null (tooltip will detect global)
tooltipIsDark.value = typeof isDark === 'boolean' ? isDark : null
visible.value = true
try {
el.setAttribute('aria-describedby', tooltipId.value!)
}
catch {}
}
if (immediate)
doShow()
else showTimer = setTimeout(doShow, 80)
}
export function hideTooltip(immediate = false) {
clearTimers()
const doHide = () => {
if (anchorEl.value && tooltipId.value) {
try {
anchorEl.value.removeAttribute('aria-describedby')
}
catch {}
}
visible.value = false
anchorEl.value = null
tooltipId.value = null
originX.value = null
originY.value = null
}
if (immediate)
doHide()
else hideTimer = setTimeout(doHide, 120)
}
export default {
showTooltipForAnchor,
hideTooltip,
}
@@ -0,0 +1,330 @@
import type { Ref } from 'vue'
import { inject, provide, ref } from 'vue'
// Injection key for viewport-priority registration
const ViewportPriorityKey = Symbol('ViewportPriority') as unknown as InjectionKey<RegisterFn>
export interface VisibilityHandle {
isVisible: Ref<boolean>
whenVisible: Promise<void>
destroy: () => void
}
export type RegisterFn = (el: HTMLElement, opts?: { rootMargin?: string, threshold?: number }) => VisibilityHandle
type InjectionKey<T> = symbol & { __type?: T }
interface IdleDeadlineLike {
didTimeout: boolean
timeRemaining: () => number
}
/**
* Provide a shared viewport-priority registrar.
* Targets resolve immediately when they enter the viewport and otherwise
* trickle through during browser idle so heavy nodes can prerender offscreen.
*/
export function provideViewportPriority(
getRootEl: (target?: HTMLElement | null) => HTMLElement | null | undefined,
enabled: Ref<boolean> | boolean,
): RegisterFn {
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'
const enabledRef = typeof enabled === 'boolean' ? ref(enabled) : enabled
const requestIdle = isBrowser
? ((window as any).requestIdleCallback as ((cb: (deadline: IdleDeadlineLike) => void, opts?: { timeout?: number }) => number) | undefined)
?? ((cb: (deadline: IdleDeadlineLike) => void) => window.setTimeout(() => cb({ didTimeout: true, timeRemaining: () => 0 }), 16))
: null
const cancelIdle = isBrowser
? ((window as any).cancelIdleCallback as ((id: number) => void) | undefined)
?? ((id: number) => window.clearTimeout(id))
: null
interface ObserverConfig {
root: HTMLElement | null
rootMargin: string
threshold: number
}
// Lazily created IO bound to the provided root element
let io: IntersectionObserver | null = null
let currentConfig: ObserverConfig | null = null
const targets = new Map<Element, { resolve: () => void, visible: Ref<boolean> }>()
const idleQueue = new Set<Element>()
let idleJob: number | null = null
function normalizeConfig(target?: HTMLElement, opts?: { rootMargin?: string, threshold?: number }): ObserverConfig {
return {
root: getRootEl?.(target ?? null) ?? null,
rootMargin: opts?.rootMargin ?? '300px',
threshold: opts?.threshold ?? 0,
}
}
function sameConfig(a: ObserverConfig | null, b: ObserverConfig) {
return !!a
&& a.root === b.root
&& a.rootMargin === b.rootMargin
&& a.threshold === b.threshold
}
function clearIdleJob() {
if (idleJob == null)
return
try {
cancelIdle?.(idleJob)
}
catch {}
idleJob = null
}
function cleanupObserver() {
if (targets.size)
return
try {
io?.disconnect()
}
catch {}
io = null
currentConfig = null
if (!idleQueue.size)
clearIdleJob()
}
function settleTarget(target: Element) {
const data = targets.get(target)
if (!data)
return
if (!data.visible.value) {
data.visible.value = true
try {
data.resolve()
}
catch {}
}
try {
io?.unobserve(target)
}
catch {}
targets.delete(target)
idleQueue.delete(target)
cleanupObserver()
}
function scheduleIdleDrain() {
if (!requestIdle || idleJob != null || !idleQueue.size)
return
idleJob = requestIdle(() => {
idleJob = null
const next = idleQueue.values().next().value as Element | undefined
if (!next)
return
idleQueue.delete(next)
settleTarget(next)
if (idleQueue.size)
scheduleIdleDrain()
}, { timeout: 1200 })
}
function ensureObserver(target?: HTMLElement, opts?: { rootMargin?: string, threshold?: number }) {
if (!isBrowser)
return io
// Guard: some browser-like environments (e.g., jsdom) don't provide IO
if (typeof IntersectionObserver === 'undefined')
return null
const nextConfig = normalizeConfig(target, opts)
if (io && sameConfig(currentConfig, nextConfig))
return io
if (io) {
try {
io.disconnect()
}
catch {}
}
io = new IntersectionObserver((entries) => {
for (const entry of entries) {
const isVisible = entry.isIntersecting || entry.intersectionRatio > 0
if (isVisible)
settleTarget(entry.target)
}
}, {
root: nextConfig.root,
rootMargin: nextConfig.rootMargin, // prefetch slightly before entering viewport
threshold: nextConfig.threshold,
})
currentConfig = nextConfig
for (const el of targets.keys())
io.observe(el)
return io
}
const register: RegisterFn = (el, opts) => {
const visible = ref(false)
let settled = false
let resolve!: () => void
const whenVisible = new Promise<void>((res) => {
resolve = () => {
if (!settled) {
settled = true
res()
}
}
})
const cleanup = () => {
try {
io?.unobserve(el)
}
catch {}
targets.delete(el)
idleQueue.delete(el)
cleanupObserver()
}
if (!isBrowser || !enabledRef.value) {
visible.value = true
resolve()
return { isVisible: visible, whenVisible, destroy: cleanup }
}
const obs = ensureObserver(el, opts)
if (!obs) {
visible.value = true
resolve()
return { isVisible: visible, whenVisible, destroy: cleanup }
}
targets.set(el, { resolve, visible })
obs.observe(el)
idleQueue.add(el)
scheduleIdleDrain()
return { isVisible: visible, whenVisible, destroy: cleanup }
}
provide(ViewportPriorityKey, register)
return register
}
/**
* Child components call this to register an element and await priority.
* If provider is missing, returns a no-op registrar that resolves immediately.
*/
export function useViewportPriority() {
const injected = inject<RegisterFn | undefined>(ViewportPriorityKey, undefined)
if (injected)
return injected
const localTargets = new WeakMap<Element, { resolve: () => void, visible: Ref<boolean> }>()
let localIo: IntersectionObserver | null = null
const localIdleQueue = new Set<Element>()
let localIdleJob: number | null = null
const requestIdle = typeof window !== 'undefined'
? ((window as any).requestIdleCallback as ((cb: (deadline: IdleDeadlineLike) => void, opts?: { timeout?: number }) => number) | undefined)
?? ((cb: (deadline: IdleDeadlineLike) => void) => window.setTimeout(() => cb({ didTimeout: true, timeRemaining: () => 0 }), 16))
: null
const cancelIdle = typeof window !== 'undefined'
? ((window as any).cancelIdleCallback as ((id: number) => void) | undefined)
?? ((id: number) => window.clearTimeout(id))
: null
const clearLocalIdleJob = () => {
if (localIdleJob == null)
return
try {
cancelIdle?.(localIdleJob)
}
catch {}
localIdleJob = null
}
const settleLocalTarget = (target: Element) => {
const data = localTargets.get(target)
if (!data)
return
if (!data.visible.value) {
data.visible.value = true
try {
data.resolve()
}
catch {}
}
try {
localIo?.unobserve(target)
}
catch {}
localTargets.delete(target)
localIdleQueue.delete(target)
if (!localIdleQueue.size)
clearLocalIdleJob()
}
const scheduleLocalIdleDrain = () => {
if (!requestIdle || localIdleJob != null || !localIdleQueue.size)
return
localIdleJob = requestIdle(() => {
localIdleJob = null
const next = localIdleQueue.values().next().value as Element | undefined
if (!next)
return
localIdleQueue.delete(next)
settleLocalTarget(next)
if (localIdleQueue.size)
scheduleLocalIdleDrain()
}, { timeout: 1200 })
}
const ensureLocal = () => {
if (localIo)
return localIo
if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined')
return null
localIo = new IntersectionObserver((entries) => {
for (const e of entries) {
const vis = e.isIntersecting || e.intersectionRatio > 0
if (vis)
settleLocalTarget(e.target)
}
}, { root: null, rootMargin: '300px', threshold: 0 })
return localIo
}
const register: RegisterFn = (el) => {
const isVisible = ref(false)
let settled = false
let resolve!: () => void
const whenVisible = new Promise<void>((res) => {
resolve = () => {
if (!settled) {
settled = true
res()
}
}
})
const cleanup = () => {
try {
localIo?.unobserve(el)
}
catch {}
localTargets.delete(el)
localIdleQueue.delete(el)
if (!localIdleQueue.size)
clearLocalIdleJob()
}
const obs = ensureLocal()
if (!obs) {
isVisible.value = true
resolve()
return { isVisible, whenVisible, destroy: cleanup }
}
localTargets.set(el, { resolve, visible: isVisible })
obs.observe(el)
localIdleQueue.add(el)
scheduleLocalIdleDrain()
return { isVisible, whenVisible, destroy: cleanup }
}
return register
}