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:
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
repo: string
|
||||
}>()
|
||||
|
||||
const stars = ref<number | null>(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch(`https://api.github.com/repos/${props.repo}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
stars.value = data.stargazers_count
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Silently fail if API request fails
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${(num / 1000).toFixed(1)}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="`https://github.com/${repo}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="github-star-badge"
|
||||
aria-label="Star on GitHub"
|
||||
>
|
||||
<svg
|
||||
class="star-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"
|
||||
/>
|
||||
</svg>
|
||||
<span v-if="isLoading" class="stars-count">...</span>
|
||||
<span v-else-if="stars !== null" class="stars-count">{{ formatNumber(stars) }}</span>
|
||||
<span v-else class="stars-count">Star</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.github-star-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.4375rem 0.875rem;
|
||||
background: linear-gradient(135deg, #f6f8fa 0%, #f1f3f5 100%);
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 24px;
|
||||
color: #24292f;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f6f8fa 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.github-star-badge:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.github-star-badge {
|
||||
background: linear-gradient(135deg, #161b22 0%, #0d1117 100%);
|
||||
border-color: #30363d;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background: linear-gradient(135deg, #21262d 0%, #161b22 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.github-star-badge:hover .star-icon {
|
||||
transform: scale(1.15) rotate(8deg);
|
||||
}
|
||||
|
||||
.stars-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.025em;
|
||||
min-width: 2ch;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stars-count:empty::before {
|
||||
content: '...';
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const stars = ref<number | null>(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/Simon-He95/markstream-vue')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
stars.value = data.stargazers_count
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Silently fail if API request fails
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${(num / 1000).toFixed(1)}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultTheme.Layout>
|
||||
<template #nav-bar-content-after>
|
||||
<a
|
||||
href="https://github.com/Simon-He95/markstream-vue"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="github-star-badge-nav"
|
||||
aria-label="Star on GitHub"
|
||||
>
|
||||
<svg
|
||||
class="star-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"
|
||||
/>
|
||||
</svg>
|
||||
<span v-if="isLoading" class="stars-count">...</span>
|
||||
<span v-else-if="stars !== null" class="stars-count">{{ formatNumber(stars) }}</span>
|
||||
<span v-else class="stars-count">Star</span>
|
||||
</a>
|
||||
</template>
|
||||
</DefaultTheme.Layout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.github-star-badge-nav {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
margin-left: 0.75rem;
|
||||
background: linear-gradient(135deg, #f6f8fa 0%, #f1f3f5 100%);
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 20px;
|
||||
color: #24292f;
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.github-star-badge-nav:hover {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f6f8fa 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.github-star-badge-nav:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.dark .github-star-badge-nav {
|
||||
background: linear-gradient(135deg, #161b22 0%, #0d1117 100%);
|
||||
border-color: #30363d;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
|
||||
.dark .github-star-badge-nav:hover {
|
||||
background: linear-gradient(135deg, #21262d 0%, #161b22 100%);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.github-star-badge-nav .star-icon {
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.github-star-badge-nav:hover .star-icon {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.github-star-badge-nav .stars-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
/* Animation for loading state */
|
||||
.github-star-badge-nav .stars-count {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove animation when loaded */
|
||||
.github-star-badge-nav .stars-count:not(:empty) {
|
||||
animation: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description: string
|
||||
note?: string
|
||||
alipayLabel?: string
|
||||
wechatLabel?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
alipayLabel: 'Alipay',
|
||||
wechatLabel: 'WeChat Pay',
|
||||
})
|
||||
|
||||
const alipaySrc = withBase('/sponsor/zhifubao.jpg')
|
||||
const wechatSrc = withBase('/sponsor/weixin.jpg')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="support-qrs">
|
||||
<div class="support-qrs__copy">
|
||||
<h2 class="support-qrs__title">
|
||||
{{ props.title }}
|
||||
</h2>
|
||||
<p class="support-qrs__description">
|
||||
{{ props.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="support-qrs__grid">
|
||||
<figure class="support-qrs__card">
|
||||
<div class="support-qrs__image-frame">
|
||||
<img
|
||||
class="support-qrs__image"
|
||||
:src="alipaySrc"
|
||||
:alt="`${props.alipayLabel} QR code`"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<figcaption class="support-qrs__label">
|
||||
{{ props.alipayLabel }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="support-qrs__card">
|
||||
<div class="support-qrs__image-frame">
|
||||
<img
|
||||
class="support-qrs__image"
|
||||
:src="wechatSrc"
|
||||
:alt="`${props.wechatLabel} QR code`"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<figcaption class="support-qrs__label">
|
||||
{{ props.wechatLabel }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<p v-if="props.note" class="support-qrs__note">
|
||||
{{ props.note }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.support-qrs {
|
||||
margin: 1.5rem 0 2.25rem;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 20px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(61, 120, 255, 0.12), transparent 34%),
|
||||
linear-gradient(180deg, rgba(248, 250, 252, 0.92), rgba(255, 255, 255, 0.98));
|
||||
}
|
||||
|
||||
.support-qrs__copy {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.support-qrs__title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.support-qrs__description {
|
||||
max-width: 44rem;
|
||||
margin: 0.6rem auto 0;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.support-qrs__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.support-qrs__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.support-qrs__image-frame {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 320px;
|
||||
padding: 0.875rem;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, rgba(248, 250, 252, 0.94), rgba(241, 245, 249, 0.9));
|
||||
}
|
||||
|
||||
.support-qrs__image {
|
||||
width: auto;
|
||||
max-width: min(100%, 240px);
|
||||
max-height: 320px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.support-qrs__label {
|
||||
margin-top: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.support-qrs__note {
|
||||
margin: 1rem auto 0;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.dark .support-qrs {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(122, 162, 255, 0.16), transparent 36%),
|
||||
linear-gradient(180deg, rgba(16, 24, 40, 0.92), rgba(10, 15, 26, 0.98));
|
||||
}
|
||||
|
||||
.dark .support-qrs__card {
|
||||
background: rgba(15, 23, 42, 0.78);
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.dark .support-qrs__image-frame {
|
||||
background: linear-gradient(180deg, rgba(30, 41, 59, 0.82), rgba(15, 23, 42, 0.92));
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.support-qrs {
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.support-qrs__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.support-qrs__card {
|
||||
padding: 0.875rem;
|
||||
}
|
||||
|
||||
.support-qrs__image-frame {
|
||||
min-height: 260px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.support-qrs__image {
|
||||
max-height: 260px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useData } from 'vitepress'
|
||||
|
||||
/**
|
||||
* VitePress theme composable for dark mode
|
||||
* Uses VitePress's built-in isDark from useData()
|
||||
*
|
||||
* @example
|
||||
* ```vue
|
||||
* <script setup>
|
||||
* const isDark = useDark()
|
||||
* </script>
|
||||
*
|
||||
* <template>
|
||||
* <MarkdownRender :is-dark="isDark" />
|
||||
* </template>
|
||||
* ```
|
||||
*/
|
||||
export function useDark() {
|
||||
const { isDark } = useData()
|
||||
return isDark
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { EnhanceAppContext } from 'vitepress'
|
||||
import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'
|
||||
import MarkdownRender from 'markstream-vue'
|
||||
import Theme from 'vitepress/theme'
|
||||
import GitHubStarBadge from './GitHubStarBadge.vue'
|
||||
import Layout from './Layout.vue'
|
||||
import SupportQRCodes from './SupportQRCodes.vue'
|
||||
import '@shikijs/vitepress-twoslash/style.css'
|
||||
import 'markstream-vue/index.css'
|
||||
import './style.css'
|
||||
|
||||
export default {
|
||||
extends: Theme,
|
||||
Layout,
|
||||
enhanceApp({ app }: EnhanceAppContext) {
|
||||
app.use(TwoslashFloatingVue)
|
||||
app.component('GitHubStarBadge', GitHubStarBadge)
|
||||
app.component('MarkdownRender', MarkdownRender)
|
||||
app.component('SupportQRCodes', SupportQRCodes)
|
||||
},
|
||||
}
|
||||
|
||||
// Export useDark for use in VitePress markdown files and components
|
||||
export { useDark } from './composables/useDark'
|
||||
@@ -0,0 +1 @@
|
||||
/* Custom styles for markstream-vue theme */
|
||||
Reference in New Issue
Block a user