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,268 @@
# CodeBlockNode 组件
`CodeBlockNode` 是库中用于渲染富交互代码块的组件。对于需要编辑/高亮/增量渲染的场景,推荐安装 `stream-monaco`。组件为头部提供灵活的自定义点(props + slots),并在常见场景下提供可拦截的事件。
## 快速概览
- Monaco 模式(安装 `stream-monaco`)— 类编辑器渲染,带 worker 支持
- 降级模式 — 未安装 `stream-monaco` 时会回退为纯 `<pre><code>` 渲染
- 如果你希望用 Shiki(不引入 Monaco),请使用 `MarkdownCodeBlockNode`(同伴依赖:`stream-markdown`)
## Props
完整签名请参阅 `src/types/component-props.ts`。关键 props:
- `node` — code_block 节点(必需)
- `loading`、`stream`、`isShowPreview`
- `monacoOptions` — 类型为 `CodeBlockMonacoOptions`,会透传给 `stream-monaco`
- `diffHideUnchangedRegions`、`diffLineStyle`、`diffAppearance`、`diffUnchangedRegionStyle`、`diffHunkActionsOnHover`、`diffHunkHoverHideDelayMs`、`onDiffHunkAction` 这类 diff 配置都应该放这里
- 头部控制:`showHeader`、`showCollapseButton`、`showCopyButton`、`showExpandButton`、`showPreviewButton`、`showFontSizeButtons`、`showTooltips`
Monaco diff 模式下的默认行为:
- `diffHideUnchangedRegions: { enabled: true, contextLineCount: 2, minimumLineCount: 4, revealLineCount: 5 }`
- `diffLineStyle: 'background'`
- `diffAppearance: 'auto'`
- `diffUnchangedRegionStyle: 'line-info'`
- `diffHunkActionsOnHover: true`
- `diffHunkHoverHideDelayMs: 160`
你可以通过 `monacoOptions` 覆盖这些默认值。
当 preset 使用 `diffAppearance: 'auto'` 时,`CodeBlockNode` 会先根据当前明暗外观解析成实际的 light/dark,再传给 `stream-monaco`。
Diff 代码块的内置 header 现在也会显示 `- / +` 行数统计。
## Slots 插槽
- `header-left` — 替换左侧头部
- `header-right` — 替换右侧头部
- `loading` — 自定义流式禁用时的占位符
## Emits 事件
- `copy(text: string)` — 点击复制时触发
- `previewCode(payload)` — 仅在你监听 `@preview-code` 时才会触发;payload 为 `{ node, artifactType, artifactTitle, id }`
## 示例
### 安装并运行(Monaco 模式)
```bash
pnpm add stream-monaco
```
### 基础示例
```vue twoslash
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'js',
code: 'console.log(1)',
raw: 'console.log(1)',
} satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" />
</template>
```
### 替换头部并隐藏复制按钮
```vue twoslash
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'js',
code: 'console.log(1)',
raw: 'console.log(1)',
} satisfies CodeBlockNodeProps['node']
function runSnippet() {}
</script>
<template>
<CodeBlockNode :node="node" :show-copy-button="false">
<template #header-left>
<div class="flex items-center">
自定义左侧
</div>
</template>
<template #header-right>
<button @click="runSnippet">
运行
</button>
</template>
</CodeBlockNode>
</template>
```
### 自定义加载占位符
```vue twoslash
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'ts',
code: 'console.log("loading")',
raw: 'console.log("loading")',
} satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" :stream="false" :loading="true">
<template #loading="{ loading, stream }">
<div v-if="loading && !stream">
正在加载编辑器资源…
</div>
</template>
</CodeBlockNode>
</template>
```
## 主题切换
`CodeBlockNode` 支持基于深色/浅色模式的自动主题切换。使用 `@vueuse/core` 的 `useDark` composable 来追踪主题状态,并将主题名称传递给 `MarkdownRender` 或 `CodeBlockNode`。
### 在独立的 Vue 应用中使用 @vueuse/core
```vue
<script setup lang="ts">
import { useDark, useToggle } from '@vueuse/core'
import MarkdownRender from 'markstream-vue'
const isDark = useDark() // Ref<boolean> 响应式系统/主题偏好
const toggleDark = useToggle(isDark)
const content = '# 示例\n\n```js\nconsole.log("深色模式")\n```'
// 可用主题(必须包含你想要使用的主题)
const themes = [
'vitesse-dark',
'vitesse-light',
'github-dark',
'github-light',
// ... 更多主题
]
</script>
<template>
<div>
<button @click="toggleDark()">
切换主题
</button>
<MarkdownRender
:is-dark="isDark"
code-block-dark-theme="vitesse-dark"
code-block-light-theme="vitesse-light"
:themes="themes"
:content="content"
/>
</div>
</template>
```
### VitePress 集成
对于 VitePress,使用 VitePress 内置的 `useData()` 中的 `isDark`:
```ts
// docs/.vitepress/theme/composables/useDark.ts
import { useData } from 'vitepress'
/**
* VitePress 主题 composable 用于深色模式
* 使用 VitePress 内置的 useData() 获取 isDark
*/
export function useDark() {
const { isDark } = useData()
return isDark
}
```
```vue
<!-- 在任意 .md 文件或组件中 -->
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import { useDark } from '../../.vitepress/theme'
const isDark = useDark()
const content = '# 示例\n\n```js\nconsole.log("深色模式")\n```'
const themes = [
'vitesse-dark',
'vitesse-light',
'github-dark',
'github-light',
// ... 更多主题
]
</script>
<template>
<MarkdownRender
:is-dark="isDark"
code-block-dark-theme="vitesse-dark"
code-block-light-theme="vitesse-light"
:themes="themes"
:content="content"
/>
</template>
```
**工作原理:**
`theme` prop 支持固定主题或明暗配对:
```vue
<!-- 自动切换明暗(推荐) -->
<CodeBlockNode :theme="{ light: 'vitesse-light', dark: 'vitesse-dark' }" />
<!-- 固定主题(忽略 isDark) -->
<CodeBlockNode theme="monokai" />
<!-- 主题对象(固定,忽略 isDark) -->
<CodeBlockNode :theme="{ name: 'my-theme', colors: { ... } }" />
```
使用 `{ light, dark }` 配对时,组件根据 `isDark` prop 自动切换。
`themes` prop 用于注册可用主题,以便 Monaco 可以按需懒加载它们。
> **向后兼容:** `darkTheme` / `lightTheme` props 仍然可用但已废弃。推荐使用统一的 `theme` prop。
**CodeBlockNode 的关键差异:**
| Prop | 直接使用 CodeBlockNode | 通过 MarkdownRender |
|------|---------------------|-------------------|
| `isDark` | 直接传给 `<CodeBlockNode :is-dark="isDark" />` | 通过 `<MarkdownRender :is-dark="isDark" />` 传入并自动转发 |
| 主题 | `:theme="{ light: 'vitesse-light', dark: 'vitesse-dark' }"` | `:code-block-dark-theme="'vitesse-dark'"` `:code-block-light-theme="'vitesse-light'"` (兼容) |
| 主题列表 | `:themes="['vitesse-dark', 'vitesse-light', ...]"` | `:themes="['vitesse-dark', 'vitesse-light', ...]"` |
## 注意事项
- CodeBlock 头部 API 在 [codeblock-header](/zh/guide/codeblock-header) 中有文档说明(包含替换头部和自定义加载占位符的示例)。
- `CodeBlockNode` 和 `MermaidBlockNode` 的 `copy` 事件 payload 不同:`CodeBlockNode` 触发 `copy(text: string)`,而 `MermaidBlockNode` 触发 `copy(ev: MermaidBlockEvent<{ type: 'copy'; text: string }>)`(支持 `preventDefault()`)。
快速尝试 — 简单的行内用法示例:
```vue
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = { type: 'code_block', language: 'js', code: 'console.log("hello")', raw: 'console.log("hello")' } satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" />
</template>
```