This commit is contained in:
denislov 2025-02-12 00:39:50 +08:00
parent a0cfe7df4a
commit d958f8d31f
8 changed files with 448 additions and 449 deletions

View File

@ -117,7 +117,8 @@ For more detailed guidelines, please refer to our [Contributing Guide](./CONTRIB
Thank you for your support and contributions! Thank you for your support and contributions!
## Related Projects ## Related Projects
* [one-api](https://github.com/songquanpeng/one-api):LLM API management and distribution system, supporting mainstream models like OpenAI, Azure, and Anthropic. Features unified API interface, suitable for key management and secondary distribution.
- [one-api](https://github.com/songquanpeng/one-api):LLM API management and distribution system, supporting mainstream models like OpenAI, Azure, and Anthropic. Features unified API interface, suitable for key management and secondary distribution.
# 🚀 Contributors # 🚀 Contributors

View File

@ -118,7 +118,8 @@ Cherry Studioへの貢献を歓迎します以下の方法で貢献できま
ご支援と貢献に感謝します! ご支援と貢献に感謝します!
## 関連頁版 ## 関連頁版
* [one-api](https://github.com/songquanpeng/one-api):LLM APIの管理・配信システム。OpenAI、Azure、Anthropicなどの主要モデルに対応し、統一APIインターフェースを提供。APIキー管理と再配布に利用可能。
- [one-api](https://github.com/songquanpeng/one-api):LLM APIの管理・配信システム。OpenAI、Azure、Anthropicなどの主要モデルに対応し、統一APIインターフェースを提供。APIキー管理と再配布に利用可能。
# 🚀 コントリビューター # 🚀 コントリビューター

View File

@ -118,7 +118,8 @@ $ yarn build:linux
感谢您的支持和贡献! 感谢您的支持和贡献!
## 相关项目 ## 相关项目
* [one-api](https://github.com/songquanpeng/one-api):LLM API管理及分发系统支持OpenAI、Azure、Anthropic等主流模型统一API接口可用于密钥管理与二次分发。
- [one-api](https://github.com/songquanpeng/one-api):LLM API管理及分发系统支持OpenAI、Azure、Anthropic等主流模型统一API接口可用于密钥管理与二次分发。
# 🚀 贡献者 # 🚀 贡献者

View File

@ -1,118 +1,111 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CherryStudio 许可协议-ZH/EN</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
</head>
<head> <body class="bg-gray-100 p-8">
<meta charset="UTF-8" /> <div class="container mx-auto bg-white p-6 rounded shadow-lg">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio 许可协议</h1>
<title>CherryStudio 许可协议-ZH/EN</title> <div class="mb-8">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" /> <h2 class="text-2xl font-semibold mb-4">许可协议</h2>
</head> <p class="mb-4">
本软件采用 <strong>Apache License 2.0</strong> 许可。除 Apache License 2.0 规定的条款外,您在使用 Cherry
<body class="bg-gray-100 p-8"> Studio 时还应遵守以下附加条款:
<div class="container mx-auto bg-white p-6 rounded shadow-lg"> </p>
<h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio 许可协议</h1> <h3 class="text-xl font-semibold mb-2">一. 商用许可</h3>
<div class="mb-8"> <ol class="list-decimal list-inside mb-4">
<h2 class="text-2xl font-semibold mb-4">许可协议</h2> <li><strong>免费商用</strong>:用户在不修改代码的情况下,可以免费用于商业目的。</li>
<p class="mb-4"> <li>
本软件采用 <strong>Apache License 2.0</strong> 许可。除 Apache License 2.0 规定的条款外,您在使用 Cherry <strong>商业授权</strong>:如果您满足以下任意条件之一,需取得商业授权:
Studio 时还应遵守以下附加条款: <ol class="list-decimal list-inside ml-4">
</p> <li>对本软件进行二次修改、开发包括但不限于修改应用名称、logo、代码以及功能</li>
<h3 class="text-xl font-semibold mb-2">一. 商用许可</h3> <li>为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。</li>
<ol class="list-decimal list-inside mb-4"> <li>预装或集成到硬件设备或产品中进行捆绑销售。</li>
<li><strong>免费商用</strong>:用户在不修改代码的情况下,可以免费用于商业目的。</li> <li>政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。</li>
<li> </ol>
<strong>商业授权</strong>:如果您满足以下任意条件之一,需取得商业授权: </li>
<ol class="list-decimal list-inside ml-4"> </ol>
<li>对本软件进行二次修改、开发包括但不限于修改应用名称、logo、代码以及功能</li> <h3 class="text-xl font-semibold mb-2">二. 贡献者协议</h3>
<li>为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。</li> <ol class="list-decimal list-inside mb-4">
<li>预装或集成到硬件设备或产品中进行捆绑销售。</li> <li><strong>许可调整</strong>:生产者有权根据需要对开源协议进行调整,使其更加严格或宽松。</li>
<li>政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。</li> <li><strong>商业用途</strong>:您贡献的代码可能会被用于商业用途,包括但不限于云业务运营。</li>
</ol> </ol>
</li> <h3 class="text-xl font-semibold mb-2">三. 其他条款</h3>
</ol> <ol class="list-decimal list-inside mb-4">
<h3 class="text-xl font-semibold mb-2">二. 贡献者协议</h3> <li>本协议条款的解释权归 Cherry Studio 开发者所有。</li>
<ol class="list-decimal list-inside mb-4"> <li>本协议可能根据实际情况进行更新,更新时将通过本软件通知用户。</li>
<li><strong>许可调整</strong>:生产者有权根据需要对开源协议进行调整,使其更加严格或宽松。</li> </ol>
<li><strong>商业用途</strong>:您贡献的代码可能会被用于商业用途,包括但不限于云业务运营。</li> <p class="mb-4">如有任何问题或需申请商业授权,请联系 Cherry Studio 开发团队。</p>
</ol> <p>
<h3 class="text-xl font-semibold mb-2">三. 其他条款</h3> 除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。有关 Apache License 2.0 的详细信息,请访问
<ol class="list-decimal list-inside mb-4"> <a href="http://www.apache.org/licenses/LICENSE-2.0" class="text-blue-500 underline"
<li>本协议条款的解释权归 Cherry Studio 开发者所有。</li> >http://www.apache.org/licenses/LICENSE-2.0</a
<li>本协议可能根据实际情况进行更新,更新时将通过本软件通知用户。</li> >
</ol> </p>
<p class="mb-4">如有任何问题或需申请商业授权,请联系 Cherry Studio 开发团队。</p> </div>
<p> <h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio License</h1>
除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。有关 Apache License 2.0 的详细信息,请访问 <div class="mb-8">
<a href="http://www.apache.org/licenses/LICENSE-2.0" <h2 class="text-2xl font-semibold mb-4">License Agreement</h2>
class="text-blue-500 underline">http://www.apache.org/licenses/LICENSE-2.0</a> <p class="mb-4">
</p> This software is licensed under the <strong>Apache License 2.0</strong>. In addition to the terms of the
Apache License 2.0, the following additional terms apply to the use of Cherry Studio:
</p>
<h3 class="text-xl font-semibold mb-2">I. Commercial Use License</h3>
<ol class="list-decimal list-inside mb-4">
<li>
<strong>Free Commercial Use</strong>: Users can use the software for commercial purposes without modifying
the code.
</li>
<li>
<strong>Commercial License Required</strong>: A commercial license is required if any of the following
conditions are met:
<ol class="list-decimal list-inside ml-4">
<li>
You modify, develop, or alter the software, including but not limited to changes to the application
name, logo, code, or functionality.
</li>
<li>You provide multi-tenant services to enterprise customers with 10 or more users.</li>
<li>
You pre-install or integrate the software into hardware devices or products and bundle it for sale.
</li>
<li>
You are engaging in large-scale procurement for government or educational institutions, especially
involving security, data privacy, or other sensitive requirements.
</li>
</ol>
</li>
</ol>
<h3 class="text-xl font-semibold mb-2">II. Contributor Agreement</h3>
<ol class="list-decimal list-inside mb-4">
<li>
<strong>License Adjustment</strong>: The producer reserves the right to adjust the open-source license as
needed, making it stricter or more lenient.
</li>
<li>
<strong>Commercial Use</strong>: Any code you contribute may be used for commercial purposes, including but
not limited to cloud business operations.
</li>
</ol>
<h3 class="text-xl font-semibold mb-2">III. Other Terms</h3>
<ol class="list-decimal list-inside mb-4">
<li>The interpretation of these terms is subject to the discretion of Cherry Studio developers.</li>
<li>These terms may be updated, and users will be notified through the software when changes occur.</li>
</ol>
<p class="mb-4">
For any questions or to request a commercial license, please contact the Cherry Studio development team.
</p>
<p>
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache
License 2.0. Detailed information about the Apache License 2.0 can be found at
<a href="http://www.apache.org/licenses/LICENSE-2.0" class="text-blue-500 underline"
>http://www.apache.org/licenses/LICENSE-2.0</a
>
</p>
</div>
</div> </div>
<h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio License</h1> </body>
<div class="mb-8"> </html>
<h2 class="text-2xl font-semibold mb-4">License Agreement</h2>
<p class="mb-4">
This software is licensed under the <strong>Apache License 2.0</strong>. In addition to the terms of the
Apache License 2.0, the following additional terms apply to the use of Cherry Studio:
</p>
<h3 class="text-xl font-semibold mb-2">I. Commercial Use License</h3>
<ol class="list-decimal list-inside mb-4">
<li>
<strong>Free Commercial Use</strong>: Users can use the software for commercial purposes without
modifying
the code.
</li>
<li>
<strong>Commercial License Required</strong>: A commercial license is required if any of the
following
conditions are met:
<ol class="list-decimal list-inside ml-4">
<li>
You modify, develop, or alter the software, including but not limited to changes to the
application
name, logo, code, or functionality.
</li>
<li>You provide multi-tenant services to enterprise customers with 10 or more users.</li>
<li>
You pre-install or integrate the software into hardware devices or products and bundle it
for sale.
</li>
<li>
You are engaging in large-scale procurement for government or educational institutions,
especially
involving security, data privacy, or other sensitive requirements.
</li>
</ol>
</li>
</ol>
<h3 class="text-xl font-semibold mb-2">II. Contributor Agreement</h3>
<ol class="list-decimal list-inside mb-4">
<li>
<strong>License Adjustment</strong>: The producer reserves the right to adjust the open-source
license as
needed, making it stricter or more lenient.
</li>
<li>
<strong>Commercial Use</strong>: Any code you contribute may be used for commercial purposes,
including but
not limited to cloud business operations.
</li>
</ol>
<h3 class="text-xl font-semibold mb-2">III. Other Terms</h3>
<ol class="list-decimal list-inside mb-4">
<li>The interpretation of these terms is subject to the discretion of Cherry Studio developers.</li>
<li>These terms may be updated, and users will be notified through the software when changes occur.</li>
</ol>
<p class="mb-4">
For any questions or to request a commercial license, please contact the Cherry Studio development team.
</p>
<p>
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache
License 2.0. Detailed information about the Apache License 2.0 can be found at
<a href="http://www.apache.org/licenses/LICENSE-2.0"
class="text-blue-500 underline">http://www.apache.org/licenses/LICENSE-2.0</a>
</p>
</div>
</div>
</body>
</html>

View File

@ -1,7 +1,6 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Github Releases Timeline</title> <title>Github Releases Timeline</title>
@ -9,194 +8,201 @@
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.5.10/dist/typography.min.css"></script> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.5.10/dist/typography.min.css"></script>
</head> </head>
<body id="app"> <body id="app">
<div :class="isDark ? 'dark-bg' : 'bg'" class="min-h-screen"> <div :class="isDark ? 'dark-bg' : 'bg'" class="min-h-screen">
<div class="max-w-3xl mx-auto py-12 px-4"> <div class="max-w-3xl mx-auto py-12 px-4">
<h1 class="text-3xl font-bold mb-8" :class="isDark ? 'text-white' : 'text-gray-900'">Release Timeline</h1> <h1 class="text-3xl font-bold mb-8" :class="isDark ? 'text-white' : 'text-gray-900'">Release Timeline</h1>
<!-- Loading状态 --> <!-- Loading状态 -->
<div v-if="loading" class="text-center py-8"> <div v-if="loading" class="text-center py-8">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-4" <div
:class="isDark ? 'border-gray-700 border-t-blue-500' : 'border-gray-300 border-t-blue-500'"></div> class="inline-block animate-spin rounded-full h-8 w-8 border-4"
</div> :class="isDark ? 'border-gray-700 border-t-blue-500' : 'border-gray-300 border-t-blue-500'"></div>
<!-- Error 状态 -->
<div v-else-if="error" class="text-red-500 text-center py-8">{{ error }}</div>
<!-- Release 列表 -->
<div v-else class="space-y-8">
<div v-for="release in releases" :key="release.id" class="relative pl-8"
:class="isDark ? 'border-l-2 border-gray-700' : 'border-l-2 border-gray-200'">
<div class="absolute -left-2 top-0 w-4 h-4 rounded-full bg-green-500"></div>
<div class="rounded-lg shadow-sm p-6 transition-shadow"
:class="isDark ? 'bg-black hover:shadow-md hover:shadow-black' : 'bg-white hover:shadow-md'">
<div class="flex items-start justify-between mb-4">
<div>
<h2 class="text-xl font-semibold" :class="isDark ? 'text-white' : 'text-gray-900'">
{{ release.name || release.tag_name }}
</h2>
<p class="text-sm mt-1" :class="isDark ? 'text-gray-400' : 'text-gray-500'">
{{ formatDate(release.published_at) }}
</p>
</div>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium"
:class="isDark ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'">
{{ release.tag_name }}
</span>
</div>
<div class="prose" :class="isDark ? 'text-gray-300 dark-prose' : 'text-gray-600'"
v-html="renderMarkdown(release.body)"></div>
</div>
</div>
</div>
</div> </div>
<!-- Error 状态 -->
<div v-else-if="error" class="text-red-500 text-center py-8">{{ error }}</div>
<!-- Release 列表 -->
<div v-else class="space-y-8">
<div
v-for="release in releases"
:key="release.id"
class="relative pl-8"
:class="isDark ? 'border-l-2 border-gray-700' : 'border-l-2 border-gray-200'">
<div class="absolute -left-2 top-0 w-4 h-4 rounded-full bg-green-500"></div>
<div
class="rounded-lg shadow-sm p-6 transition-shadow"
:class="isDark ? 'bg-black hover:shadow-md hover:shadow-black' : 'bg-white hover:shadow-md'">
<div class="flex items-start justify-between mb-4">
<div>
<h2 class="text-xl font-semibold" :class="isDark ? 'text-white' : 'text-gray-900'">
{{ release.name || release.tag_name }}
</h2>
<p class="text-sm mt-1" :class="isDark ? 'text-gray-400' : 'text-gray-500'">
{{ formatDate(release.published_at) }}
</p>
</div>
<span
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium"
:class="isDark ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'">
{{ release.tag_name }}
</span>
</div>
<div
class="prose"
:class="isDark ? 'text-gray-300 dark-prose' : 'text-gray-600'"
v-html="renderMarkdown(release.body)"></div>
</div>
</div>
</div>
</div>
</div> </div>
<script> <script>
const md = window.markdownit({ const md = window.markdownit({
breaks: true, breaks: true,
linkify: true linkify: true
}) })
const { createApp } = Vue const { createApp } = Vue
createApp({ createApp({
data() { data() {
return { return {
releases: [], releases: [],
loading: true, loading: true,
error: null, error: null,
isDark: false isDark: false
} }
}, },
methods: { methods: {
async fetchReleases() { async fetchReleases() {
try { try {
this.loading = true this.loading = true
this.error = null this.error = null
const response = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases') const response = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases')
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch releases') throw new Error('Failed to fetch releases')
} }
this.releases = await response.json() this.releases = await response.json()
} catch (err) { } catch (err) {
this.error = 'Error loading releases: ' + err.message this.error = 'Error loading releases: ' + err.message
} finally { } finally {
this.loading = false this.loading = false
}
},
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
},
renderMarkdown(content) {
if (!content) return ''
return md.render(content)
},
initTheme() {
// 从 URL 参数获取主题设置
const url = new URL(window.location.href)
const theme = url.searchParams.get('theme')
this.isDark = theme === 'dark'
}
},
mounted() {
this.initTheme()
this.fetchReleases()
} }
}).mount('#app') },
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
},
renderMarkdown(content) {
if (!content) return ''
return md.render(content)
},
initTheme() {
// 从 URL 参数获取主题设置
const url = new URL(window.location.href)
const theme = url.searchParams.get('theme')
this.isDark = theme === 'dark'
}
},
mounted() {
this.initTheme()
this.fetchReleases()
}
}).mount('#app')
</script> </script>
<style> <style>
/* 基础的 Markdown 样式 */ /* 基础的 Markdown 样式 */
.prose { .prose {
line-height: 1.6; line-height: 1.6;
} }
.prose h1 { .prose h1 {
font-size: 1.5em; font-size: 1.5em;
margin: 1em 0; margin: 1em 0;
} }
.prose h2 { .prose h2 {
font-size: 1.3em; font-size: 1.3em;
margin: 0.8em 0; margin: 0.8em 0;
} }
.prose h3 { .prose h3 {
font-size: 1.1em; font-size: 1.1em;
margin: 0.6em 0; margin: 0.6em 0;
} }
.prose ul { .prose ul {
list-style-type: disc; list-style-type: disc;
margin-left: 1.5em; margin-left: 1.5em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.prose ol { .prose ol {
list-style-type: decimal; list-style-type: decimal;
margin-left: 1.5em; margin-left: 1.5em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.prose code { .prose code {
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
border-radius: 0.2em; border-radius: 0.2em;
font-size: 0.9em; font-size: 0.9em;
} }
.dark .prose code { .dark .prose code {
background-color: #1f2937; background-color: #1f2937;
} }
.prose code { .prose code {
background-color: #f3f4f6; background-color: #f3f4f6;
} }
.prose pre code { .prose pre code {
display: block; display: block;
padding: 1em; padding: 1em;
overflow-x: auto; overflow-x: auto;
} }
.prose a { .prose a {
color: #3b82f6; color: #3b82f6;
text-decoration: underline; text-decoration: underline;
} }
.dark .prose a { .dark .prose a {
color: #60a5fa; color: #60a5fa;
} }
.prose blockquote { .prose blockquote {
border-left: 4px solid #e5e7eb; border-left: 4px solid #e5e7eb;
padding-left: 1em; padding-left: 1em;
margin: 1em 0; margin: 1em 0;
} }
.dark .prose blockquote { .dark .prose blockquote {
border-left-color: #374151; border-left-color: #374151;
color: #9ca3af; color: #9ca3af;
} }
.dark .prose { .dark .prose {
color: #e5e7eb; color: #e5e7eb;
} }
.dark-bg { .dark-bg {
background-color: #151515; background-color: #151515;
} }
.bg { .bg {
background-color: #f2f2f2; background-color: #f2f2f2;
} }
</style> </style>
</body> </body>
</html>
</html>

View File

@ -1,42 +1,41 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<head> <style>
<meta charset="UTF-8" /> html,
<meta name="viewport" content="initial-scale=1, width=device-width" /> body {
<meta http-equiv="Content-Security-Policy" margin: 0;
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" /> }
<style> #spinner {
html, position: fixed;
body { width: 100vw;
margin: 0; height: 100vh;
} flex-direction: row;
justify-content: center;
align-items: center;
display: none;
}
#spinner { #spinner img {
position: fixed; width: 100px;
width: 100vw; border-radius: 50px;
height: 100vh; }
flex-direction: row; </style>
justify-content: center; </head>
align-items: center;
display: none;
}
#spinner img { <body>
width: 100px; <div id="root"></div>
border-radius: 50px; <div id="spinner">
} <img src="/src/assets/images/logo.png" />
</style> </div>
</head> <script type="module" src="/src/init.ts"></script>
<script type="module" src="/src/main.tsx"></script>
<body> </body>
<div id="root"></div> </html>
<div id="spinner">
<img src="/src/assets/images/logo.png" />
</div>
<script type="module" src="/src/init.ts"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -95,14 +95,14 @@ export const Box = styled.div<BoxProps>`
box-sizing: border-box; box-sizing: border-box;
border: ${(props) => props?.border || 'none'}; border: ${(props) => props?.border || 'none'};
gap: ${(p) => (p.gap ? getElementValue(p.gap) : 0)}; gap: ${(p) => (p.gap ? getElementValue(p.gap) : 0)};
margin: ${(props) => (props.m || props.margin ? props.m ?? props.margin : 'none')}; margin: ${(props) => (props.m || props.margin ? (props.m ?? props.margin) : 'none')};
margin-top: ${(props) => (props.mt || props.marginTop ? getElementValue(props.mt || props.marginTop) : 'default')}; margin-top: ${(props) => (props.mt || props.marginTop ? getElementValue(props.mt || props.marginTop) : 'default')};
margin-bottom: ${(props) => margin-bottom: ${(props) =>
props.mb || props.marginBottom ? getElementValue(props.mb ?? props.marginBottom) : 'default'}; props.mb || props.marginBottom ? getElementValue(props.mb ?? props.marginBottom) : 'default'};
margin-left: ${(props) => (props.ml || props.marginLeft ? getElementValue(props.ml ?? props.marginLeft) : 'default')}; margin-left: ${(props) => (props.ml || props.marginLeft ? getElementValue(props.ml ?? props.marginLeft) : 'default')};
margin-right: ${(props) => margin-right: ${(props) =>
props.mr || props.marginRight ? getElementValue(props.mr ?? props.marginRight) : 'default'}; props.mr || props.marginRight ? getElementValue(props.mr ?? props.marginRight) : 'default'};
padding: ${(props) => (props.p || props.padding ? props.p ?? props.padding : 'none')}; padding: ${(props) => (props.p || props.padding ? (props.p ?? props.padding) : 'none')};
padding-top: ${(props) => (props.pt || props.paddingTop ? getElementValue(props.pt ?? props.paddingTop) : 'auto')}; padding-top: ${(props) => (props.pt || props.paddingTop ? getElementValue(props.pt ?? props.paddingTop) : 'auto')};
padding-bottom: ${(props) => padding-bottom: ${(props) =>
props.pb || props.paddingBottom ? getElementValue(props.pb ?? props.paddingBottom) : 'auto'}; props.pb || props.paddingBottom ? getElementValue(props.pb ?? props.paddingBottom) : 'auto'};

View File

@ -1,146 +1,144 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Selection Menu</title>
<title>Selection Menu</title> <style>
<style>
:root {
--bg-color: rgba(255, 255, 255, 0.95);
--button-bg: #f5f5f5;
--button-hover: #e8e8e8;
--text-color: #333;
--border-color: rgba(0, 0, 0, 0.06);
}
@media (prefers-color-scheme: dark) {
:root { :root {
--bg-color: rgba(80, 80, 80, 0.95); --bg-color: rgba(255, 255, 255, 0.95);
--button-bg: #2c2c2c; --button-bg: #f5f5f5;
--button-hover: #383838; --button-hover: #e8e8e8;
--text-color: #e0e0e0; --text-color: #333;
--border-color: rgba(255, 255, 255, 0.08); --border-color: rgba(0, 0, 0, 0.06);
} }
}
* { @media (prefers-color-scheme: dark) {
margin: 0; :root {
padding: 0; --bg-color: rgba(80, 80, 80, 0.95);
box-sizing: border-box; --button-bg: #2c2c2c;
user-select: none; --button-hover: #383838;
} --text-color: #e0e0e0;
--border-color: rgba(255, 255, 255, 0.08);
}
}
body { * {
width: 280px; margin: 0;
height: 40px; padding: 0;
background: var(--bg-color); box-sizing: border-box;
overflow: hidden; user-select: none;
display: flex; }
align-items: center;
}
.drag-handle { body {
width: 20px; width: 280px;
height: 100%; height: 40px;
display: flex; background: var(--bg-color);
align-items: center; overflow: hidden;
justify-content: center; display: flex;
gap: 2px; align-items: center;
-webkit-app-region: drag; }
}
.drag-handle::before, .drag-handle {
.drag-handle::after { width: 20px;
content: ''; height: 100%;
width: 2px; display: flex;
height: 16px; align-items: center;
background-color: var(--border-color); justify-content: center;
border-radius: 1px; gap: 2px;
} -webkit-app-region: drag;
}
menu { .drag-handle::before,
display: flex; .drag-handle::after {
align-items: center; content: '';
height: 40px; width: 2px;
flex: 1; height: 16px;
margin-right: 10px; background-color: var(--border-color);
gap: 5px; border-radius: 1px;
} }
button { menu {
flex: 1; display: flex;
min-width: 0; align-items: center;
height: 32px; height: 40px;
border: none; flex: 1;
background: transparent; margin-right: 10px;
border-radius: 6px; gap: 5px;
cursor: pointer; }
font-size: 13px;
font-weight: 500;
color: var(--text-color);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
outline: none;
}
button:hover { button {
background: var(--button-hover); flex: 1;
} min-width: 0;
height: 32px;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
color: var(--text-color);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
outline: none;
}
button:active { button:hover {
transform: scale(0.95); background: var(--button-hover);
} }
svg { button:active {
width: 16px; transform: scale(0.95);
height: 16px; }
fill: currentColor;
}
</style>
</head>
<body> svg {
<div class="drag-handle"></div> width: 16px;
<menu> height: 16px;
<button data-action="chat"> fill: currentColor;
<svg viewBox="0 0 24 24"> }
<path d="M20,2H4C2.9,2,2,2.9,2,4v18l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M20,16H6l-2,2V4h16V16z" /> </style>
</svg> </head>
提问
</button>
<button data-action="explanation">
<svg viewBox="0 0 24 24">
<path
d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M13,17h-2v-6h2V17z M13,9h-2V7h2V9z" />
</svg>
释义
</button>
<button data-action="translate">
<svg viewBox="0 0 24 24">
<path d="M6 4h12v2H6zM6 10h12v2H6zM6 16h8v2H6z" />
</svg>
翻译
</button>
<button data-action="summary">
<svg viewBox="0 0 24 24">
<path d="M14,17H4v2h10V17z M20,9H4v2h16V9z M4,15h16v-2H4V15z M4,5v2h16V5H4z" />
</svg>
总结
</button>
</menu>
<script> <body>
document.querySelectorAll('button').forEach(button => { <div class="drag-handle"></div>
button.addEventListener('click', () => { <menu>
const action = button.getAttribute('data-action') <button data-action="chat">
window.api.selectionMenu.action(action) <svg viewBox="0 0 24 24">
<path d="M20,2H4C2.9,2,2,2.9,2,4v18l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M20,16H6l-2,2V4h16V16z" />
</svg>
提问
</button>
<button data-action="explanation">
<svg viewBox="0 0 24 24">
<path
d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M13,17h-2v-6h2V17z M13,9h-2V7h2V9z" />
</svg>
释义
</button>
<button data-action="translate">
<svg viewBox="0 0 24 24">
<path d="M6 4h12v2H6zM6 10h12v2H6zM6 16h8v2H6z" />
</svg>
翻译
</button>
<button data-action="summary">
<svg viewBox="0 0 24 24">
<path d="M14,17H4v2h10V17z M20,9H4v2h16V9z M4,15h16v-2H4V15z M4,5v2h16V5H4z" />
</svg>
总结
</button>
</menu>
<script>
document.querySelectorAll('button').forEach((button) => {
button.addEventListener('click', () => {
const action = button.getAttribute('data-action')
window.api.selectionMenu.action(action)
})
}) })
}) </script>
</script> </body>
</body> </html>
</html>