Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 689b0d5d14 | |||
| a058e62595 | |||
| 8571fc0f5c | |||
| 4dad0a86b3 | |||
| 3168b7ff43 | |||
| d002a5be5a | |||
| 24a550463c | |||
| 14ae3e80c4 | |||
| e4d3969e95 | |||
| 0b16d1d4ac | |||
| 300514531d | |||
| 6a4780b002 | |||
| 5688304817 | |||
| 4380ad010c | |||
| 4fa6bb42e3 | |||
| 7aa3a7e37f | |||
| 94306497a9 | |||
| 93c85b800b | |||
| 8ac78e39f1 | |||
| 61a23fe7f2 | |||
| 87fea58102 | |||
|
|
edeac01bee | ||
| 852424356a | |||
| b704dd2438 | |||
| aa8139884b | |||
| 9a15df01ee | |||
| 03780b5bc7 | |||
| b5d90cc59a | |||
| d49ffc20df | |||
| c22e349181 | |||
| 45968cd353 | |||
| 2d02bf7f1f | |||
| 1216b0b67c | |||
| cf8bf688bf | |||
| 4d6a4ff79f | |||
| 3077d5a7c5 | |||
| bc0569af93 | |||
| 0188b618f2 | |||
| 08860e9a5c | |||
| a56d4ef379 | |||
| f5bfff80b7 | |||
| 1462d8a753 |
67
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程
|
||||||
|
#
|
||||||
|
name: Deploy VitePress site to Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
# 在针对 `main` 分支的推送上运行。如果你
|
||||||
|
# 使用 `master` 分支作为默认分支,请将其更改为 `master`
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
# 允许你从 Actions 选项卡手动运行此工作流程
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列
|
||||||
|
# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# 构建工作
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # 如果未启用 lastUpdated,则不需要
|
||||||
|
# - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消此区域注释
|
||||||
|
# with:
|
||||||
|
# version: 9
|
||||||
|
# - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd frontend && npm ci
|
||||||
|
- name: Build with VitePress
|
||||||
|
run: cd frontend && npm run docs:build
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: frontend/docs/.vitepress/dist
|
||||||
|
|
||||||
|
# 部署工作
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Deploy
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
2
.gitignore
vendored
@@ -5,3 +5,5 @@ frontend/node_modules
|
|||||||
build/linux/appimage/build
|
build/linux/appimage/build
|
||||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||||
.idea
|
.idea
|
||||||
|
frontend/docs/.vitepress/cache/
|
||||||
|
frontend/docs/.vitepress/dist/
|
||||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 0 B |
@@ -39,7 +39,7 @@ tasks:
|
|||||||
summary: Generates Windows `.syso` file
|
summary: Generates Windows `.syso` file
|
||||||
dir: build
|
dir: build
|
||||||
cmds:
|
cmds:
|
||||||
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
- wails3 generate syso -arch {{.ARCH}} -icon windows/favicon_256x256.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
voidraft.landaiqing.cn
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>voidraft - Changelog</title>
|
|
||||||
<link rel="stylesheet" href="css/styles.css">
|
|
||||||
<link rel="stylesheet" href="css/changelog.css">
|
|
||||||
<link rel="icon" href="img/favicon.ico">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Space+Mono&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
</head>
|
|
||||||
<body class="theme-dark">
|
|
||||||
<div class="container">
|
|
||||||
<!-- 主卡片 -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h1 class="card-title" data-en="voidraft Changelog" data-zh="voidraft 更新日志">voidraft Changelog</h1>
|
|
||||||
<div class="card-controls">
|
|
||||||
<button id="theme-toggle" class="btn btn-secondary" title="切换主题">
|
|
||||||
<i class="fas fa-sun"></i> <span data-en="Theme" data-zh="主题">Theme</span>
|
|
||||||
</button>
|
|
||||||
<button id="lang-toggle" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-language"></i> 中/EN
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-content">
|
|
||||||
<!-- 导航区域 -->
|
|
||||||
<div class="nav-links">
|
|
||||||
<a href="index.html" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-home"></i> <span data-en="Home" data-zh="首页">Home</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft" class="btn btn-secondary">
|
|
||||||
<i class="fab fa-github"></i> <span data-en="Source Code" data-zh="源代码">Source Code</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 加载中提示 -->
|
|
||||||
<div id="loading" class="loading-container">
|
|
||||||
<div class="loading-spinner"></div>
|
|
||||||
<p data-en="Loading releases..." data-zh="正在加载版本信息...">Loading releases...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 更新日志内容 -->
|
|
||||||
<div id="changelog" class="changelog-container">
|
|
||||||
<!-- 通过JavaScript动态填充内容 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 错误信息 -->
|
|
||||||
<div id="error-message" class="error-container" style="display: none;">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<p data-en="Failed to load release information. Please try again later."
|
|
||||||
data-zh="加载版本信息失败,请稍后再试。">Failed to load release information. Please try again later.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 页脚 -->
|
|
||||||
<footer class="footer">
|
|
||||||
<p class="footer-text" data-en="© 2025 voidraft - An elegant text snippet recording tool designed for developers" data-zh="© 2025 voidraft - 专为开发者打造的优雅文本片段记录工具">© 2023-2024 voidraft - An elegant text snippet recording tool designed for developers</p>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a href="https://github.com/landaiqing/voidraft" target="_blank" class="footer-link">GitHub</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft/issues" target="_blank" class="footer-link" data-en="Issues" data-zh="问题反馈">Issues</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft/releases" target="_blank" class="footer-link" data-en="Releases" data-zh="版本发布">Releases</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
<script src="js/changelog.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,347 +0,0 @@
|
|||||||
/* 更新日志页面样式 */
|
|
||||||
.nav-links {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
|
||||||
border-left-color: var(--primary-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .loading-spinner {
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
border-left-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--error-color);
|
|
||||||
padding: 20px;
|
|
||||||
border: 2px dashed var(--error-color);
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: rgba(var(--card-bg-rgb), 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container i {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 更新日志容器 */
|
|
||||||
.changelog-container {
|
|
||||||
display: none;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
border-left: 4px solid var(--primary-color);
|
|
||||||
padding-left: 20px;
|
|
||||||
background-color: rgba(var(--card-bg-rgb), 0.5);
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-version {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-date {
|
|
||||||
color: var(--text-color);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 3px 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-left: 10px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-badge.pre-release {
|
|
||||||
background-color: var(--warning-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-description {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-assets {
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.7);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-assets-title {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-list {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-icon {
|
|
||||||
margin-right: 10px;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-name {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-size {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-color);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 资源下载按钮 */
|
|
||||||
.download-btn {
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 3px 10px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn:hover {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content {
|
|
||||||
line-height: 1.8;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content h1,
|
|
||||||
.markdown-content h2,
|
|
||||||
.markdown-content h3 {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content ul,
|
|
||||||
.markdown-content ol {
|
|
||||||
padding-left: 20px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content li {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content li:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 2px dashed var(--border-color);
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content br {
|
|
||||||
display: block;
|
|
||||||
content: "";
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content code {
|
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
|
||||||
background-color: rgba(128, 128, 128, 0.1);
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content pre {
|
|
||||||
background-color: rgba(128, 128, 128, 0.1);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content pre code {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source {
|
|
||||||
padding: 10px 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.7);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: right;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Markdown内容样式增强 */
|
|
||||||
.markdown-content blockquote {
|
|
||||||
border-left: 4px solid var(--primary-color);
|
|
||||||
padding: 10px 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.5);
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content ul,
|
|
||||||
.markdown-content ol {
|
|
||||||
padding-left: 20px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 移动设备响应式优化 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.release-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-assets {
|
|
||||||
padding: 12px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-item {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 12px 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-name {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-size {
|
|
||||||
margin-left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 5px 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.release {
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-item {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-icon {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-size {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content pre {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保日志页面页脚样式一致 */
|
|
||||||
.footer {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-text {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/* cyrillic-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'IBM Plex Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/ibm-plex-mono/-F63fjptAgt5VM-kVkqdyU8n1iIq129k.woff2) format('woff2');
|
|
||||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
||||||
}
|
|
||||||
/* cyrillic */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'IBM Plex Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/ibm-plex-mono/-F63fjptAgt5VM-kVkqdyU8n1isq129k.woff2) format('woff2');
|
|
||||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
||||||
}
|
|
||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'IBM Plex Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/ibm-plex-mono/-F63fjptAgt5VM-kVkqdyU8n1iAq129k.woff2) format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'IBM Plex Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/ibm-plex-mono/-F63fjptAgt5VM-kVkqdyU8n1iEq129k.woff2) format('woff2');
|
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'IBM Plex Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/ibm-plex-mono/-F63fjptAgt5VM-kVkqdyU8n1i8q1w.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/* vietnamese */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Space Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/space-mono/i7dPIFZifjKcF5UAWdDRYE58RWq7.woff2) format('woff2');
|
|
||||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
||||||
}
|
|
||||||
/* latin-ext */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Space Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/space-mono/i7dPIFZifjKcF5UAWdDRYE98RWq7.woff2) format('woff2');
|
|
||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
||||||
}
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Space Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(../font/space-mono/i7dPIFZifjKcF5UAWdDRYEF8RQ.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
@@ -1,717 +0,0 @@
|
|||||||
@import url('./space-mono-font.css');
|
|
||||||
@import url('./ibm-plex-mono-font.css');
|
|
||||||
|
|
||||||
/* 浅色主题 */
|
|
||||||
:root {
|
|
||||||
--bg-color: #fefefe;
|
|
||||||
--text-color: #000000;
|
|
||||||
--primary-color: #F08080;
|
|
||||||
--primary-color-rgb: 240, 128, 128;
|
|
||||||
--secondary-color: #ff006e;
|
|
||||||
--accent-color: #073B4C;
|
|
||||||
--card-bg: #ffffff;
|
|
||||||
--card-bg-rgb: 255, 255, 255;
|
|
||||||
--border-color: #000000;
|
|
||||||
--light-bg: #f0f0f0;
|
|
||||||
--light-bg-rgb: 240, 240, 240;
|
|
||||||
--shadow-color: rgba(240, 128, 128, 0.5);
|
|
||||||
--success-color: #27c93f;
|
|
||||||
--warning-color: #FFD166;
|
|
||||||
--error-color: #ff006e;
|
|
||||||
--info-color: #118ab2;
|
|
||||||
--code-bg: #ffffff;
|
|
||||||
--code-bg-rgb: 255, 255, 255;
|
|
||||||
--preview-header-bg: #f0f0f0;
|
|
||||||
--preview-header-bg-rgb: 240, 240, 240;
|
|
||||||
--grid-color-1: rgba(0, 0, 0, 0.08);
|
|
||||||
--grid-color-2: rgba(0, 0, 0, 0.05);
|
|
||||||
--header-title-color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗色主题变量 */
|
|
||||||
.theme-dark {
|
|
||||||
--bg-color: #121212;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--primary-color: #F08080;
|
|
||||||
--primary-color-rgb: 240, 128, 128;
|
|
||||||
--secondary-color: #ff006e;
|
|
||||||
--accent-color: #118ab2;
|
|
||||||
--card-bg: #1e1e1e;
|
|
||||||
--card-bg-rgb: 30, 30, 30;
|
|
||||||
--border-color: #ffffff;
|
|
||||||
--light-bg: #2a2a2a;
|
|
||||||
--light-bg-rgb: 42, 42, 42;
|
|
||||||
--shadow-color: rgba(240, 128, 128, 0.5);
|
|
||||||
--success-color: #27c93f;
|
|
||||||
--warning-color: #FFD166;
|
|
||||||
--error-color: #ff006e;
|
|
||||||
--info-color: #118ab2;
|
|
||||||
--code-bg: #1e1e1e;
|
|
||||||
--code-bg-rgb: 30, 30, 30;
|
|
||||||
--preview-header-bg: #252526;
|
|
||||||
--preview-header-bg-rgb: 37, 37, 38;
|
|
||||||
--grid-color-1: rgba(255, 255, 255, 0.08);
|
|
||||||
--grid-color-2: rgba(255, 255, 255, 0.05);
|
|
||||||
--header-title-color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主题切换和语言切换的过渡效果 */
|
|
||||||
.theme-transition,
|
|
||||||
.theme-transition *,
|
|
||||||
.lang-transition,
|
|
||||||
.lang-transition * {
|
|
||||||
transition: all 0.3s ease !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes gridMove {
|
|
||||||
0% {
|
|
||||||
background-position: 0px 0px, 0px 0px, 0px 0px, 0px 0px;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 80px 80px, 80px 80px, 20px 20px, 20px 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
background-image:
|
|
||||||
linear-gradient(var(--grid-color-1) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, var(--grid-color-1) 1px, transparent 1px),
|
|
||||||
linear-gradient(var(--grid-color-2) 0.5px, transparent 0.5px),
|
|
||||||
linear-gradient(90deg, var(--grid-color-2) 0.5px, transparent 0.5px);
|
|
||||||
background-size: 80px 80px, 80px 80px, 20px 20px, 20px 20px;
|
|
||||||
background-position: center;
|
|
||||||
animation: gridMove 40s linear infinite;
|
|
||||||
font-family: 'Space Mono', monospace;
|
|
||||||
color: var(--text-color);
|
|
||||||
line-height: 1.6;
|
|
||||||
padding: 20px;
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 卡片容器 */
|
|
||||||
.card {
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
background-image:
|
|
||||||
linear-gradient(var(--grid-color-1) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, var(--grid-color-1) 1px, transparent 1px),
|
|
||||||
linear-gradient(var(--grid-color-2) 0.5px, transparent 0.5px),
|
|
||||||
linear-gradient(90deg, var(--grid-color-2) 0.5px, transparent 0.5px);
|
|
||||||
background-size: 80px 80px, 80px 80px, 20px 20px, 20px 20px;
|
|
||||||
background-position: center;
|
|
||||||
border: 4px solid var(--border-color);
|
|
||||||
box-shadow: 12px 12px 0 var(--shadow-color);
|
|
||||||
margin-bottom: 40px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 16px 16px 0 var(--shadow-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 卡片头部 */
|
|
||||||
.card-header {
|
|
||||||
background-color: rgba(var(--primary-color-rgb), 0.9);
|
|
||||||
border-bottom: 4px solid var(--border-color);
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--header-title-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: var(--secondary-color);
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
border: 3px solid var(--border-color);
|
|
||||||
box-shadow: 4px 4px 0 var(--shadow-color);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: 'Space Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background: var(--card-bg);
|
|
||||||
color: var(--primary-color);
|
|
||||||
border: 3px solid var(--primary-color);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--light-bg);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: var(--card-bg);
|
|
||||||
color: var(--primary-color);
|
|
||||||
border: 3px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 卡片内容 */
|
|
||||||
.card-content {
|
|
||||||
padding: 30px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
background-color: rgba(var(--card-bg-rgb), 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logo区域 */
|
|
||||||
.logo-container {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-frame {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 4px solid var(--border-color);
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-image {
|
|
||||||
width: 130px;
|
|
||||||
height: 130px;
|
|
||||||
object-fit: contain;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-text {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagline {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 10px 0 0;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 介绍区域 */
|
|
||||||
.intro-box {
|
|
||||||
border: 2px dashed var(--border-color);
|
|
||||||
padding: 20px;
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.7);
|
|
||||||
margin-bottom: 30px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro-text {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮组 */
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 特性网格 */
|
|
||||||
.features-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
gap: 30px;
|
|
||||||
margin: 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 特性卡片 */
|
|
||||||
.feature-card {
|
|
||||||
background-color: rgba(var(--card-bg-rgb), 0.8);
|
|
||||||
border: 3px solid var(--border-color);
|
|
||||||
box-shadow: 5px 5px 0 var(--shadow-color);
|
|
||||||
padding: 20px;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card:hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 7px 7px 0 var(--shadow-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览区域 */
|
|
||||||
.preview-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px;
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.preview-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览窗口 */
|
|
||||||
.preview-window {
|
|
||||||
border: 3px solid var(--border-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 10px;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 300px;
|
|
||||||
background-color: rgba(var(--card-bg-rgb), 0.7);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: 5px 5px 0 var(--shadow-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览头部 */
|
|
||||||
.preview-header {
|
|
||||||
background-color: rgba(var(--preview-header-bg-rgb), 0.9);
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 2px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-btn {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-btn:nth-child(1) {
|
|
||||||
background-color: #ff5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-btn:nth-child(2) {
|
|
||||||
background-color: #ffbd2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-btn:nth-child(3) {
|
|
||||||
background-color: #27c93f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-title {
|
|
||||||
font-size: 13px;
|
|
||||||
opacity: 0.8;
|
|
||||||
color: var(--text-color);
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览内容 */
|
|
||||||
.preview-content {
|
|
||||||
padding: 15px;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: auto;
|
|
||||||
background-color: rgba(var(--code-bg-rgb), 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码块容器 */
|
|
||||||
.code-block-wrapper {
|
|
||||||
background-color: rgba(var(--code-bg-rgb), 0.8);
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 块头部 */
|
|
||||||
.block-header {
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.8);
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 2px solid var(--border-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-language {
|
|
||||||
color: rgba(128, 128, 128, 0.8);
|
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-language::before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
margin-right: 5px;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23888'%3E%3Cpath d='M9.7,16.7L5.3,12.3C4.9,11.9 4.9,11.1 5.3,10.7C5.7,10.3 6.3,10.3 6.7,10.7L10.5,14.5L17.3,7.7C17.7,7.3 18.3,7.3 18.7,7.7C19.1,8.1 19.1,8.7 18.7,9.1L11.3,16.7C10.9,17.1 10.1,17.1 9.7,16.7Z'/%3E%3C/svg%3E");
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block {
|
|
||||||
font-family: 'IBM Plex Mono', monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre;
|
|
||||||
tab-size: 4;
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .code-block-wrapper {
|
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .block-header {
|
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .block-language {
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .block-language::before {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23aaa'%3E%3Cpath d='M9.7,16.7L5.3,12.3C4.9,11.9 4.9,11.1 5.3,10.7C5.7,10.3 6.3,10.3 6.7,10.7L10.5,14.5L17.3,7.7C17.7,7.3 18.3,7.3 18.7,7.7C19.1,8.1 19.1,8.7 18.7,9.1L11.3,16.7C10.9,17.1 10.1,17.1 9.7,16.7Z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .code-block {
|
|
||||||
color: #d4d4d4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码高亮 */
|
|
||||||
.theme-dark .keyword { color: #c586c0; }
|
|
||||||
.theme-dark .function { color: #dcdcaa; }
|
|
||||||
.theme-dark .variable { color: #9cdcfe; }
|
|
||||||
.theme-dark .string { color: #ce9178; }
|
|
||||||
.theme-dark .comment { color: #6a9955; }
|
|
||||||
.theme-dark .class { color: #4ec9b0; }
|
|
||||||
.theme-dark .parameter { color: #9cdcfe; }
|
|
||||||
.theme-dark .built-in { color: #4ec9b0; }
|
|
||||||
|
|
||||||
/* 浅色主题代码高亮 */
|
|
||||||
.keyword { color: #af00db; }
|
|
||||||
.function { color: #795e26; }
|
|
||||||
.variable { color: #001080; }
|
|
||||||
.string { color: #a31515; }
|
|
||||||
.comment { color: #008000; }
|
|
||||||
.class { color: #267f99; }
|
|
||||||
.parameter { color: #001080; }
|
|
||||||
.built-in { color: #267f99; }
|
|
||||||
|
|
||||||
.preview-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
border: none;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .light-theme-img {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .dark-theme-img {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:not(.theme-dark) .dark-theme-img {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:not(.theme-dark) .light-theme-img {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 技术栈列表 */
|
|
||||||
.tech-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 技术栈列表 */
|
|
||||||
.tech-item {
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
background-color: rgba(var(--light-bg-rgb), 0.7);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-icon {
|
|
||||||
margin-right: 15px;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
font-size: 20px;
|
|
||||||
width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-name {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 页脚 */
|
|
||||||
.footer {
|
|
||||||
border-top: 2px solid var(--border-color);
|
|
||||||
padding: 20px 0;
|
|
||||||
margin-top: 40px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
background-color: transparent;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-text {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-link {
|
|
||||||
color: var(--secondary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-link:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.button-group {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.features-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.card-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-controls {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-frame {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-image {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 针对移动设备的响应式优化 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-controls {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览区域优化 */
|
|
||||||
.preview-content {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-header {
|
|
||||||
padding: 6px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 日志界面导航链接优化 */
|
|
||||||
.nav-links {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links .btn {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
/* 特性卡片优化 */
|
|
||||||
.features-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 预览窗口优化 */
|
|
||||||
.preview-container {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-window {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 技术栈列表小屏幕优化 */
|
|
||||||
.tech-item {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-desc {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 40px; /* 图标宽度+右边距 */
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 日志界面资源列表项优化 */
|
|
||||||
.asset-item {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-name {
|
|
||||||
width: 100%;
|
|
||||||
word-break: break-all;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-size {
|
|
||||||
order: 2;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
order: 3;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 页脚链接优化 */
|
|
||||||
.footer {
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
|
||||||
margin-top: 15px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 49 KiB |
256
docs/index.html
@@ -1,256 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>voidraft - An elegant text snippet recording tool designed for developers.</title>
|
|
||||||
<meta name="description" content="voidraft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
|
||||||
<meta name="keywords" content="text editor, code snippets, developer tools, syntax highlighting, code formatting, multi-language, voidraft">
|
|
||||||
<meta name="author" content="voidraft Team">
|
|
||||||
<meta name="robots" content="index, follow">
|
|
||||||
<link rel="canonical" href="https://landaiqing.github.io/voidraft/">
|
|
||||||
|
|
||||||
<!-- Internationalization / hreflang -->
|
|
||||||
<link rel="alternate" hreflang="en" href="https://landaiqing.github.io/voidraft/">
|
|
||||||
<link rel="alternate" hreflang="zh" href="https://landaiqing.github.io/voidraft/?lang=zh">
|
|
||||||
<link rel="alternate" hreflang="x-default" href="https://landaiqing.github.io/voidraft/">
|
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:url" content="https://landaiqing.github.io/voidraft/">
|
|
||||||
<meta property="og:title" content="voidraft - An elegant text snippet recording tool designed for developers">
|
|
||||||
<meta property="og:description" content="voidraft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
|
||||||
<meta property="og:image" content="https://landaiqing.github.io/voidraft/img/screenshot-dark.png">
|
|
||||||
<meta property="og:site_name" content="voidraft">
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
|
||||||
<meta property="twitter:card" content="summary_large_image">
|
|
||||||
<meta property="twitter:url" content="https://landaiqing.github.io/voidraft/">
|
|
||||||
<meta property="twitter:title" content="voidraft - An elegant text snippet recording tool designed for developers">
|
|
||||||
<meta property="twitter:description" content="voidraft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
|
||||||
<meta property="twitter:image" content="https://landaiqing.github.io/voidraft/img/screenshot-dark.png">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="./css/styles.css">
|
|
||||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
||||||
|
|
||||||
<!-- Structured Data -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "SoftwareApplication",
|
|
||||||
"name": "voidraft",
|
|
||||||
"description": "An elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.",
|
|
||||||
"url": "https://landaiqing.github.io/voidraft/",
|
|
||||||
"downloadUrl": "https://github.com/landaiqing/voidraft/releases",
|
|
||||||
"author": {
|
|
||||||
"@type": "Organization",
|
|
||||||
"name": "voidraft"
|
|
||||||
},
|
|
||||||
"operatingSystem": ["Windows", "macOS", "Linux"],
|
|
||||||
"applicationCategory": "DeveloperApplication",
|
|
||||||
"offers": {
|
|
||||||
"@type": "Offer",
|
|
||||||
"price": "0",
|
|
||||||
"priceCurrency": "USD"
|
|
||||||
},
|
|
||||||
"screenshot": "https://landaiqing.github.io/voidraft/img/screenshot-dark.png",
|
|
||||||
"softwareVersion": "Latest",
|
|
||||||
"programmingLanguage": ["Go", "TypeScript", "Vue.js"],
|
|
||||||
"codeRepository": "https://github.com/landaiqing/voidraft"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body class="theme-dark">
|
|
||||||
<div class="container">
|
|
||||||
<!-- 主卡片 -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h1 class="card-title">voidraft</h1>
|
|
||||||
<div class="card-controls">
|
|
||||||
<button id="theme-toggle" class="btn btn-secondary" title="切换主题">
|
|
||||||
<i class="fas fa-sun"></i> <span data-en="Theme" data-zh="主题">Theme</span>
|
|
||||||
</button>
|
|
||||||
<button id="lang-toggle" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-language"></i> 中/EN
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-content">
|
|
||||||
<!-- Logo和介绍 -->
|
|
||||||
<div class="logo-container">
|
|
||||||
<div class="logo-frame">
|
|
||||||
<img src="img/logo.png" alt="voidraft Logo" class="logo-image">
|
|
||||||
</div>
|
|
||||||
<h2 class="logo-text" data-en="voidraft" data-zh="voidraft">voidraft</h2>
|
|
||||||
<p class="tagline" data-en="An elegant text snippet recording tool" data-zh="优雅的文本片段记录工具">An elegant text snippet recording tool</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="intro-box">
|
|
||||||
<p class="intro-text" data-en="Designed for developers to record, organize, and manage various text snippets anytime, anywhere." data-zh="专为开发者打造,随时随地记录、整理和管理各种文本片段。">Designed for developers to record, organize, and manage various text snippets anytime, anywhere.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<a href="https://github.com/landaiqing/voidraft/releases" class="btn" data-en="Download" data-zh="下载">
|
|
||||||
<i class="fas fa-download"></i> Download
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft" class="btn btn-secondary" data-en="Source Code" data-zh="源代码">
|
|
||||||
<i class="fab fa-github"></i> Source Code
|
|
||||||
</a>
|
|
||||||
<a href="changelog.html" class="btn btn-secondary" data-en="Changelog" data-zh="更新日志">
|
|
||||||
<i class="fas fa-history"></i> Changelog
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 特性部分 -->
|
|
||||||
<h2 data-en="Core Features" data-zh="核心特性">Core Features</h2>
|
|
||||||
|
|
||||||
<div class="features-grid">
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-code"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Developer-Friendly" data-zh="开发者友好">Developer-Friendly</h3>
|
|
||||||
<p class="feature-desc" data-en="Multi-language code blocks with syntax highlighting for 30+ programming languages" data-zh="多语言代码块支持,为30+种编程语言提供语法高亮">Multi-language code blocks with syntax highlighting for 30+ programming languages</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-magic"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Code Formatting" data-zh="代码格式化">Code Formatting</h3>
|
|
||||||
<p class="feature-desc" data-en="Built-in Prettier support for one-click code beautification" data-zh="内置Prettier支持,一键美化代码">Built-in Prettier support for one-click code beautification</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-palette"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Custom Themes" data-zh="自定义主题">Custom Themes</h3>
|
|
||||||
<p class="feature-desc" data-en="Dark/Light themes with full customization options" data-zh="深色/浅色主题,支持完全自定义">Dark/Light themes with full customization options</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-clone"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Multi-Window" data-zh="多窗口支持">Multi-Window</h3>
|
|
||||||
<p class="feature-desc" data-en="Edit multiple documents simultaneously" data-zh="同时编辑多个文档">Edit multiple documents simultaneously</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-layer-group"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Block Editing" data-zh="块状编辑">Block Editing</h3>
|
|
||||||
<p class="feature-desc" data-en="Split content into independent code blocks with different language settings" data-zh="将内容分割为独立的代码块,每个块可设置不同语言">Split content into independent code blocks with different language settings</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">
|
|
||||||
<i class="fas fa-puzzle-piece"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="feature-title" data-en="Extensions" data-zh="丰富扩展">Extensions</h3>
|
|
||||||
<p class="feature-desc" data-en="Rainbow brackets, VSCode-style search, color picker, translation tool, and more" data-zh="彩虹括号、VSCode风格搜索、颜色选择器、翻译工具等多种扩展">Rainbow brackets, VSCode-style search, color picker, translation tool, and more</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 预览部分 -->
|
|
||||||
<h2 data-en="Preview" data-zh="预览">Preview</h2>
|
|
||||||
|
|
||||||
<div class="preview-container">
|
|
||||||
<div class="preview-window">
|
|
||||||
<div class="preview-header">
|
|
||||||
<div class="preview-controls">
|
|
||||||
<span class="preview-btn"></span>
|
|
||||||
<span class="preview-btn"></span>
|
|
||||||
<span class="preview-btn"></span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-title">voidraft</div>
|
|
||||||
</div>
|
|
||||||
<div class="preview-content">
|
|
||||||
<div class="code-block-wrapper">
|
|
||||||
<div class="block-header">
|
|
||||||
<div class="block-language">javascript</div>
|
|
||||||
</div>
|
|
||||||
<pre class="code-block">
|
|
||||||
<span class="keyword">function</span> <span class="function">createDocument</span>() {
|
|
||||||
<span class="keyword">const</span> <span class="variable">doc</span> = <span class="keyword">new</span> <span class="class">Document</span>();
|
|
||||||
|
|
||||||
<span class="variable">doc</span>.<span class="function">addCodeBlock</span>(<span class="string">'javascript'</span>, <span class="string">`
|
|
||||||
<span class="keyword">function</span> <span class="function">greeting</span>(<span class="parameter">name</span>) {
|
|
||||||
<span class="keyword">return</span> <span class="string">`Hello, </span>${<span class="parameter">name</span>}<span class="string">!`</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
<span class="built-in">console</span>.<span class="function">log</span>(<span class="function">greeting</span>(<span class="string">'World'</span>));
|
|
||||||
`</span>);
|
|
||||||
|
|
||||||
<span class="keyword">return</span> <span class="variable">doc</span>;
|
|
||||||
}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="code-block-wrapper" style="margin-top: 10px;">
|
|
||||||
<div class="block-header">
|
|
||||||
<div class="block-language">text</div>
|
|
||||||
</div>
|
|
||||||
<pre class="code-block">
|
|
||||||
<span class="comment">// voidraft - An elegant text snippet recording tool</span>
|
|
||||||
<span class="comment">// Multi-language support | Code formatting | Custom themes</span>
|
|
||||||
<span class="comment">// A modern text editor designed for developers</span></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="preview-window">
|
|
||||||
<img src="img/screenshot-dark.png" alt="voidraft 界面预览" class="preview-image dark-theme-img">
|
|
||||||
<img src="img/screenshot-light.png" alt="voidraft 界面预览" class="preview-image light-theme-img" style="display: none;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 技术栈部分 -->
|
|
||||||
<h2 data-en="Technical Stack" data-zh="技术栈">Technical Stack</h2>
|
|
||||||
|
|
||||||
<ul class="tech-list">
|
|
||||||
<li class="tech-item">
|
|
||||||
<div class="tech-icon"><i class="fas fa-desktop"></i></div>
|
|
||||||
<span class="tech-name">Wails3</span>
|
|
||||||
<span class="tech-desc" data-en="Cross-platform desktop application framework" data-zh="跨平台桌面应用框架">Cross-platform desktop application framework</span>
|
|
||||||
</li>
|
|
||||||
<li class="tech-item">
|
|
||||||
<div class="tech-icon"><i class="fas fa-cogs"></i></div>
|
|
||||||
<span class="tech-name">Go 1.21+</span>
|
|
||||||
<span class="tech-desc" data-en="Fast and efficient backend language" data-zh="快速高效的后端语言">Fast and efficient backend language</span>
|
|
||||||
</li>
|
|
||||||
<li class="tech-item">
|
|
||||||
<div class="tech-icon"><i class="fab fa-vuejs"></i></div>
|
|
||||||
<span class="tech-name">Vue 3 + TypeScript</span>
|
|
||||||
<span class="tech-desc" data-en="Modern frontend framework" data-zh="现代化前端框架">Modern frontend framework</span>
|
|
||||||
</li>
|
|
||||||
<li class="tech-item">
|
|
||||||
<div class="tech-icon"><i class="fas fa-edit"></i></div>
|
|
||||||
<span class="tech-name">CodeMirror 6</span>
|
|
||||||
<span class="tech-desc" data-en="Modern code editor with extension support" data-zh="支持扩展的现代化代码编辑器">Modern code editor with extension support</span>
|
|
||||||
</li>
|
|
||||||
<li class="tech-item">
|
|
||||||
<div class="tech-icon"><i class="fas fa-database"></i></div>
|
|
||||||
<span class="tech-name">SQLite</span>
|
|
||||||
<span class="tech-desc" data-en="Lightweight database for document storage" data-zh="轻量级文档存储数据库">Lightweight database for document storage</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 页脚 -->
|
|
||||||
<footer class="footer">
|
|
||||||
<p class="footer-text" data-en="© 2025 voidraft - An elegant text snippet recording tool designed for developers" data-zh="© 2025 voidraft - 专为开发者打造的优雅文本片段记录工具">© 2025 voidraft - An elegant text snippet recording tool designed for developers</p>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a href="https://github.com/landaiqing/voidraft" target="_blank" class="footer-link">GitHub</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft/issues" target="_blank" class="footer-link" data-en="Issues" data-zh="问题反馈">Issues</a>
|
|
||||||
<a href="https://github.com/landaiqing/voidraft/releases" target="_blank" class="footer-link" data-en="Releases" data-zh="版本发布">Releases</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,705 +0,0 @@
|
|||||||
/**
|
|
||||||
* voidraft - Changelog Script
|
|
||||||
* 从GitHub API获取发布信息,支持Gitea备用源
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仓库配置类
|
|
||||||
*/
|
|
||||||
class RepositoryConfig {
|
|
||||||
constructor() {
|
|
||||||
this.repos = {
|
|
||||||
github: {
|
|
||||||
owner: 'landaiqing',
|
|
||||||
name: 'voidraft',
|
|
||||||
apiUrl: 'https://api.github.com/repos/landaiqing/voidraft/releases',
|
|
||||||
releasesUrl: 'https://github.com/landaiqing/voidraft/releases'
|
|
||||||
},
|
|
||||||
gitea: {
|
|
||||||
owner: 'landaiqing',
|
|
||||||
name: 'voidraft',
|
|
||||||
domain: 'git.landaiqing.cn',
|
|
||||||
apiUrl: 'https://git.landaiqing.cn/api/v1/repos/landaiqing/voidraft/releases',
|
|
||||||
releasesUrl: 'https://git.landaiqing.cn/landaiqing/voidraft/releases'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取仓库配置
|
|
||||||
* @param {string} source - 'github' 或 'gitea'
|
|
||||||
*/
|
|
||||||
getRepo(source) {
|
|
||||||
return this.repos[source];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有仓库配置
|
|
||||||
*/
|
|
||||||
getAllRepos() {
|
|
||||||
return this.repos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 国际化消息管理类
|
|
||||||
*/
|
|
||||||
class I18nMessages {
|
|
||||||
constructor() {
|
|
||||||
this.messages = {
|
|
||||||
loading: {
|
|
||||||
en: 'Loading releases...',
|
|
||||||
zh: '正在加载版本信息...'
|
|
||||||
},
|
|
||||||
noReleases: {
|
|
||||||
en: 'No release information found',
|
|
||||||
zh: '没有找到版本发布信息'
|
|
||||||
},
|
|
||||||
fetchError: {
|
|
||||||
en: 'Failed to load release information. Please try again later.',
|
|
||||||
zh: '无法获取版本信息,请稍后再试'
|
|
||||||
},
|
|
||||||
githubApiError: {
|
|
||||||
en: 'GitHub API returned an error status: ',
|
|
||||||
zh: 'GitHub API返回错误状态: '
|
|
||||||
},
|
|
||||||
giteaApiError: {
|
|
||||||
en: 'Gitea API returned an error status: ',
|
|
||||||
zh: 'Gitea API返回错误状态: '
|
|
||||||
},
|
|
||||||
dataSource: {
|
|
||||||
en: 'Data source: ',
|
|
||||||
zh: '数据来源: '
|
|
||||||
},
|
|
||||||
downloads: {
|
|
||||||
en: 'Downloads',
|
|
||||||
zh: '下载资源'
|
|
||||||
},
|
|
||||||
download: {
|
|
||||||
en: 'Download',
|
|
||||||
zh: '下载'
|
|
||||||
},
|
|
||||||
preRelease: {
|
|
||||||
en: 'Pre-release',
|
|
||||||
zh: '预发布'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息
|
|
||||||
* @param {string} key - 消息键
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
getMessage(key, lang = 'en') {
|
|
||||||
return this.messages[key] && this.messages[key][lang] || this.messages[key]['en'] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前语言
|
|
||||||
*/
|
|
||||||
getCurrentLang() {
|
|
||||||
return window.currentLang || 'en';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API客户端类
|
|
||||||
*/
|
|
||||||
class APIClient {
|
|
||||||
constructor(repositoryConfig, i18nMessages) {
|
|
||||||
this.repositoryConfig = repositoryConfig;
|
|
||||||
this.i18nMessages = i18nMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从指定源获取发布信息
|
|
||||||
* @param {string} source - 'github' 或 'gitea'
|
|
||||||
*/
|
|
||||||
async fetchReleases(source) {
|
|
||||||
const repo = this.repositoryConfig.getRepo(source);
|
|
||||||
const errorMessageKey = source === 'github' ? 'githubApiError' : 'giteaApiError';
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
headers: { 'Accept': 'application/json' }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (source === 'github') {
|
|
||||||
return this.fetchFromGitHub(repo, options, errorMessageKey);
|
|
||||||
} else {
|
|
||||||
return this.fetchFromGitea(repo, options, errorMessageKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从GitHub获取数据
|
|
||||||
* @param {Object} repo - 仓库配置
|
|
||||||
* @param {Object} options - 请求选项
|
|
||||||
* @param {string} errorMessageKey - 错误消息键
|
|
||||||
*/
|
|
||||||
async fetchFromGitHub(repo, options, errorMessageKey) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
||||||
|
|
||||||
options.signal = controller.signal;
|
|
||||||
options.headers['Accept'] = 'application/vnd.github.v3+json';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(repo.apiUrl, options);
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const releases = await response.json();
|
|
||||||
|
|
||||||
if (!releases || releases.length === 0) {
|
|
||||||
throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases;
|
|
||||||
} catch (error) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从Gitea获取数据
|
|
||||||
* @param {Object} repo - 仓库配置
|
|
||||||
* @param {Object} options - 请求选项
|
|
||||||
* @param {string} errorMessageKey - 错误消息键
|
|
||||||
*/
|
|
||||||
async fetchFromGitea(repo, options, errorMessageKey) {
|
|
||||||
const response = await fetch(repo.apiUrl, options);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const releases = await response.json();
|
|
||||||
|
|
||||||
if (!releases || releases.length === 0) {
|
|
||||||
throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UI管理类
|
|
||||||
*/
|
|
||||||
class UIManager {
|
|
||||||
constructor(i18nMessages) {
|
|
||||||
this.i18nMessages = i18nMessages;
|
|
||||||
this.elements = {
|
|
||||||
loading: document.getElementById('loading'),
|
|
||||||
changelog: document.getElementById('changelog'),
|
|
||||||
error: document.getElementById('error-message')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示加载状态
|
|
||||||
*/
|
|
||||||
showLoading() {
|
|
||||||
this.elements.loading.style.display = 'block';
|
|
||||||
this.elements.error.style.display = 'none';
|
|
||||||
this.elements.changelog.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 隐藏加载状态
|
|
||||||
*/
|
|
||||||
hideLoading() {
|
|
||||||
this.elements.loading.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示错误消息
|
|
||||||
* @param {string} message - 错误消息
|
|
||||||
*/
|
|
||||||
showError(message) {
|
|
||||||
const errorMessageElement = this.elements.error.querySelector('p');
|
|
||||||
if (errorMessageElement) {
|
|
||||||
errorMessageElement.textContent = message;
|
|
||||||
} else {
|
|
||||||
this.elements.error.textContent = message;
|
|
||||||
}
|
|
||||||
this.elements.error.style.display = 'block';
|
|
||||||
this.hideLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示发布信息
|
|
||||||
* @param {Array} releases - 发布信息数组
|
|
||||||
* @param {string} source - 数据源
|
|
||||||
*/
|
|
||||||
displayReleases(releases, source) {
|
|
||||||
this.hideLoading();
|
|
||||||
|
|
||||||
// 清除现有内容
|
|
||||||
this.elements.changelog.innerHTML = '';
|
|
||||||
|
|
||||||
// 创建数据源元素
|
|
||||||
const sourceElement = this.createSourceElement(source);
|
|
||||||
this.elements.changelog.appendChild(sourceElement);
|
|
||||||
|
|
||||||
// 创建发布信息元素
|
|
||||||
releases.forEach(release => {
|
|
||||||
const releaseElement = this.createReleaseElement(release, source);
|
|
||||||
this.elements.changelog.appendChild(releaseElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.elements.changelog.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建数据源元素
|
|
||||||
* @param {string} source - 数据源
|
|
||||||
*/
|
|
||||||
createSourceElement(source) {
|
|
||||||
const sourceElement = document.createElement('div');
|
|
||||||
sourceElement.className = 'data-source';
|
|
||||||
|
|
||||||
// 创建带有国际化支持的源标签
|
|
||||||
const sourceLabel = document.createElement('span');
|
|
||||||
sourceLabel.setAttribute('data-en', this.i18nMessages.getMessage('dataSource', 'en'));
|
|
||||||
sourceLabel.setAttribute('data-zh', this.i18nMessages.getMessage('dataSource', 'zh'));
|
|
||||||
sourceLabel.textContent = this.i18nMessages.getMessage('dataSource', this.i18nMessages.getCurrentLang());
|
|
||||||
|
|
||||||
// 创建链接
|
|
||||||
const sourceLink = document.createElement('a');
|
|
||||||
const repositoryConfig = new RepositoryConfig();
|
|
||||||
sourceLink.href = repositoryConfig.getRepo(source).releasesUrl;
|
|
||||||
sourceLink.textContent = source === 'github' ? 'GitHub' : 'Gitea';
|
|
||||||
sourceLink.target = '_blank';
|
|
||||||
|
|
||||||
// 组装元素
|
|
||||||
sourceElement.appendChild(sourceLabel);
|
|
||||||
sourceElement.appendChild(sourceLink);
|
|
||||||
|
|
||||||
return sourceElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建发布信息元素
|
|
||||||
* @param {Object} release - 发布信息对象
|
|
||||||
* @param {string} source - 数据源
|
|
||||||
*/
|
|
||||||
createReleaseElement(release, source) {
|
|
||||||
const releaseElement = document.createElement('div');
|
|
||||||
releaseElement.className = 'release';
|
|
||||||
|
|
||||||
// 格式化发布日期
|
|
||||||
const releaseDate = new Date(release.published_at || release.created_at);
|
|
||||||
const formattedDate = DateFormatter.formatDate(releaseDate);
|
|
||||||
|
|
||||||
// 创建头部
|
|
||||||
const headerElement = this.createReleaseHeader(release, formattedDate);
|
|
||||||
releaseElement.appendChild(headerElement);
|
|
||||||
|
|
||||||
// 添加发布说明
|
|
||||||
if (release.body) {
|
|
||||||
const descriptionElement = document.createElement('div');
|
|
||||||
descriptionElement.className = 'release-description markdown-content';
|
|
||||||
descriptionElement.innerHTML = MarkdownParser.parseMarkdown(release.body);
|
|
||||||
releaseElement.appendChild(descriptionElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加下载资源
|
|
||||||
const assets = AssetManager.getAssetsFromRelease(release, source);
|
|
||||||
if (assets && assets.length > 0) {
|
|
||||||
const assetsElement = this.createAssetsElement(assets);
|
|
||||||
releaseElement.appendChild(assetsElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return releaseElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建发布信息头部
|
|
||||||
*/
|
|
||||||
createReleaseHeader(release, formattedDate) {
|
|
||||||
const headerElement = document.createElement('div');
|
|
||||||
headerElement.className = 'release-header';
|
|
||||||
|
|
||||||
// 版本元素
|
|
||||||
const versionElement = document.createElement('div');
|
|
||||||
versionElement.className = 'release-version';
|
|
||||||
|
|
||||||
// 版本文本
|
|
||||||
const versionText = document.createElement('span');
|
|
||||||
versionText.textContent = release.name || release.tag_name;
|
|
||||||
versionElement.appendChild(versionText);
|
|
||||||
|
|
||||||
// 预发布标记
|
|
||||||
if (release.prerelease) {
|
|
||||||
const preReleaseTag = document.createElement('span');
|
|
||||||
preReleaseTag.className = 'release-badge pre-release';
|
|
||||||
preReleaseTag.setAttribute('data-en', this.i18nMessages.getMessage('preRelease', 'en'));
|
|
||||||
preReleaseTag.setAttribute('data-zh', this.i18nMessages.getMessage('preRelease', 'zh'));
|
|
||||||
preReleaseTag.textContent = this.i18nMessages.getMessage('preRelease', this.i18nMessages.getCurrentLang());
|
|
||||||
versionElement.appendChild(preReleaseTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 日期元素
|
|
||||||
const dateElement = document.createElement('div');
|
|
||||||
dateElement.className = 'release-date';
|
|
||||||
dateElement.textContent = formattedDate;
|
|
||||||
|
|
||||||
headerElement.appendChild(versionElement);
|
|
||||||
headerElement.appendChild(dateElement);
|
|
||||||
|
|
||||||
return headerElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建资源文件元素
|
|
||||||
* @param {Array} assets - 资源文件数组
|
|
||||||
*/
|
|
||||||
createAssetsElement(assets) {
|
|
||||||
const assetsElement = document.createElement('div');
|
|
||||||
assetsElement.className = 'release-assets';
|
|
||||||
|
|
||||||
// 资源标题
|
|
||||||
const assetsTitle = document.createElement('div');
|
|
||||||
assetsTitle.className = 'release-assets-title';
|
|
||||||
assetsTitle.setAttribute('data-en', this.i18nMessages.getMessage('downloads', 'en'));
|
|
||||||
assetsTitle.setAttribute('data-zh', this.i18nMessages.getMessage('downloads', 'zh'));
|
|
||||||
assetsTitle.textContent = this.i18nMessages.getMessage('downloads', this.i18nMessages.getCurrentLang());
|
|
||||||
|
|
||||||
// 资源列表
|
|
||||||
const assetList = document.createElement('ul');
|
|
||||||
assetList.className = 'asset-list';
|
|
||||||
|
|
||||||
// 添加每个资源
|
|
||||||
assets.forEach(asset => {
|
|
||||||
const assetItem = this.createAssetItem(asset);
|
|
||||||
assetList.appendChild(assetItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
assetsElement.appendChild(assetsTitle);
|
|
||||||
assetsElement.appendChild(assetList);
|
|
||||||
|
|
||||||
return assetsElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建资源文件项
|
|
||||||
* @param {Object} asset - 资源文件对象
|
|
||||||
*/
|
|
||||||
createAssetItem(asset) {
|
|
||||||
const assetItem = document.createElement('li');
|
|
||||||
assetItem.className = 'asset-item';
|
|
||||||
|
|
||||||
// 文件图标
|
|
||||||
const iconElement = document.createElement('i');
|
|
||||||
iconElement.className = `asset-icon fas fa-${FileIconHelper.getFileIcon(asset.name)}`;
|
|
||||||
|
|
||||||
// 文件名
|
|
||||||
const nameElement = document.createElement('span');
|
|
||||||
nameElement.className = 'asset-name';
|
|
||||||
nameElement.textContent = asset.name;
|
|
||||||
|
|
||||||
// 文件大小
|
|
||||||
const sizeElement = document.createElement('span');
|
|
||||||
sizeElement.className = 'asset-size';
|
|
||||||
sizeElement.textContent = FileSizeFormatter.formatFileSize(asset.size);
|
|
||||||
|
|
||||||
// 下载链接
|
|
||||||
const downloadLink = document.createElement('a');
|
|
||||||
downloadLink.className = 'download-btn';
|
|
||||||
downloadLink.href = asset.browser_download_url;
|
|
||||||
downloadLink.target = '_blank';
|
|
||||||
downloadLink.setAttribute('data-en', this.i18nMessages.getMessage('download', 'en'));
|
|
||||||
downloadLink.setAttribute('data-zh', this.i18nMessages.getMessage('download', 'zh'));
|
|
||||||
downloadLink.textContent = this.i18nMessages.getMessage('download', this.i18nMessages.getCurrentLang());
|
|
||||||
|
|
||||||
// 组装资源项
|
|
||||||
assetItem.appendChild(iconElement);
|
|
||||||
assetItem.appendChild(nameElement);
|
|
||||||
assetItem.appendChild(sizeElement);
|
|
||||||
assetItem.appendChild(downloadLink);
|
|
||||||
|
|
||||||
return assetItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 资源管理器类
|
|
||||||
*/
|
|
||||||
class AssetManager {
|
|
||||||
/**
|
|
||||||
* 从发布信息中获取资源文件
|
|
||||||
* @param {Object} release - 发布信息对象
|
|
||||||
* @param {string} source - 数据源
|
|
||||||
*/
|
|
||||||
static getAssetsFromRelease(release, source) {
|
|
||||||
let assets = [];
|
|
||||||
|
|
||||||
if (source === 'github') {
|
|
||||||
assets = release.assets || [];
|
|
||||||
} else { // Gitea
|
|
||||||
assets = release.assets || [];
|
|
||||||
// 检查Gitea特定的资源结构
|
|
||||||
if (!assets.length && release.attachments) {
|
|
||||||
assets = release.attachments.map(attachment => ({
|
|
||||||
name: attachment.name,
|
|
||||||
size: attachment.size,
|
|
||||||
browser_download_url: attachment.browser_download_url
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return assets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件图标助手类
|
|
||||||
*/
|
|
||||||
class FileIconHelper {
|
|
||||||
/**
|
|
||||||
* 根据文件扩展名获取图标
|
|
||||||
* @param {string} filename - 文件名
|
|
||||||
*/
|
|
||||||
static getFileIcon(filename) {
|
|
||||||
const extension = filename.split('.').pop().toLowerCase();
|
|
||||||
|
|
||||||
const iconMap = {
|
|
||||||
'exe': 'download',
|
|
||||||
'msi': 'download',
|
|
||||||
'dmg': 'download',
|
|
||||||
'pkg': 'download',
|
|
||||||
'deb': 'download',
|
|
||||||
'rpm': 'download',
|
|
||||||
'tar': 'file-archive',
|
|
||||||
'gz': 'file-archive',
|
|
||||||
'zip': 'file-archive',
|
|
||||||
'7z': 'file-archive',
|
|
||||||
'rar': 'file-archive',
|
|
||||||
'pdf': 'file-pdf',
|
|
||||||
'txt': 'file-alt',
|
|
||||||
'md': 'file-alt',
|
|
||||||
'json': 'file-code',
|
|
||||||
'xml': 'file-code',
|
|
||||||
'yml': 'file-code',
|
|
||||||
'yaml': 'file-code'
|
|
||||||
};
|
|
||||||
|
|
||||||
return iconMap[extension] || 'file';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件大小格式化器类
|
|
||||||
*/
|
|
||||||
class FileSizeFormatter {
|
|
||||||
/**
|
|
||||||
* 格式化文件大小
|
|
||||||
* @param {number} bytes - 字节数
|
|
||||||
*/
|
|
||||||
static formatFileSize(bytes) {
|
|
||||||
if (!bytes) return '';
|
|
||||||
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
||||||
|
|
||||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日期格式化器类
|
|
||||||
*/
|
|
||||||
class DateFormatter {
|
|
||||||
/**
|
|
||||||
* 格式化日期
|
|
||||||
* @param {Date} date - 日期对象
|
|
||||||
*/
|
|
||||||
static formatDate(date) {
|
|
||||||
const options = {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
};
|
|
||||||
|
|
||||||
const lang = window.currentLang || 'en';
|
|
||||||
const locale = lang === 'zh' ? 'zh-CN' : 'en-US';
|
|
||||||
|
|
||||||
return date.toLocaleDateString(locale, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Markdown解析器类
|
|
||||||
*/
|
|
||||||
class MarkdownParser {
|
|
||||||
/**
|
|
||||||
* 简单的Markdown解析
|
|
||||||
* @param {string} markdown - Markdown文本
|
|
||||||
*/
|
|
||||||
static parseMarkdown(markdown) {
|
|
||||||
if (!markdown) return '';
|
|
||||||
|
|
||||||
// 预处理:保留原始换行符,用特殊标记替换
|
|
||||||
const preservedLineBreaks = '___LINE_BREAK___';
|
|
||||||
markdown = markdown.replace(/\n/g, preservedLineBreaks);
|
|
||||||
|
|
||||||
// 引用块 - > text
|
|
||||||
markdown = markdown.replace(/>\s*(.*?)(?=>|$)/g, '<blockquote>$1</blockquote>');
|
|
||||||
markdown = markdown.replace(/>\s*(.*?)(?=>|$)/g, '<blockquote>$1</blockquote>');
|
|
||||||
|
|
||||||
// 链接 - [text](url)
|
|
||||||
markdown = markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
||||||
|
|
||||||
// 标题 - # Heading
|
|
||||||
markdown = markdown.replace(/^### (.*?)(?=___LINE_BREAK___|$)/gm, '<h3>$1</h3>');
|
|
||||||
markdown = markdown.replace(/^## (.*?)(?=___LINE_BREAK___|$)/gm, '<h2>$1</h2>');
|
|
||||||
markdown = markdown.replace(/^# (.*?)(?=___LINE_BREAK___|$)/gm, '<h1>$1</h1>');
|
|
||||||
|
|
||||||
// 粗体 - **text**
|
|
||||||
markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
||||||
|
|
||||||
// 斜体 - *text*
|
|
||||||
markdown = markdown.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
|
||||||
|
|
||||||
// 代码块 - ```code```
|
|
||||||
markdown = markdown.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
|
||||||
|
|
||||||
// 行内代码 - `code`
|
|
||||||
markdown = markdown.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
||||||
|
|
||||||
// 处理列表项
|
|
||||||
// 先将每个列表项转换为HTML
|
|
||||||
markdown = markdown.replace(/- (.*?)(?=___LINE_BREAK___- |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
|
||||||
markdown = markdown.replace(/\* (.*?)(?=___LINE_BREAK___\* |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
|
||||||
markdown = markdown.replace(/\d+\. (.*?)(?=___LINE_BREAK___\d+\. |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
|
||||||
|
|
||||||
// 然后将连续的列表项包装在ul或ol中
|
|
||||||
const listItemRegex = /<li>.*?<\/li>/g;
|
|
||||||
const listItems = markdown.match(listItemRegex) || [];
|
|
||||||
|
|
||||||
if (listItems.length > 0) {
|
|
||||||
// 将连续的列表项组合在一起
|
|
||||||
let lastIndex = 0;
|
|
||||||
let result = '';
|
|
||||||
let inList = false;
|
|
||||||
|
|
||||||
listItems.forEach(item => {
|
|
||||||
const itemIndex = markdown.indexOf(item, lastIndex);
|
|
||||||
|
|
||||||
// 添加列表项之前的内容
|
|
||||||
if (itemIndex > lastIndex) {
|
|
||||||
result += markdown.substring(lastIndex, itemIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果不在列表中,开始一个新列表
|
|
||||||
if (!inList) {
|
|
||||||
result += '<ul>';
|
|
||||||
inList = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加列表项
|
|
||||||
result += item;
|
|
||||||
|
|
||||||
// 更新lastIndex
|
|
||||||
lastIndex = itemIndex + item.length;
|
|
||||||
|
|
||||||
// 检查下一个内容是否是列表项
|
|
||||||
const nextItemIndex = markdown.indexOf('<li>', lastIndex);
|
|
||||||
if (nextItemIndex === -1 || nextItemIndex > lastIndex + 20) { // 如果下一个列表项不紧邻
|
|
||||||
result += '</ul>';
|
|
||||||
inList = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加剩余内容
|
|
||||||
if (lastIndex < markdown.length) {
|
|
||||||
result += markdown.substring(lastIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
markdown = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理水平分隔线
|
|
||||||
markdown = markdown.replace(/---/g, '<hr>');
|
|
||||||
|
|
||||||
// 恢复换行符
|
|
||||||
markdown = markdown.replace(/___LINE_BREAK___/g, '<br>');
|
|
||||||
|
|
||||||
// 处理段落
|
|
||||||
markdown = markdown.replace(/<br><br>/g, '</p><p>');
|
|
||||||
|
|
||||||
// 包装在段落标签中
|
|
||||||
if (!markdown.startsWith('<p>')) {
|
|
||||||
markdown = `<p>${markdown}</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return markdown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新日志主应用类
|
|
||||||
*/
|
|
||||||
class ChangelogApp {
|
|
||||||
constructor() {
|
|
||||||
this.repositoryConfig = new RepositoryConfig();
|
|
||||||
this.i18nMessages = new I18nMessages();
|
|
||||||
this.apiClient = new APIClient(this.repositoryConfig, this.i18nMessages);
|
|
||||||
this.uiManager = new UIManager(this.i18nMessages);
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化应用
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.uiManager.showLoading();
|
|
||||||
|
|
||||||
// 首先尝试GitHub API
|
|
||||||
this.apiClient.fetchReleases('github')
|
|
||||||
.then(releases => {
|
|
||||||
this.uiManager.displayReleases(releases, 'github');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// GitHub失败时尝试Gitea
|
|
||||||
return this.apiClient.fetchReleases('gitea')
|
|
||||||
.then(releases => {
|
|
||||||
this.uiManager.displayReleases(releases, 'gitea');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('获取发布信息失败:', error);
|
|
||||||
this.uiManager.showError(this.i18nMessages.getMessage('fetchError', this.i18nMessages.getCurrentLang()));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听语言变化事件
|
|
||||||
document.addEventListener('languageChanged', () => this.updateUI());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新UI元素(当语言变化时)
|
|
||||||
*/
|
|
||||||
updateUI() {
|
|
||||||
const elementsToUpdate = document.querySelectorAll('[data-en][data-zh]');
|
|
||||||
const currentLang = this.i18nMessages.getCurrentLang();
|
|
||||||
|
|
||||||
elementsToUpdate.forEach(element => {
|
|
||||||
const text = element.getAttribute(`data-${currentLang}`);
|
|
||||||
if (text) {
|
|
||||||
element.textContent = text;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当DOM加载完成时初始化应用
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
new ChangelogApp();
|
|
||||||
});
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
/**
|
|
||||||
* voidraft - Website Script
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题管理类
|
|
||||||
*/
|
|
||||||
class ThemeManager {
|
|
||||||
constructor() {
|
|
||||||
this.themeToggle = document.getElementById('theme-toggle');
|
|
||||||
this.currentTheme = this.getInitialTheme();
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取初始主题
|
|
||||||
*/
|
|
||||||
getInitialTheme() {
|
|
||||||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
return savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化主题管理器
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
if (!this.themeToggle) return;
|
|
||||||
|
|
||||||
this.setTheme(this.currentTheme);
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绑定事件
|
|
||||||
*/
|
|
||||||
bindEvents() {
|
|
||||||
this.themeToggle.addEventListener('click', () => {
|
|
||||||
this.toggleTheme();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换主题
|
|
||||||
*/
|
|
||||||
toggleTheme() {
|
|
||||||
document.body.classList.add('theme-transition');
|
|
||||||
|
|
||||||
const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
|
|
||||||
this.setTheme(newTheme);
|
|
||||||
this.saveTheme(newTheme);
|
|
||||||
|
|
||||||
setTimeout(() => document.body.classList.remove('theme-transition'), 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置主题
|
|
||||||
* @param {string} theme - 'dark' 或 'light'
|
|
||||||
*/
|
|
||||||
setTheme(theme) {
|
|
||||||
this.currentTheme = theme;
|
|
||||||
const isDark = theme === 'dark';
|
|
||||||
|
|
||||||
document.body.classList.toggle('theme-dark', isDark);
|
|
||||||
document.body.classList.toggle('theme-light', !isDark);
|
|
||||||
|
|
||||||
this.updateToggleIcon(isDark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新切换按钮图标
|
|
||||||
* @param {boolean} isDark - 是否为暗色主题
|
|
||||||
*/
|
|
||||||
updateToggleIcon(isDark) {
|
|
||||||
if (this.themeToggle) {
|
|
||||||
const icon = this.themeToggle.querySelector('i');
|
|
||||||
if (icon) {
|
|
||||||
icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存主题到本地存储
|
|
||||||
* @param {string} theme - 主题名称
|
|
||||||
*/
|
|
||||||
saveTheme(theme) {
|
|
||||||
localStorage.setItem('theme', theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 语言管理类
|
|
||||||
*/
|
|
||||||
class LanguageManager {
|
|
||||||
constructor() {
|
|
||||||
this.langToggle = document.getElementById('lang-toggle');
|
|
||||||
this.currentLang = this.getInitialLanguage();
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取初始语言
|
|
||||||
*/
|
|
||||||
getInitialLanguage() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const urlLang = urlParams.get('lang');
|
|
||||||
const savedLang = localStorage.getItem('lang');
|
|
||||||
const browserLang = navigator.language.startsWith('zh') ? 'zh' : 'en';
|
|
||||||
return urlLang || savedLang || browserLang;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化语言管理器
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
if (!this.langToggle) return;
|
|
||||||
|
|
||||||
window.currentLang = this.currentLang;
|
|
||||||
this.setLanguage(this.currentLang);
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绑定事件
|
|
||||||
*/
|
|
||||||
bindEvents() {
|
|
||||||
this.langToggle.addEventListener('click', () => {
|
|
||||||
this.toggleLanguage();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换语言
|
|
||||||
*/
|
|
||||||
toggleLanguage() {
|
|
||||||
document.body.classList.add('lang-transition');
|
|
||||||
|
|
||||||
const newLang = this.currentLang === 'zh' ? 'en' : 'zh';
|
|
||||||
this.setLanguage(newLang);
|
|
||||||
this.saveLanguage(newLang);
|
|
||||||
this.updateURL(newLang);
|
|
||||||
this.notifyLanguageChange(newLang);
|
|
||||||
|
|
||||||
setTimeout(() => document.body.classList.remove('lang-transition'), 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置页面语言
|
|
||||||
* @param {string} lang - 'zh' 或 'en'
|
|
||||||
*/
|
|
||||||
setLanguage(lang) {
|
|
||||||
this.currentLang = lang;
|
|
||||||
window.currentLang = lang;
|
|
||||||
|
|
||||||
this.updatePageElements(lang);
|
|
||||||
this.updateHTMLLang(lang);
|
|
||||||
this.updateToggleButton(lang);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新页面元素文本
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
updatePageElements(lang) {
|
|
||||||
document.querySelectorAll('[data-zh][data-en]').forEach(el => {
|
|
||||||
el.textContent = el.getAttribute(`data-${lang}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新HTML语言属性
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
updateHTMLLang(lang) {
|
|
||||||
document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新切换按钮文本
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
updateToggleButton(lang) {
|
|
||||||
if (this.langToggle) {
|
|
||||||
const text = lang === 'zh' ? 'EN/中' : '中/EN';
|
|
||||||
this.langToggle.innerHTML = `<i class="fas fa-language"></i> ${text}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存语言到本地存储
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
saveLanguage(lang) {
|
|
||||||
localStorage.setItem('lang', lang);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新URL参数
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
updateURL(lang) {
|
|
||||||
const newUrl = new URL(window.location);
|
|
||||||
if (lang === 'zh') {
|
|
||||||
newUrl.searchParams.set('lang', 'zh');
|
|
||||||
} else {
|
|
||||||
newUrl.searchParams.delete('lang');
|
|
||||||
}
|
|
||||||
window.history.replaceState({}, '', newUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通知语言变更
|
|
||||||
* @param {string} lang - 语言代码
|
|
||||||
*/
|
|
||||||
notifyLanguageChange(lang) {
|
|
||||||
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang } }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前语言
|
|
||||||
*/
|
|
||||||
getCurrentLanguage() {
|
|
||||||
return this.currentLang;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SEO管理类
|
|
||||||
*/
|
|
||||||
class SEOManager {
|
|
||||||
constructor(languageManager) {
|
|
||||||
this.languageManager = languageManager;
|
|
||||||
this.metaTexts = {
|
|
||||||
en: {
|
|
||||||
description: 'voidraft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.',
|
|
||||||
title: 'voidraft - An elegant text snippet recording tool designed for developers.',
|
|
||||||
ogTitle: 'voidraft - An elegant text snippet recording tool designed for developers'
|
|
||||||
},
|
|
||||||
zh: {
|
|
||||||
description: 'voidraft 是专为开发者打造的优雅文本片段记录工具。支持多语言代码块、语法高亮、代码格式化、自定义主题等功能。',
|
|
||||||
title: 'voidraft - 专为开发者打造的优雅文本片段记录工具',
|
|
||||||
ogTitle: 'voidraft - 专为开发者打造的优雅文本片段记录工具'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化SEO管理器
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.bindEvents();
|
|
||||||
this.updateMetaTags(this.languageManager.getCurrentLanguage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绑定事件
|
|
||||||
*/
|
|
||||||
bindEvents() {
|
|
||||||
window.addEventListener('languageChanged', (event) => {
|
|
||||||
this.updateMetaTags(event.detail.lang);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新SEO元标签
|
|
||||||
* @param {string} lang - 当前语言
|
|
||||||
*/
|
|
||||||
updateMetaTags(lang) {
|
|
||||||
const texts = this.metaTexts[lang];
|
|
||||||
|
|
||||||
this.updateMetaDescription(texts.description);
|
|
||||||
this.updateOpenGraphTags(texts.ogTitle, texts.description);
|
|
||||||
this.updateTwitterCardTags(texts.ogTitle, texts.description);
|
|
||||||
this.updatePageTitle(texts.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新meta描述
|
|
||||||
* @param {string} description - 描述文本
|
|
||||||
*/
|
|
||||||
updateMetaDescription(description) {
|
|
||||||
const metaDesc = document.querySelector('meta[name="description"]');
|
|
||||||
if (metaDesc) {
|
|
||||||
metaDesc.content = description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新Open Graph标签
|
|
||||||
* @param {string} title - 标题
|
|
||||||
* @param {string} description - 描述
|
|
||||||
*/
|
|
||||||
updateOpenGraphTags(title, description) {
|
|
||||||
const ogTitle = document.querySelector('meta[property="og:title"]');
|
|
||||||
const ogDesc = document.querySelector('meta[property="og:description"]');
|
|
||||||
|
|
||||||
if (ogTitle) ogTitle.content = title;
|
|
||||||
if (ogDesc) ogDesc.content = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新Twitter Card标签
|
|
||||||
* @param {string} title - 标题
|
|
||||||
* @param {string} description - 描述
|
|
||||||
*/
|
|
||||||
updateTwitterCardTags(title, description) {
|
|
||||||
const twitterTitle = document.querySelector('meta[property="twitter:title"]');
|
|
||||||
const twitterDesc = document.querySelector('meta[property="twitter:description"]');
|
|
||||||
|
|
||||||
if (twitterTitle) twitterTitle.content = title;
|
|
||||||
if (twitterDesc) twitterDesc.content = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新页面标题
|
|
||||||
* @param {string} title - 标题
|
|
||||||
*/
|
|
||||||
updatePageTitle(title) {
|
|
||||||
document.title = title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UI效果管理类
|
|
||||||
*/
|
|
||||||
class UIEffects {
|
|
||||||
constructor() {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化UI效果
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.initCardEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化卡片悬停效果
|
|
||||||
*/
|
|
||||||
initCardEffects() {
|
|
||||||
const cards = document.querySelectorAll('.feature-card');
|
|
||||||
|
|
||||||
cards.forEach(card => {
|
|
||||||
card.addEventListener('mouseenter', () => {
|
|
||||||
this.animateCardHover(card, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.addEventListener('mouseleave', () => {
|
|
||||||
this.animateCardHover(card, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 卡片悬停动画
|
|
||||||
* @param {Element} card - 卡片元素
|
|
||||||
* @param {boolean} isHover - 是否悬停
|
|
||||||
*/
|
|
||||||
animateCardHover(card, isHover) {
|
|
||||||
if (isHover) {
|
|
||||||
card.style.transform = 'translateY(-8px)';
|
|
||||||
card.style.boxShadow = '7px 7px 0 var(--shadow-color)';
|
|
||||||
} else {
|
|
||||||
card.style.transform = 'translateY(0)';
|
|
||||||
card.style.boxShadow = '5px 5px 0 var(--shadow-color)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* voidraft主应用类
|
|
||||||
*/
|
|
||||||
class voidraftApp {
|
|
||||||
constructor() {
|
|
||||||
this.themeManager = null;
|
|
||||||
this.languageManager = null;
|
|
||||||
this.seoManager = null;
|
|
||||||
this.uiEffects = null;
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化应用
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.initializeManagers();
|
|
||||||
this.showConsoleBranding();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化各个管理器
|
|
||||||
*/
|
|
||||||
initializeManagers() {
|
|
||||||
this.themeManager = new ThemeManager();
|
|
||||||
this.languageManager = new LanguageManager();
|
|
||||||
this.seoManager = new SEOManager(this.languageManager);
|
|
||||||
this.uiEffects = new UIEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示控制台品牌信息
|
|
||||||
*/
|
|
||||||
showConsoleBranding() {
|
|
||||||
console.log('%c voidraft', 'color: #ff006e; font-size: 20px; font-family: "Space Mono", monospace;');
|
|
||||||
console.log('%c An elegant text snippet recording tool designed for developers.', 'color: #073B4C; font-family: "Space Mono", monospace;');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主题管理器
|
|
||||||
*/
|
|
||||||
getThemeManager() {
|
|
||||||
return this.themeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取语言管理器
|
|
||||||
*/
|
|
||||||
getLanguageManager() {
|
|
||||||
return this.languageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SEO管理器
|
|
||||||
*/
|
|
||||||
getSEOManager() {
|
|
||||||
return this.seoManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取UI效果管理器
|
|
||||||
*/
|
|
||||||
getUIEffects() {
|
|
||||||
return this.uiEffects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当DOM加载完成时初始化应用
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.voidRaftApp = new voidraftApp();
|
|
||||||
});
|
|
||||||
@@ -5,303 +5,6 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Create as $Create} from "@wailsio/runtime";
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as slog$0 from "../../../../../../log/slog/models.js";
|
|
||||||
|
|
||||||
export class App {
|
|
||||||
/**
|
|
||||||
* Manager pattern for organized API
|
|
||||||
*/
|
|
||||||
"Window": WindowManager | null;
|
|
||||||
"ContextMenu": ContextMenuManager | null;
|
|
||||||
"KeyBinding": KeyBindingManager | null;
|
|
||||||
"Browser": BrowserManager | null;
|
|
||||||
"Env": EnvironmentManager | null;
|
|
||||||
"Dialog": DialogManager | null;
|
|
||||||
"Event": EventManager | null;
|
|
||||||
"Menu": MenuManager | null;
|
|
||||||
"Screen": ScreenManager | null;
|
|
||||||
"Clipboard": ClipboardManager | null;
|
|
||||||
"SystemTray": SystemTrayManager | null;
|
|
||||||
"Logger": slog$0.Logger | null;
|
|
||||||
|
|
||||||
/** Creates a new App instance. */
|
|
||||||
constructor($$source: Partial<App> = {}) {
|
|
||||||
if (!("Window" in $$source)) {
|
|
||||||
this["Window"] = null;
|
|
||||||
}
|
|
||||||
if (!("ContextMenu" in $$source)) {
|
|
||||||
this["ContextMenu"] = null;
|
|
||||||
}
|
|
||||||
if (!("KeyBinding" in $$source)) {
|
|
||||||
this["KeyBinding"] = null;
|
|
||||||
}
|
|
||||||
if (!("Browser" in $$source)) {
|
|
||||||
this["Browser"] = null;
|
|
||||||
}
|
|
||||||
if (!("Env" in $$source)) {
|
|
||||||
this["Env"] = null;
|
|
||||||
}
|
|
||||||
if (!("Dialog" in $$source)) {
|
|
||||||
this["Dialog"] = null;
|
|
||||||
}
|
|
||||||
if (!("Event" in $$source)) {
|
|
||||||
this["Event"] = null;
|
|
||||||
}
|
|
||||||
if (!("Menu" in $$source)) {
|
|
||||||
this["Menu"] = null;
|
|
||||||
}
|
|
||||||
if (!("Screen" in $$source)) {
|
|
||||||
this["Screen"] = null;
|
|
||||||
}
|
|
||||||
if (!("Clipboard" in $$source)) {
|
|
||||||
this["Clipboard"] = null;
|
|
||||||
}
|
|
||||||
if (!("SystemTray" in $$source)) {
|
|
||||||
this["SystemTray"] = null;
|
|
||||||
}
|
|
||||||
if (!("Logger" in $$source)) {
|
|
||||||
this["Logger"] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new App instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): App {
|
|
||||||
const $$createField0_0 = $$createType1;
|
|
||||||
const $$createField1_0 = $$createType3;
|
|
||||||
const $$createField2_0 = $$createType5;
|
|
||||||
const $$createField3_0 = $$createType7;
|
|
||||||
const $$createField4_0 = $$createType9;
|
|
||||||
const $$createField5_0 = $$createType11;
|
|
||||||
const $$createField6_0 = $$createType13;
|
|
||||||
const $$createField7_0 = $$createType15;
|
|
||||||
const $$createField8_0 = $$createType17;
|
|
||||||
const $$createField9_0 = $$createType19;
|
|
||||||
const $$createField10_0 = $$createType21;
|
|
||||||
const $$createField11_0 = $$createType23;
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
if ("Window" in $$parsedSource) {
|
|
||||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
|
||||||
}
|
|
||||||
if ("ContextMenu" in $$parsedSource) {
|
|
||||||
$$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]);
|
|
||||||
}
|
|
||||||
if ("KeyBinding" in $$parsedSource) {
|
|
||||||
$$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]);
|
|
||||||
}
|
|
||||||
if ("Browser" in $$parsedSource) {
|
|
||||||
$$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]);
|
|
||||||
}
|
|
||||||
if ("Env" in $$parsedSource) {
|
|
||||||
$$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]);
|
|
||||||
}
|
|
||||||
if ("Dialog" in $$parsedSource) {
|
|
||||||
$$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]);
|
|
||||||
}
|
|
||||||
if ("Event" in $$parsedSource) {
|
|
||||||
$$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]);
|
|
||||||
}
|
|
||||||
if ("Menu" in $$parsedSource) {
|
|
||||||
$$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]);
|
|
||||||
}
|
|
||||||
if ("Screen" in $$parsedSource) {
|
|
||||||
$$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]);
|
|
||||||
}
|
|
||||||
if ("Clipboard" in $$parsedSource) {
|
|
||||||
$$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]);
|
|
||||||
}
|
|
||||||
if ("SystemTray" in $$parsedSource) {
|
|
||||||
$$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]);
|
|
||||||
}
|
|
||||||
if ("Logger" in $$parsedSource) {
|
|
||||||
$$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]);
|
|
||||||
}
|
|
||||||
return new App($$parsedSource as Partial<App>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BrowserManager manages browser-related operations
|
|
||||||
*/
|
|
||||||
export class BrowserManager {
|
|
||||||
|
|
||||||
/** Creates a new BrowserManager instance. */
|
|
||||||
constructor($$source: Partial<BrowserManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new BrowserManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): BrowserManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new BrowserManager($$parsedSource as Partial<BrowserManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ClipboardManager manages clipboard operations
|
|
||||||
*/
|
|
||||||
export class ClipboardManager {
|
|
||||||
|
|
||||||
/** Creates a new ClipboardManager instance. */
|
|
||||||
constructor($$source: Partial<ClipboardManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ClipboardManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): ClipboardManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new ClipboardManager($$parsedSource as Partial<ClipboardManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ContextMenuManager manages all context menu operations
|
|
||||||
*/
|
|
||||||
export class ContextMenuManager {
|
|
||||||
|
|
||||||
/** Creates a new ContextMenuManager instance. */
|
|
||||||
constructor($$source: Partial<ContextMenuManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ContextMenuManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): ContextMenuManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new ContextMenuManager($$parsedSource as Partial<ContextMenuManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DialogManager manages dialog-related operations
|
|
||||||
*/
|
|
||||||
export class DialogManager {
|
|
||||||
|
|
||||||
/** Creates a new DialogManager instance. */
|
|
||||||
constructor($$source: Partial<DialogManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new DialogManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): DialogManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new DialogManager($$parsedSource as Partial<DialogManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EnvironmentManager manages environment-related operations
|
|
||||||
*/
|
|
||||||
export class EnvironmentManager {
|
|
||||||
|
|
||||||
/** Creates a new EnvironmentManager instance. */
|
|
||||||
constructor($$source: Partial<EnvironmentManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new EnvironmentManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): EnvironmentManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new EnvironmentManager($$parsedSource as Partial<EnvironmentManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EventManager manages event-related operations
|
|
||||||
*/
|
|
||||||
export class EventManager {
|
|
||||||
|
|
||||||
/** Creates a new EventManager instance. */
|
|
||||||
constructor($$source: Partial<EventManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new EventManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): EventManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new EventManager($$parsedSource as Partial<EventManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* KeyBindingManager manages all key binding operations
|
|
||||||
*/
|
|
||||||
export class KeyBindingManager {
|
|
||||||
|
|
||||||
/** Creates a new KeyBindingManager instance. */
|
|
||||||
constructor($$source: Partial<KeyBindingManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new KeyBindingManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): KeyBindingManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new KeyBindingManager($$parsedSource as Partial<KeyBindingManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MenuManager manages menu-related operations
|
|
||||||
*/
|
|
||||||
export class MenuManager {
|
|
||||||
|
|
||||||
/** Creates a new MenuManager instance. */
|
|
||||||
constructor($$source: Partial<MenuManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new MenuManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): MenuManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new MenuManager($$parsedSource as Partial<MenuManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ScreenManager {
|
|
||||||
|
|
||||||
/** Creates a new ScreenManager instance. */
|
|
||||||
constructor($$source: Partial<ScreenManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ScreenManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): ScreenManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new ScreenManager($$parsedSource as Partial<ScreenManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceOptions provides optional parameters for calls to [NewService].
|
* ServiceOptions provides optional parameters for calls to [NewService].
|
||||||
*/
|
*/
|
||||||
@@ -359,26 +62,6 @@ export class ServiceOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SystemTrayManager manages system tray-related operations
|
|
||||||
*/
|
|
||||||
export class SystemTrayManager {
|
|
||||||
|
|
||||||
/** Creates a new SystemTrayManager instance. */
|
|
||||||
constructor($$source: Partial<SystemTrayManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new SystemTrayManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): SystemTrayManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new SystemTrayManager($$parsedSource as Partial<SystemTrayManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WebviewWindow {
|
export class WebviewWindow {
|
||||||
|
|
||||||
/** Creates a new WebviewWindow instance. */
|
/** Creates a new WebviewWindow instance. */
|
||||||
@@ -395,49 +78,3 @@ export class WebviewWindow {
|
|||||||
return new WebviewWindow($$parsedSource as Partial<WebviewWindow>);
|
return new WebviewWindow($$parsedSource as Partial<WebviewWindow>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* WindowManager manages all window-related operations
|
|
||||||
*/
|
|
||||||
export class WindowManager {
|
|
||||||
|
|
||||||
/** Creates a new WindowManager instance. */
|
|
||||||
constructor($$source: Partial<WindowManager> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new WindowManager instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): WindowManager {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new WindowManager($$parsedSource as Partial<WindowManager>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = WindowManager.createFrom;
|
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
|
||||||
const $$createType2 = ContextMenuManager.createFrom;
|
|
||||||
const $$createType3 = $Create.Nullable($$createType2);
|
|
||||||
const $$createType4 = KeyBindingManager.createFrom;
|
|
||||||
const $$createType5 = $Create.Nullable($$createType4);
|
|
||||||
const $$createType6 = BrowserManager.createFrom;
|
|
||||||
const $$createType7 = $Create.Nullable($$createType6);
|
|
||||||
const $$createType8 = EnvironmentManager.createFrom;
|
|
||||||
const $$createType9 = $Create.Nullable($$createType8);
|
|
||||||
const $$createType10 = DialogManager.createFrom;
|
|
||||||
const $$createType11 = $Create.Nullable($$createType10);
|
|
||||||
const $$createType12 = EventManager.createFrom;
|
|
||||||
const $$createType13 = $Create.Nullable($$createType12);
|
|
||||||
const $$createType14 = MenuManager.createFrom;
|
|
||||||
const $$createType15 = $Create.Nullable($$createType14);
|
|
||||||
const $$createType16 = ScreenManager.createFrom;
|
|
||||||
const $$createType17 = $Create.Nullable($$createType16);
|
|
||||||
const $$createType18 = ClipboardManager.createFrom;
|
|
||||||
const $$createType19 = $Create.Nullable($$createType18);
|
|
||||||
const $$createType20 = SystemTrayManager.createFrom;
|
|
||||||
const $$createType21 = $Create.Nullable($$createType20);
|
|
||||||
const $$createType22 = slog$0.Logger.createFrom;
|
|
||||||
const $$createType23 = $Create.Nullable($$createType22);
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service represents the notifications service
|
* Service represents the dock service
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -18,11 +18,19 @@ import * as application$0 from "../../application/models.js";
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as $models from "./models.js";
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HideAppIcon hides the app icon in the dock/taskbar.
|
||||||
|
*/
|
||||||
|
export function HideAppIcon(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3413658144) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RemoveBadge removes the badge label from the application icon.
|
* RemoveBadge removes the badge label from the application icon.
|
||||||
*/
|
*/
|
||||||
export function RemoveBadge(): Promise<void> & { cancel(): void } {
|
export function RemoveBadge(): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(2374916939) as any;
|
let $resultPromise = $Call.ByID(2752757297) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +38,7 @@ export function RemoveBadge(): Promise<void> & { cancel(): void } {
|
|||||||
* ServiceName returns the name of the service.
|
* ServiceName returns the name of the service.
|
||||||
*/
|
*/
|
||||||
export function ServiceName(): Promise<string> & { cancel(): void } {
|
export function ServiceName(): Promise<string> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(2428202016) as any;
|
let $resultPromise = $Call.ByID(2949906614) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +46,7 @@ export function ServiceName(): Promise<string> & { cancel(): void } {
|
|||||||
* ServiceShutdown is called when the service is unloaded.
|
* ServiceShutdown is called when the service is unloaded.
|
||||||
*/
|
*/
|
||||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3893755233) as any;
|
let $resultPromise = $Call.ByID(307064411) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +54,7 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|||||||
* ServiceStartup is called when the service is loaded.
|
* ServiceStartup is called when the service is loaded.
|
||||||
*/
|
*/
|
||||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(4078800764, options) as any;
|
let $resultPromise = $Call.ByID(1350118426, options) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +62,22 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
|
|||||||
* SetBadge sets the badge label on the application icon.
|
* SetBadge sets the badge label on the application icon.
|
||||||
*/
|
*/
|
||||||
export function SetBadge(label: string): Promise<void> & { cancel(): void } {
|
export function SetBadge(label: string): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(784276339, label) as any;
|
let $resultPromise = $Call.ByID(1717705661, label) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SetCustomBadge(label: string, options: $models.Options): Promise<void> & { cancel(): void } {
|
/**
|
||||||
let $resultPromise = $Call.ByID(3058653106, label, options) as any;
|
* SetCustomBadge sets the badge label on the application icon with custom options.
|
||||||
|
*/
|
||||||
|
export function SetCustomBadge(label: string, options: $models.BadgeOptions): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2730169760, label, options) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShowAppIcon shows the app icon in the dock/taskbar.
|
||||||
|
*/
|
||||||
|
export function ShowAppIcon(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3409697379) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
import * as BadgeService from "./badgeservice.js";
|
import * as DockService from "./dockservice.js";
|
||||||
export {
|
export {
|
||||||
BadgeService
|
DockService
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./models.js";
|
export * from "./models.js";
|
||||||
@@ -9,15 +9,18 @@ import {Create as $Create} from "@wailsio/runtime";
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as color$0 from "../../../../../../../image/color/models.js";
|
import * as color$0 from "../../../../../../../image/color/models.js";
|
||||||
|
|
||||||
export class Options {
|
/**
|
||||||
|
* BadgeOptions represents options for customizing badge appearance
|
||||||
|
*/
|
||||||
|
export class BadgeOptions {
|
||||||
"TextColour": color$0.RGBA;
|
"TextColour": color$0.RGBA;
|
||||||
"BackgroundColour": color$0.RGBA;
|
"BackgroundColour": color$0.RGBA;
|
||||||
"FontName": string;
|
"FontName": string;
|
||||||
"FontSize": number;
|
"FontSize": number;
|
||||||
"SmallFontSize": number;
|
"SmallFontSize": number;
|
||||||
|
|
||||||
/** Creates a new Options instance. */
|
/** Creates a new BadgeOptions instance. */
|
||||||
constructor($$source: Partial<Options> = {}) {
|
constructor($$source: Partial<BadgeOptions> = {}) {
|
||||||
if (!("TextColour" in $$source)) {
|
if (!("TextColour" in $$source)) {
|
||||||
this["TextColour"] = (new color$0.RGBA());
|
this["TextColour"] = (new color$0.RGBA());
|
||||||
}
|
}
|
||||||
@@ -38,9 +41,9 @@ export class Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Options instance from a string or object.
|
* Creates a new BadgeOptions instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): Options {
|
static createFrom($$source: any = {}): BadgeOptions {
|
||||||
const $$createField0_0 = $$createType0;
|
const $$createField0_0 = $$createType0;
|
||||||
const $$createField1_0 = $$createType0;
|
const $$createField1_0 = $$createType0;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
@@ -50,7 +53,7 @@ export class Options {
|
|||||||
if ("BackgroundColour" in $$parsedSource) {
|
if ("BackgroundColour" in $$parsedSource) {
|
||||||
$$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]);
|
$$parsedSource["BackgroundColour"] = $$createField1_0($$parsedSource["BackgroundColour"]);
|
||||||
}
|
}
|
||||||
return new Options($$parsedSource as Partial<Options>);
|
return new BadgeOptions($$parsedSource as Partial<BadgeOptions>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import {Create as $Create} from "@wailsio/runtime";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Logger records structured information about each call to its
|
|
||||||
* Log, Debug, Info, Warn, and Error methods.
|
|
||||||
* For each call, it creates a [Record] and passes it to a [Handler].
|
|
||||||
*
|
|
||||||
* To create a new Logger, call [New] or a Logger method
|
|
||||||
* that begins "With".
|
|
||||||
*/
|
|
||||||
export class Logger {
|
|
||||||
|
|
||||||
/** Creates a new Logger instance. */
|
|
||||||
constructor($$source: Partial<Logger> = {}) {
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Logger instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): Logger {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new Logger($$parsedSource as Partial<Logger>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
frontend/bindings/net/http/models.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Header represents the key-value pairs in an HTTP header.
|
||||||
|
*
|
||||||
|
* The keys should be in canonical form, as returned by
|
||||||
|
* [CanonicalHeaderKey].
|
||||||
|
*/
|
||||||
|
export type Header = { [_: string]: string[] };
|
||||||
@@ -68,4 +68,9 @@ export enum TranslatorType {
|
|||||||
* DeeplTranslatorType DeepL翻译器
|
* DeeplTranslatorType DeepL翻译器
|
||||||
*/
|
*/
|
||||||
DeeplTranslatorType = "deepl",
|
DeeplTranslatorType = "deepl",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TartuNLPTranslatorType TartuNLP翻译器
|
||||||
|
*/
|
||||||
|
TartuNLPTranslatorType = "tartunlp",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Create as $Create} from "@wailsio/runtime";
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as time$0 from "../../../time/models.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppConfig 应用配置 - 按照前端设置页面分类组织
|
* AppConfig 应用配置 - 按照前端设置页面分类组织
|
||||||
*/
|
*/
|
||||||
@@ -114,6 +110,11 @@ export class AppearanceConfig {
|
|||||||
*/
|
*/
|
||||||
"systemTheme": SystemThemeType;
|
"systemTheme": SystemThemeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前选择的预设主题名称
|
||||||
|
*/
|
||||||
|
"currentTheme": string;
|
||||||
|
|
||||||
/** Creates a new AppearanceConfig instance. */
|
/** Creates a new AppearanceConfig instance. */
|
||||||
constructor($$source: Partial<AppearanceConfig> = {}) {
|
constructor($$source: Partial<AppearanceConfig> = {}) {
|
||||||
if (!("language" in $$source)) {
|
if (!("language" in $$source)) {
|
||||||
@@ -122,6 +123,9 @@ export class AppearanceConfig {
|
|||||||
if (!("systemTheme" in $$source)) {
|
if (!("systemTheme" in $$source)) {
|
||||||
this["systemTheme"] = ("" as SystemThemeType);
|
this["systemTheme"] = ("" as SystemThemeType);
|
||||||
}
|
}
|
||||||
|
if (!("currentTheme" in $$source)) {
|
||||||
|
this["currentTheme"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
}
|
}
|
||||||
@@ -196,8 +200,8 @@ export class Document {
|
|||||||
"id": number;
|
"id": number;
|
||||||
"title": string;
|
"title": string;
|
||||||
"content": string;
|
"content": string;
|
||||||
"createdAt": time$0.Time;
|
"createdAt": string;
|
||||||
"updatedAt": time$0.Time;
|
"updatedAt": string;
|
||||||
"is_deleted": boolean;
|
"is_deleted": boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,10 +221,10 @@ export class Document {
|
|||||||
this["content"] = "";
|
this["content"] = "";
|
||||||
}
|
}
|
||||||
if (!("createdAt" in $$source)) {
|
if (!("createdAt" in $$source)) {
|
||||||
this["createdAt"] = null;
|
this["createdAt"] = "";
|
||||||
}
|
}
|
||||||
if (!("updatedAt" in $$source)) {
|
if (!("updatedAt" in $$source)) {
|
||||||
this["updatedAt"] = null;
|
this["updatedAt"] = "";
|
||||||
}
|
}
|
||||||
if (!("is_deleted" in $$source)) {
|
if (!("is_deleted" in $$source)) {
|
||||||
this["is_deleted"] = false;
|
this["is_deleted"] = false;
|
||||||
@@ -490,6 +494,11 @@ export class GeneralConfig {
|
|||||||
*/
|
*/
|
||||||
"enableLoadingAnimation": boolean;
|
"enableLoadingAnimation": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用标签页模式
|
||||||
|
*/
|
||||||
|
"enableTabs": boolean;
|
||||||
|
|
||||||
/** Creates a new GeneralConfig instance. */
|
/** Creates a new GeneralConfig instance. */
|
||||||
constructor($$source: Partial<GeneralConfig> = {}) {
|
constructor($$source: Partial<GeneralConfig> = {}) {
|
||||||
if (!("alwaysOnTop" in $$source)) {
|
if (!("alwaysOnTop" in $$source)) {
|
||||||
@@ -516,6 +525,9 @@ export class GeneralConfig {
|
|||||||
if (!("enableLoadingAnimation" in $$source)) {
|
if (!("enableLoadingAnimation" in $$source)) {
|
||||||
this["enableLoadingAnimation"] = false;
|
this["enableLoadingAnimation"] = false;
|
||||||
}
|
}
|
||||||
|
if (!("enableTabs" in $$source)) {
|
||||||
|
this["enableTabs"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
}
|
}
|
||||||
@@ -1143,8 +1155,8 @@ export class Theme {
|
|||||||
"type": ThemeType;
|
"type": ThemeType;
|
||||||
"colors": ThemeColorConfig;
|
"colors": ThemeColorConfig;
|
||||||
"isDefault": boolean;
|
"isDefault": boolean;
|
||||||
"createdAt": time$0.Time;
|
"createdAt": string;
|
||||||
"updatedAt": time$0.Time;
|
"updatedAt": string;
|
||||||
|
|
||||||
/** Creates a new Theme instance. */
|
/** Creates a new Theme instance. */
|
||||||
constructor($$source: Partial<Theme> = {}) {
|
constructor($$source: Partial<Theme> = {}) {
|
||||||
@@ -1164,10 +1176,10 @@ export class Theme {
|
|||||||
this["isDefault"] = false;
|
this["isDefault"] = false;
|
||||||
}
|
}
|
||||||
if (!("createdAt" in $$source)) {
|
if (!("createdAt" in $$source)) {
|
||||||
this["createdAt"] = null;
|
this["createdAt"] = "";
|
||||||
}
|
}
|
||||||
if (!("updatedAt" in $$source)) {
|
if (!("updatedAt" in $$source)) {
|
||||||
this["updatedAt"] = null;
|
this["updatedAt"] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
@@ -1187,9 +1199,20 @@ export class Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThemeColorConfig 主题颜色配置
|
* ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致)
|
||||||
*/
|
*/
|
||||||
export class ThemeColorConfig {
|
export class ThemeColorConfig {
|
||||||
|
/**
|
||||||
|
* 主题基本信息
|
||||||
|
* 主题名称
|
||||||
|
*/
|
||||||
|
"name": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为深色主题
|
||||||
|
*/
|
||||||
|
"dark": boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础色调
|
* 基础色调
|
||||||
* 主背景色
|
* 主背景色
|
||||||
@@ -1197,7 +1220,7 @@ export class ThemeColorConfig {
|
|||||||
"background": string;
|
"background": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 次要背景色
|
* 次要背景色(用于代码块交替背景)
|
||||||
*/
|
*/
|
||||||
"backgroundSecondary": string;
|
"backgroundSecondary": string;
|
||||||
|
|
||||||
@@ -1207,6 +1230,17 @@ export class ThemeColorConfig {
|
|||||||
"surface": string;
|
"surface": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 下拉菜单背景
|
||||||
|
*/
|
||||||
|
"dropdownBackground": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉菜单边框
|
||||||
|
*/
|
||||||
|
"dropdownBorder": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本颜色
|
||||||
* 主文本色
|
* 主文本色
|
||||||
*/
|
*/
|
||||||
"foreground": string;
|
"foreground": string;
|
||||||
@@ -1217,12 +1251,12 @@ export class ThemeColorConfig {
|
|||||||
"foregroundSecondary": string;
|
"foregroundSecondary": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 语法高亮
|
|
||||||
* 注释色
|
* 注释色
|
||||||
*/
|
*/
|
||||||
"comment": string;
|
"comment": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 语法高亮色 - 核心
|
||||||
* 关键字
|
* 关键字
|
||||||
*/
|
*/
|
||||||
"keyword": string;
|
"keyword": string;
|
||||||
@@ -1257,6 +1291,42 @@ export class ThemeColorConfig {
|
|||||||
*/
|
*/
|
||||||
"type": string;
|
"type": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语法高亮色 - 扩展
|
||||||
|
* 常量
|
||||||
|
*/
|
||||||
|
"constant": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储类型(如 static, const)
|
||||||
|
*/
|
||||||
|
"storage": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
"parameter": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类名
|
||||||
|
*/
|
||||||
|
"class": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题(Markdown等)
|
||||||
|
*/
|
||||||
|
"heading": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无效内容/错误
|
||||||
|
*/
|
||||||
|
"invalid": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则表达式
|
||||||
|
*/
|
||||||
|
"regexp": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 界面元素
|
* 界面元素
|
||||||
* 光标
|
* 光标
|
||||||
@@ -1284,12 +1354,12 @@ export class ThemeColorConfig {
|
|||||||
"lineNumber": string;
|
"lineNumber": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 活动行号
|
* 活动行号颜色
|
||||||
*/
|
*/
|
||||||
"activeLineNumber": string;
|
"activeLineNumber": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 边框分割线
|
* 边框和分割线
|
||||||
* 边框色
|
* 边框色
|
||||||
*/
|
*/
|
||||||
"borderColor": string;
|
"borderColor": string;
|
||||||
@@ -1300,7 +1370,7 @@ export class ThemeColorConfig {
|
|||||||
"borderLight": string;
|
"borderLight": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索匹配
|
* 搜索和匹配
|
||||||
* 搜索匹配
|
* 搜索匹配
|
||||||
*/
|
*/
|
||||||
"searchMatch": string;
|
"searchMatch": string;
|
||||||
@@ -1312,6 +1382,12 @@ export class ThemeColorConfig {
|
|||||||
|
|
||||||
/** Creates a new ThemeColorConfig instance. */
|
/** Creates a new ThemeColorConfig instance. */
|
||||||
constructor($$source: Partial<ThemeColorConfig> = {}) {
|
constructor($$source: Partial<ThemeColorConfig> = {}) {
|
||||||
|
if (!("name" in $$source)) {
|
||||||
|
this["name"] = "";
|
||||||
|
}
|
||||||
|
if (!("dark" in $$source)) {
|
||||||
|
this["dark"] = false;
|
||||||
|
}
|
||||||
if (!("background" in $$source)) {
|
if (!("background" in $$source)) {
|
||||||
this["background"] = "";
|
this["background"] = "";
|
||||||
}
|
}
|
||||||
@@ -1321,6 +1397,12 @@ export class ThemeColorConfig {
|
|||||||
if (!("surface" in $$source)) {
|
if (!("surface" in $$source)) {
|
||||||
this["surface"] = "";
|
this["surface"] = "";
|
||||||
}
|
}
|
||||||
|
if (!("dropdownBackground" in $$source)) {
|
||||||
|
this["dropdownBackground"] = "";
|
||||||
|
}
|
||||||
|
if (!("dropdownBorder" in $$source)) {
|
||||||
|
this["dropdownBorder"] = "";
|
||||||
|
}
|
||||||
if (!("foreground" in $$source)) {
|
if (!("foreground" in $$source)) {
|
||||||
this["foreground"] = "";
|
this["foreground"] = "";
|
||||||
}
|
}
|
||||||
@@ -1351,6 +1433,27 @@ export class ThemeColorConfig {
|
|||||||
if (!("type" in $$source)) {
|
if (!("type" in $$source)) {
|
||||||
this["type"] = "";
|
this["type"] = "";
|
||||||
}
|
}
|
||||||
|
if (!("constant" in $$source)) {
|
||||||
|
this["constant"] = "";
|
||||||
|
}
|
||||||
|
if (!("storage" in $$source)) {
|
||||||
|
this["storage"] = "";
|
||||||
|
}
|
||||||
|
if (!("parameter" in $$source)) {
|
||||||
|
this["parameter"] = "";
|
||||||
|
}
|
||||||
|
if (!("class" in $$source)) {
|
||||||
|
this["class"] = "";
|
||||||
|
}
|
||||||
|
if (!("heading" in $$source)) {
|
||||||
|
this["heading"] = "";
|
||||||
|
}
|
||||||
|
if (!("invalid" in $$source)) {
|
||||||
|
this["invalid"] = "";
|
||||||
|
}
|
||||||
|
if (!("regexp" in $$source)) {
|
||||||
|
this["regexp"] = "";
|
||||||
|
}
|
||||||
if (!("cursor" in $$source)) {
|
if (!("cursor" in $$source)) {
|
||||||
this["cursor"] = "";
|
this["cursor"] = "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as models$0 from "../models/models.js";
|
import * as models$0 from "../models/models.js";
|
||||||
@@ -54,6 +57,11 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2900331732, options) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StartAutoBackup 启动自动备份定时器
|
* StartAutoBackup 启动自动备份定时器
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,10 +10,6 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SelectDirectory 打开目录选择对话框
|
* SelectDirectory 打开目录选择对话框
|
||||||
*/
|
*/
|
||||||
@@ -29,11 +25,3 @@ export function SelectFile(): Promise<string> & { cancel(): void } {
|
|||||||
let $resultPromise = $Call.ByID(37302920) as any;
|
let $resultPromise = $Call.ByID(37302920) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SetWindow 设置绑定的窗口
|
|
||||||
*/
|
|
||||||
export function SetWindow(window: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(968177170, window) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ export function GetDocumentByID(id: number): Promise<models$0.Document | null> &
|
|||||||
return $typingPromise;
|
return $typingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GetFirstDocumentID gets the first active document's ID for frontend initialization
|
|
||||||
*/
|
|
||||||
export function GetFirstDocumentID(): Promise<number> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(2970773833) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ListAllDocumentsMeta lists all active (non-deleted) document metadata
|
* ListAllDocumentsMeta lists all active (non-deleted) document metadata
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ export function GetCurrentHotkey(): Promise<models$0.HotkeyCombo | null> & { can
|
|||||||
/**
|
/**
|
||||||
* Initialize 初始化热键服务
|
* Initialize 初始化热键服务
|
||||||
*/
|
*/
|
||||||
export function Initialize(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
export function Initialize(): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3671360458, app, mainWindow) as any;
|
let $resultPromise = $Call.ByID(3671360458) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +61,14 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServiceStartup initializes the service when the application starts
|
||||||
|
*/
|
||||||
|
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3079990808, options) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UnregisterHotkey 取消注册全局热键
|
* UnregisterHotkey 取消注册全局热键
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpClientService HTTP客户端服务
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecuteRequest 执行HTTP请求
|
||||||
|
*/
|
||||||
|
export function ExecuteRequest(request: $models.HttpRequest | null): Promise<$models.HttpResponse | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3143343977, request) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = $models.HttpResponse.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
@@ -8,6 +8,7 @@ import * as DialogService from "./dialogservice.js";
|
|||||||
import * as DocumentService from "./documentservice.js";
|
import * as DocumentService from "./documentservice.js";
|
||||||
import * as ExtensionService from "./extensionservice.js";
|
import * as ExtensionService from "./extensionservice.js";
|
||||||
import * as HotkeyService from "./hotkeyservice.js";
|
import * as HotkeyService from "./hotkeyservice.js";
|
||||||
|
import * as HttpClientService from "./httpclientservice.js";
|
||||||
import * as KeyBindingService from "./keybindingservice.js";
|
import * as KeyBindingService from "./keybindingservice.js";
|
||||||
import * as MigrationService from "./migrationservice.js";
|
import * as MigrationService from "./migrationservice.js";
|
||||||
import * as SelfUpdateService from "./selfupdateservice.js";
|
import * as SelfUpdateService from "./selfupdateservice.js";
|
||||||
@@ -26,6 +27,7 @@ export {
|
|||||||
DocumentService,
|
DocumentService,
|
||||||
ExtensionService,
|
ExtensionService,
|
||||||
HotkeyService,
|
HotkeyService,
|
||||||
|
HttpClientService,
|
||||||
KeyBindingService,
|
KeyBindingService,
|
||||||
MigrationService,
|
MigrationService,
|
||||||
SelfUpdateService,
|
SelfUpdateService,
|
||||||
|
|||||||
@@ -8,6 +8,114 @@ import {Create as $Create} from "@wailsio/runtime";
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as http$0 from "../../../net/http/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as time$0 from "../../../time/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpRequest HTTP请求结构
|
||||||
|
*/
|
||||||
|
export class HttpRequest {
|
||||||
|
"method": string;
|
||||||
|
"url": string;
|
||||||
|
"headers": { [_: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* json, formdata, urlencoded, text, params, xml, html, javascript, binary
|
||||||
|
*/
|
||||||
|
"bodyType"?: string;
|
||||||
|
"body"?: any;
|
||||||
|
|
||||||
|
/** Creates a new HttpRequest instance. */
|
||||||
|
constructor($$source: Partial<HttpRequest> = {}) {
|
||||||
|
if (!("method" in $$source)) {
|
||||||
|
this["method"] = "";
|
||||||
|
}
|
||||||
|
if (!("url" in $$source)) {
|
||||||
|
this["url"] = "";
|
||||||
|
}
|
||||||
|
if (!("headers" in $$source)) {
|
||||||
|
this["headers"] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HttpRequest instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): HttpRequest {
|
||||||
|
const $$createField2_0 = $$createType0;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("headers" in $$parsedSource) {
|
||||||
|
$$parsedSource["headers"] = $$createField2_0($$parsedSource["headers"]);
|
||||||
|
}
|
||||||
|
return new HttpRequest($$parsedSource as Partial<HttpRequest>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpResponse HTTP响应结构
|
||||||
|
*/
|
||||||
|
export class HttpResponse {
|
||||||
|
/**
|
||||||
|
* 使用resp.Status()返回完整状态如"200 OK"
|
||||||
|
*/
|
||||||
|
"status": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应时间(毫秒)
|
||||||
|
*/
|
||||||
|
"time": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求大小
|
||||||
|
*/
|
||||||
|
"requestSize": string;
|
||||||
|
"body": any;
|
||||||
|
"headers": http$0.Header;
|
||||||
|
"timestamp": time$0.Time;
|
||||||
|
"error"?: any;
|
||||||
|
|
||||||
|
/** Creates a new HttpResponse instance. */
|
||||||
|
constructor($$source: Partial<HttpResponse> = {}) {
|
||||||
|
if (!("status" in $$source)) {
|
||||||
|
this["status"] = "";
|
||||||
|
}
|
||||||
|
if (!("time" in $$source)) {
|
||||||
|
this["time"] = 0;
|
||||||
|
}
|
||||||
|
if (!("requestSize" in $$source)) {
|
||||||
|
this["requestSize"] = "";
|
||||||
|
}
|
||||||
|
if (!("body" in $$source)) {
|
||||||
|
this["body"] = null;
|
||||||
|
}
|
||||||
|
if (!("headers" in $$source)) {
|
||||||
|
this["headers"] = ({} as http$0.Header);
|
||||||
|
}
|
||||||
|
if (!("timestamp" in $$source)) {
|
||||||
|
this["timestamp"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HttpResponse instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): HttpResponse {
|
||||||
|
const $$createField4_0 = $$createType1;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("headers" in $$parsedSource) {
|
||||||
|
$$parsedSource["headers"] = $$createField4_0($$parsedSource["headers"]);
|
||||||
|
}
|
||||||
|
return new HttpResponse($$parsedSource as Partial<HttpResponse>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MemoryStats 内存统计信息
|
* MemoryStats 内存统计信息
|
||||||
@@ -119,6 +227,42 @@ export enum MigrationStatus {
|
|||||||
MigrationStatusFailed = "failed",
|
MigrationStatusFailed = "failed",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSInfo 操作系统信息
|
||||||
|
*/
|
||||||
|
export class OSInfo {
|
||||||
|
"id": string;
|
||||||
|
"name": string;
|
||||||
|
"version": string;
|
||||||
|
"branding": string;
|
||||||
|
|
||||||
|
/** Creates a new OSInfo instance. */
|
||||||
|
constructor($$source: Partial<OSInfo> = {}) {
|
||||||
|
if (!("id" in $$source)) {
|
||||||
|
this["id"] = "";
|
||||||
|
}
|
||||||
|
if (!("name" in $$source)) {
|
||||||
|
this["name"] = "";
|
||||||
|
}
|
||||||
|
if (!("version" in $$source)) {
|
||||||
|
this["version"] = "";
|
||||||
|
}
|
||||||
|
if (!("branding" in $$source)) {
|
||||||
|
this["branding"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new OSInfo instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): OSInfo {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new OSInfo($$parsedSource as Partial<OSInfo>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SelfUpdateResult 自我更新结果
|
* SelfUpdateResult 自我更新结果
|
||||||
*/
|
*/
|
||||||
@@ -203,7 +347,55 @@ export class SelfUpdateResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WindowInfo 窗口信息(简化版)
|
* SystemInfo 系统信息
|
||||||
|
*/
|
||||||
|
export class SystemInfo {
|
||||||
|
"os": string;
|
||||||
|
"arch": string;
|
||||||
|
"debug": boolean;
|
||||||
|
"osInfo": OSInfo | null;
|
||||||
|
"platformInfo": { [_: string]: any };
|
||||||
|
|
||||||
|
/** Creates a new SystemInfo instance. */
|
||||||
|
constructor($$source: Partial<SystemInfo> = {}) {
|
||||||
|
if (!("os" in $$source)) {
|
||||||
|
this["os"] = "";
|
||||||
|
}
|
||||||
|
if (!("arch" in $$source)) {
|
||||||
|
this["arch"] = "";
|
||||||
|
}
|
||||||
|
if (!("debug" in $$source)) {
|
||||||
|
this["debug"] = false;
|
||||||
|
}
|
||||||
|
if (!("osInfo" in $$source)) {
|
||||||
|
this["osInfo"] = null;
|
||||||
|
}
|
||||||
|
if (!("platformInfo" in $$source)) {
|
||||||
|
this["platformInfo"] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SystemInfo instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): SystemInfo {
|
||||||
|
const $$createField3_0 = $$createType5;
|
||||||
|
const $$createField4_0 = $$createType6;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("osInfo" in $$parsedSource) {
|
||||||
|
$$parsedSource["osInfo"] = $$createField3_0($$parsedSource["osInfo"]);
|
||||||
|
}
|
||||||
|
if ("platformInfo" in $$parsedSource) {
|
||||||
|
$$parsedSource["platformInfo"] = $$createField4_0($$parsedSource["platformInfo"]);
|
||||||
|
}
|
||||||
|
return new SystemInfo($$parsedSource as Partial<SystemInfo>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WindowInfo 窗口信息
|
||||||
*/
|
*/
|
||||||
export class WindowInfo {
|
export class WindowInfo {
|
||||||
"Window": application$0.WebviewWindow | null;
|
"Window": application$0.WebviewWindow | null;
|
||||||
@@ -229,7 +421,7 @@ export class WindowInfo {
|
|||||||
* Creates a new WindowInfo instance from a string or object.
|
* Creates a new WindowInfo instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): WindowInfo {
|
static createFrom($$source: any = {}): WindowInfo {
|
||||||
const $$createField0_0 = $$createType1;
|
const $$createField0_0 = $$createType8;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("Window" in $$parsedSource) {
|
if ("Window" in $$parsedSource) {
|
||||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||||
@@ -259,5 +451,17 @@ export class WindowSnapService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = application$0.WebviewWindow.createFrom;
|
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
var $$createType1 = (function $$initCreateType1(...args): any {
|
||||||
|
if ($$createType1 === $$initCreateType1) {
|
||||||
|
$$createType1 = $$createType3;
|
||||||
|
}
|
||||||
|
return $$createType1(...args);
|
||||||
|
});
|
||||||
|
const $$createType2 = $Create.Array($Create.Any);
|
||||||
|
const $$createType3 = $Create.Map($Create.Any, $$createType2);
|
||||||
|
const $$createType4 = OSInfo.createFrom;
|
||||||
|
const $$createType5 = $Create.Nullable($$createType4);
|
||||||
|
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
|
||||||
|
const $$createType7 = application$0.WebviewWindow.createFrom;
|
||||||
|
const $$createType8 = $Create.Nullable($$createType7);
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ export function GetMemoryStats(): Promise<$models.MemoryStats> & { cancel(): voi
|
|||||||
return $typingPromise;
|
return $typingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetSystemInfo 获取系统环境信息
|
||||||
|
*/
|
||||||
|
export function GetSystemInfo(): Promise<$models.SystemInfo | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2629436820) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType2($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TriggerGC 手动触发垃圾回收
|
* TriggerGC 手动触发垃圾回收
|
||||||
*/
|
*/
|
||||||
@@ -44,3 +56,5 @@ export function TriggerGC(): Promise<void> & { cancel(): void } {
|
|||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = $models.MemoryStats.createFrom;
|
const $$createType0 = $models.MemoryStats.createFrom;
|
||||||
|
const $$createType1 = $models.SystemInfo.createFrom;
|
||||||
|
const $$createType2 = $Create.Nullable($$createType1);
|
||||||
|
|||||||
@@ -17,18 +17,6 @@ import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/applic
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as models$0 from "../models/models.js";
|
import * as models$0 from "../models/models.js";
|
||||||
|
|
||||||
/**
|
|
||||||
* CreateTheme 创建新主题
|
|
||||||
*/
|
|
||||||
export function CreateTheme(theme: models$0.Theme | null): Promise<models$0.Theme | null> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(3274757686, theme) as any;
|
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
|
||||||
return $$createType1($result);
|
|
||||||
}) as any;
|
|
||||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
|
||||||
return $typingPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetAllThemes 获取所有主题
|
* GetAllThemes 获取所有主题
|
||||||
*/
|
*/
|
||||||
@@ -42,22 +30,11 @@ export function GetAllThemes(): Promise<(models$0.Theme | null)[]> & { cancel():
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetDefaultThemes 获取默认主题
|
* GetThemeByID 根据ID或名称获取主题
|
||||||
|
* 如果 id > 0,按ID查询;如果 id = 0,按名称查询
|
||||||
*/
|
*/
|
||||||
export function GetDefaultThemes(): Promise<{ [_: string]: models$0.Theme | null }> & { cancel(): void } {
|
export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<models$0.Theme | null> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3801788118) as any;
|
let $resultPromise = $Call.ByID(127385338, id, name) as any;
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
|
||||||
return $$createType3($result);
|
|
||||||
}) as any;
|
|
||||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
|
||||||
return $typingPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetThemeByType 根据类型获取默认主题
|
|
||||||
*/
|
|
||||||
export function GetThemeByType(themeType: models$0.ThemeType): Promise<models$0.Theme | null> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1680465265, themeType) as any;
|
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
return $$createType1($result);
|
return $$createType1($result);
|
||||||
}) as any;
|
}) as any;
|
||||||
@@ -66,10 +43,10 @@ export function GetThemeByType(themeType: models$0.ThemeType): Promise<models$0.
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResetThemeColors 重置主题颜色为默认值
|
* ResetTheme 重置主题为预设配置
|
||||||
*/
|
*/
|
||||||
export function ResetThemeColors(themeType: models$0.ThemeType): Promise<void> & { cancel(): void } {
|
export function ResetTheme(id: number, ...name: string[]): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(342461245, themeType) as any;
|
let $resultPromise = $Call.ByID(1806334457, id, name) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +67,10 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateThemeColors 更新主题颜色
|
* UpdateTheme 更新主题
|
||||||
*/
|
*/
|
||||||
export function UpdateThemeColors(themeType: models$0.ThemeType, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
|
export function UpdateTheme(id: number, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(2750902529, themeType, colors) as any;
|
let $resultPromise = $Call.ByID(70189749, id, colors) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +78,3 @@ export function UpdateThemeColors(themeType: models$0.ThemeType, colors: models$
|
|||||||
const $$createType0 = models$0.Theme.createFrom;
|
const $$createType0 = models$0.Theme.createFrom;
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
const $$createType2 = $Create.Array($$createType1);
|
const $$createType2 = $Create.Array($$createType1);
|
||||||
const $$createType3 = $Create.Map($Create.Any, $$createType1);
|
|
||||||
|
|||||||
@@ -14,27 +14,6 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as translator$0 from "../common/translator/models.js";
|
import * as translator$0 from "../common/translator/models.js";
|
||||||
|
|
||||||
/**
|
|
||||||
* GetAvailableTranslators 获取所有可用翻译器类型
|
|
||||||
* @returns {[]string} 翻译器类型列表
|
|
||||||
*/
|
|
||||||
export function GetAvailableTranslators(): Promise<string[]> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1186597995) as any;
|
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
}) as any;
|
|
||||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
|
||||||
return $typingPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetStandardLanguageCode 获取标准化的语言代码
|
|
||||||
*/
|
|
||||||
export function GetStandardLanguageCode(translatorType: translator$0.TranslatorType, languageCode: string): Promise<string> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1158131995, translatorType, languageCode) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetTranslatorLanguages 获取翻译器的语言列表
|
* GetTranslatorLanguages 获取翻译器的语言列表
|
||||||
* @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
|
* @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
|
||||||
@@ -43,6 +22,19 @@ export function GetStandardLanguageCode(translatorType: translator$0.TranslatorT
|
|||||||
*/
|
*/
|
||||||
export function GetTranslatorLanguages(translatorType: translator$0.TranslatorType): Promise<{ [_: string]: translator$0.LanguageInfo }> & { cancel(): void } {
|
export function GetTranslatorLanguages(translatorType: translator$0.TranslatorType): Promise<{ [_: string]: translator$0.LanguageInfo }> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3976114458, translatorType) as any;
|
let $resultPromise = $Call.ByID(3976114458, translatorType) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetTranslators 获取所有可用翻译器类型
|
||||||
|
* @returns {[]string} 翻译器类型列表
|
||||||
|
*/
|
||||||
|
export function GetTranslators(): Promise<string[]> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3720069432) as any;
|
||||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
return $$createType2($result);
|
return $$createType2($result);
|
||||||
}) as any;
|
}) as any;
|
||||||
@@ -73,6 +65,6 @@ export function TranslateWith(text: string, $from: string, to: string, translato
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = $Create.Array($Create.Any);
|
const $$createType0 = translator$0.LanguageInfo.createFrom;
|
||||||
const $$createType1 = translator$0.LanguageInfo.createFrom;
|
const $$createType1 = $Create.Map($Create.Any, $$createType0);
|
||||||
const $$createType2 = $Create.Map($Create.Any, $$createType1);
|
const $$createType2 = $Create.Array($Create.Any);
|
||||||
|
|||||||
@@ -10,10 +10,6 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HandleWindowClose 处理窗口关闭事件
|
* HandleWindowClose 处理窗口关闭事件
|
||||||
*/
|
*/
|
||||||
@@ -38,14 +34,6 @@ export function MinimizeButtonClicked(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SetAppReferences 设置应用引用
|
|
||||||
*/
|
|
||||||
export function SetAppReferences(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(3544515719, app, mainWindow) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ShouldMinimizeToTray 检查是否应该最小化到托盘
|
* ShouldMinimizeToTray 检查是否应该最小化到托盘
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,10 +10,6 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as $models from "./models.js";
|
import * as $models from "./models.js";
|
||||||
@@ -54,14 +50,6 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SetAppReferences 设置应用和主窗口引用
|
|
||||||
*/
|
|
||||||
export function SetAppReferences(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1120840759, app, mainWindow) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SetWindowSnapService 设置窗口吸附服务引用
|
* SetWindowSnapService 设置窗口吸附服务引用
|
||||||
*/
|
*/
|
||||||
|
|||||||
8
frontend/components.d.ts
vendored
@@ -1,8 +1,11 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
@@ -16,6 +19,9 @@ declare module 'vue' {
|
|||||||
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
TabContainer: typeof import('./src/components/tabs/TabContainer.vue')['default']
|
||||||
|
TabContextMenu: typeof import('./src/components/tabs/TabContextMenu.vue')['default']
|
||||||
|
TabItem: typeof import('./src/components/tabs/TabItem.vue')['default']
|
||||||
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']
|
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']
|
||||||
WindowsTitleBar: typeof import('./src/components/titlebar/WindowsTitleBar.vue')['default']
|
WindowsTitleBar: typeof import('./src/components/titlebar/WindowsTitleBar.vue')['default']
|
||||||
WindowTitleBar: typeof import('./src/components/titlebar/WindowTitleBar.vue')['default']
|
WindowTitleBar: typeof import('./src/components/titlebar/WindowTitleBar.vue')['default']
|
||||||
|
|||||||
130
frontend/docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import {defineConfig} from 'vitepress'
|
||||||
|
const base = '/'
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({
|
||||||
|
base: base,
|
||||||
|
title: "voidraft",
|
||||||
|
description: "An elegant text snippet recording tool designed for developers.",
|
||||||
|
srcDir: 'src',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
cacheDir: './.vitepress/cache',
|
||||||
|
outDir: './.vitepress/dist',
|
||||||
|
srcExclude: [],
|
||||||
|
ignoreDeadLinks: false,
|
||||||
|
head: [
|
||||||
|
["link", {rel: "icon", type: "image/png", href: "/icon/favicon-96x96.png", sizes: "96x96"}],
|
||||||
|
["link", {rel: "icon", type: "image/svg+xml", href: "/icon/favicon.svg"}],
|
||||||
|
["link", {rel: "shortcut icon", href: "/icon/favicon.ico"}],
|
||||||
|
["link", {rel: "apple-touch-icon", sizes: "180x180", href: "/icon/apple-touch-icon.png"}],
|
||||||
|
["meta", {name: "apple-mobile-web-app-title", content: "voidraft"}],
|
||||||
|
["link", {rel: "manifest", href: "/icon/site.webmanifest"}],
|
||||||
|
['meta', {name: 'viewport', content: 'width=device-width,initial-scale=1'}]
|
||||||
|
],
|
||||||
|
|
||||||
|
// 国际化配置
|
||||||
|
locales: {
|
||||||
|
root: {
|
||||||
|
label: 'English',
|
||||||
|
lang: 'en-US',
|
||||||
|
description: 'An elegant text snippet recording tool designed for developers.',
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/icon/logo.png',
|
||||||
|
siteTitle: 'voidraft',
|
||||||
|
nav: [
|
||||||
|
{text: 'Home', link: '/'},
|
||||||
|
{text: 'Guide', link: '/guide/introduction'}
|
||||||
|
],
|
||||||
|
sidebar: {
|
||||||
|
'/guide/': [
|
||||||
|
{
|
||||||
|
text: 'Getting Started',
|
||||||
|
items: [
|
||||||
|
{text: 'Introduction', link: '/guide/introduction'},
|
||||||
|
{text: 'Installation', link: '/guide/installation'},
|
||||||
|
{text: 'Quick Start', link: '/guide/getting-started'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Features',
|
||||||
|
items: [
|
||||||
|
{text: 'Overview', link: '/guide/features'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
socialLinks: [
|
||||||
|
{icon: 'github', link: 'https://github.com/landaiqing/voidraft'}
|
||||||
|
],
|
||||||
|
outline: {
|
||||||
|
label: 'On this page'
|
||||||
|
},
|
||||||
|
lastUpdated: {
|
||||||
|
text: 'Last updated'
|
||||||
|
},
|
||||||
|
docFooter: {
|
||||||
|
prev: 'Previous',
|
||||||
|
next: 'Next'
|
||||||
|
},
|
||||||
|
darkModeSwitchLabel: 'Appearance',
|
||||||
|
sidebarMenuLabel: 'Menu',
|
||||||
|
returnToTopLabel: 'Return to top',
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2025-present landaiqing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
label: '简体中文',
|
||||||
|
lang: 'zh-CN',
|
||||||
|
link: '/zh/',
|
||||||
|
description: '一个为开发者设计的优雅文本片段记录工具',
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/icon/logo.png',
|
||||||
|
siteTitle: 'voidraft',
|
||||||
|
nav: [
|
||||||
|
{text: '首页', link: '/zh/'},
|
||||||
|
{text: '指南', link: '/zh/guide/introduction'}
|
||||||
|
],
|
||||||
|
sidebar: {
|
||||||
|
'/zh/guide/': [
|
||||||
|
{
|
||||||
|
text: '开始使用',
|
||||||
|
items: [
|
||||||
|
{text: '简介', link: '/zh/guide/introduction'},
|
||||||
|
{text: '安装', link: '/zh/guide/installation'},
|
||||||
|
{text: '快速开始', link: '/zh/guide/getting-started'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '功能特性',
|
||||||
|
items: [
|
||||||
|
{text: '功能概览', link: '/zh/guide/features'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
socialLinks: [
|
||||||
|
{icon: 'github', link: 'https://github.com/landaiqing/voidraft'}
|
||||||
|
],
|
||||||
|
outline: {
|
||||||
|
label: '本页目录'
|
||||||
|
},
|
||||||
|
lastUpdated: {
|
||||||
|
text: '最后更新'
|
||||||
|
},
|
||||||
|
docFooter: {
|
||||||
|
prev: '上一页',
|
||||||
|
next: '下一页'
|
||||||
|
},
|
||||||
|
darkModeSwitchLabel: '外观',
|
||||||
|
sidebarMenuLabel: '菜单',
|
||||||
|
returnToTopLabel: '返回顶部',
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2025-present landaiqing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
6
frontend/docs/.vitepress/theme/index.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@import "style/var.css";
|
||||||
|
@import "style/blur.css";
|
||||||
|
@import "style/badge.css";
|
||||||
|
@import "style/grid.css";
|
||||||
|
|
||||||
|
|
||||||
17
frontend/docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// https://vitepress.dev/guide/custom-theme
|
||||||
|
import { h } from 'vue'
|
||||||
|
import type { Theme } from 'vitepress'
|
||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: DefaultTheme,
|
||||||
|
Layout: () => {
|
||||||
|
return h(DefaultTheme.Layout, null, {
|
||||||
|
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enhanceApp({ app, router, siteData }) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
} satisfies Theme
|
||||||
21
frontend/docs/.vitepress/theme/style/badge.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* 提示框背景颜色 */
|
||||||
|
:root {
|
||||||
|
--vp-custom-block-tip-bg: var(--vp-c-green-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示框 */
|
||||||
|
.custom-block.tip {
|
||||||
|
border-color: var(--vp-c-green-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 警告框 */
|
||||||
|
.custom-block.warning {
|
||||||
|
/* border-color: #d97706; */
|
||||||
|
border-color: var(--vp-c-yellow-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 危险框 */
|
||||||
|
.custom-block.danger {
|
||||||
|
/* border-color: #f43f5e; */
|
||||||
|
border-color: var(--vp-c-red-2);
|
||||||
|
}
|
||||||
73
frontend/docs/.vitepress/theme/style/blur.css
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/* .vitepress/theme/style/blur.css */
|
||||||
|
:root {
|
||||||
|
/* 首页导航 */
|
||||||
|
.VPNavBar {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文档页导航两侧 */
|
||||||
|
.VPNavBar:not(.home) {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
/* 文档页导航两侧 */
|
||||||
|
.VPNavBar:not(.home) {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页下滑后导航两侧 */
|
||||||
|
.VPNavBar:not(.has-sidebar):not(.home.top) {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
/* 文档页导航中间 */
|
||||||
|
.VPNavBar:not(.home.top) .content-body {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页下滑后导航中间 */
|
||||||
|
.VPNavBar:not(.has-sidebar):not(.home.top) .content-body {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分割线 */
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
/* 文档页分割线 */
|
||||||
|
.VPNavBar:not(.home.top) .divider-line {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页分割线 */
|
||||||
|
.VPNavBar:not(.has-sidebar):not(.home.top) .divider {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框 VPNavBarSearchButton.vue */
|
||||||
|
.DocSearch-Button {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端大纲栏 */
|
||||||
|
.VPLocalNav {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
/* 隐藏分割线 */
|
||||||
|
/* border-bottom: 5px solid var(--vp-c-gutter); */
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
frontend/docs/.vitepress/theme/style/grid.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Grid Background
|
||||||
|
* 网格背景样式 - 为文档页面添加简约的网格背景
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.VPDoc,
|
||||||
|
.VPHome {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc::before,
|
||||||
|
.VPHome::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 亮色模式网格 */
|
||||||
|
:root:not(.dark) .VPDoc::before,
|
||||||
|
:root:not(.dark) .VPHome::before {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px);
|
||||||
|
background-size: 60px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式网格 */
|
||||||
|
.dark .VPDoc::before,
|
||||||
|
.dark .VPHome::before {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
|
||||||
|
background-size: 60px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
137
frontend/docs/.vitepress/theme/style/var.css
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Customize default theme styling by overriding CSS variables:
|
||||||
|
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colors
|
||||||
|
*
|
||||||
|
* Each colors have exact same color scale system with 3 levels of solid
|
||||||
|
* colors with different brightness, and 1 soft color.
|
||||||
|
*
|
||||||
|
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||||
|
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||||
|
*
|
||||||
|
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||||
|
*
|
||||||
|
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||||
|
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||||
|
* top of it.
|
||||||
|
*
|
||||||
|
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||||
|
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||||
|
* on top of it.
|
||||||
|
*
|
||||||
|
* The soft color must be semi transparent alpha channel. This is crucial
|
||||||
|
* because it allows adding multiple "soft" colors on top of each other
|
||||||
|
* to create an accent, such as when having inline code block inside
|
||||||
|
* custom containers.
|
||||||
|
*
|
||||||
|
* - `default`: The color used purely for subtle indication without any
|
||||||
|
* special meanings attached to it such as bg color for menu hover state.
|
||||||
|
*
|
||||||
|
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||||
|
* brand theme, etc.
|
||||||
|
*
|
||||||
|
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||||
|
* brand color for this by default.
|
||||||
|
*
|
||||||
|
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||||
|
* container, badges, etc.
|
||||||
|
*
|
||||||
|
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||||
|
* in custom container, badges, etc.
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
:root {
|
||||||
|
--vp-c-default-1: var(--vp-c-gray-1);
|
||||||
|
--vp-c-default-2: var(--vp-c-gray-2);
|
||||||
|
--vp-c-default-3: var(--vp-c-gray-3);
|
||||||
|
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||||
|
|
||||||
|
--vp-c-brand-1: var(--vp-c-indigo-1);
|
||||||
|
--vp-c-brand-2: var(--vp-c-indigo-2);
|
||||||
|
--vp-c-brand-3: var(--vp-c-indigo-3);
|
||||||
|
--vp-c-brand-soft: var(--vp-c-indigo-soft);
|
||||||
|
|
||||||
|
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||||
|
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||||
|
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||||
|
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||||
|
|
||||||
|
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||||
|
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||||
|
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||||
|
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||||
|
|
||||||
|
--vp-c-danger-1: var(--vp-c-red-1);
|
||||||
|
--vp-c-danger-2: var(--vp-c-red-2);
|
||||||
|
--vp-c-danger-3: var(--vp-c-red-3);
|
||||||
|
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: Button
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--vp-button-brand-border: transparent;
|
||||||
|
--vp-button-brand-text: var(--vp-c-white);
|
||||||
|
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||||
|
--vp-button-brand-hover-border: transparent;
|
||||||
|
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||||
|
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||||
|
--vp-button-brand-active-border: transparent;
|
||||||
|
--vp-button-brand-active-text: var(--vp-c-white);
|
||||||
|
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: Home
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-name-color: transparent;
|
||||||
|
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||||
|
120deg,
|
||||||
|
#bd34fe 30%,
|
||||||
|
#41d1ff
|
||||||
|
);
|
||||||
|
|
||||||
|
--vp-home-hero-image-background-image: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#bd34fe 50%,
|
||||||
|
#47caff 50%
|
||||||
|
);
|
||||||
|
--vp-home-hero-image-filter: blur(44px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(56px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(68px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: Custom Block
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--vp-custom-block-tip-border: transparent;
|
||||||
|
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||||
|
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||||
|
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: Algolia
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.DocSearch {
|
||||||
|
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||||
|
}
|
||||||
163
frontend/docs/src/guide/features.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Features
|
||||||
|
|
||||||
|
Explore the powerful features that make voidraft a great tool for developers.
|
||||||
|
|
||||||
|
## Block-Based Editing
|
||||||
|
|
||||||
|
voidraft's core feature is its block-based editing system:
|
||||||
|
|
||||||
|
- Each block can have a different programming language
|
||||||
|
- Blocks are separated by delimiters (`∞∞∞language`)
|
||||||
|
- Navigate quickly between blocks
|
||||||
|
- Format each block independently
|
||||||
|
|
||||||
|
## Syntax Highlighting
|
||||||
|
|
||||||
|
Professional syntax highlighting for 30+ languages:
|
||||||
|
|
||||||
|
- Automatic language detection
|
||||||
|
- Customizable color schemes
|
||||||
|
- Support for nested languages
|
||||||
|
- Code folding support
|
||||||
|
|
||||||
|
## HTTP Client
|
||||||
|
|
||||||
|
Built-in HTTP client for API testing:
|
||||||
|
|
||||||
|
### Request Types
|
||||||
|
- GET, POST, PUT, DELETE, PATCH
|
||||||
|
- Custom headers
|
||||||
|
- Multiple body formats: JSON, FormData, URL-encoded, XML, Text
|
||||||
|
|
||||||
|
### Request Variables
|
||||||
|
Define and reuse variables:
|
||||||
|
|
||||||
|
```http
|
||||||
|
@var {
|
||||||
|
baseUrl: "https://api.example.com",
|
||||||
|
token: "your-api-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
GET "{{baseUrl}}/users" {
|
||||||
|
authorization: "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Handling
|
||||||
|
- View formatted JSON responses
|
||||||
|
- See response time and size
|
||||||
|
- Inspect headers
|
||||||
|
- Save responses for later
|
||||||
|
|
||||||
|
## Code Formatting
|
||||||
|
|
||||||
|
Integrated Prettier support:
|
||||||
|
|
||||||
|
- Format on save (optional)
|
||||||
|
- Format selection or entire block
|
||||||
|
- Supports JavaScript, TypeScript, CSS, HTML, JSON, and more
|
||||||
|
- Customizable formatting rules
|
||||||
|
|
||||||
|
## Editor Extensions
|
||||||
|
|
||||||
|
### VSCode-Style Search
|
||||||
|
- Find and replace with regex support
|
||||||
|
- Case-sensitive and whole word options
|
||||||
|
- Search across all blocks
|
||||||
|
|
||||||
|
### Minimap
|
||||||
|
- Bird's-eye view of your document
|
||||||
|
- Quick navigation
|
||||||
|
- Customizable size and position
|
||||||
|
|
||||||
|
### Rainbow Brackets
|
||||||
|
- Color-coded bracket pairs
|
||||||
|
- Easier to match brackets
|
||||||
|
- Customizable colors
|
||||||
|
|
||||||
|
### Color Picker
|
||||||
|
- Visual color selection
|
||||||
|
- Supports hex, RGB, HSL
|
||||||
|
- Live preview
|
||||||
|
|
||||||
|
### Translation Tool
|
||||||
|
- Translate selected text
|
||||||
|
- Multiple language support
|
||||||
|
- Quick keyboard access
|
||||||
|
|
||||||
|
### Text Highlighting
|
||||||
|
- Highlight important text
|
||||||
|
- Multiple highlight colors
|
||||||
|
- Persistent highlights
|
||||||
|
|
||||||
|
## Multi-Window Support
|
||||||
|
|
||||||
|
Work efficiently with multiple windows:
|
||||||
|
|
||||||
|
- Each window is independent
|
||||||
|
- Separate documents
|
||||||
|
- Synchronized settings
|
||||||
|
- Window state persistence
|
||||||
|
|
||||||
|
## Theme Customization
|
||||||
|
|
||||||
|
Full control over editor appearance:
|
||||||
|
|
||||||
|
### Built-in Themes
|
||||||
|
- Dark mode
|
||||||
|
- Light mode
|
||||||
|
- Auto-switch based on system
|
||||||
|
|
||||||
|
### Custom Themes
|
||||||
|
- Create your own themes
|
||||||
|
- Customize every color
|
||||||
|
- Save and share themes
|
||||||
|
- Import community themes
|
||||||
|
|
||||||
|
## Auto-Update System
|
||||||
|
|
||||||
|
Stay current with automatic updates:
|
||||||
|
|
||||||
|
- Background update checks
|
||||||
|
- Notification of new versions
|
||||||
|
- One-click update
|
||||||
|
- Update history
|
||||||
|
- Support for multiple update sources (GitHub, Gitea)
|
||||||
|
|
||||||
|
## Data Backup
|
||||||
|
|
||||||
|
Secure your data with Git-based backup:
|
||||||
|
|
||||||
|
- Automatic backups
|
||||||
|
- Manual backup triggers
|
||||||
|
- Support for GitHub and Gitea
|
||||||
|
- Multiple authentication methods (SSH, Token, Password)
|
||||||
|
- Configurable backup intervals
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
Extensive keyboard support:
|
||||||
|
|
||||||
|
- Customizable shortcuts
|
||||||
|
- Vim/Emacs keybindings (planned)
|
||||||
|
- Quick command palette
|
||||||
|
- Context-aware shortcuts
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Built for speed:
|
||||||
|
|
||||||
|
- Fast startup time
|
||||||
|
- Smooth scrolling
|
||||||
|
- Efficient memory usage
|
||||||
|
- Large file support
|
||||||
|
|
||||||
|
## Privacy & Security
|
||||||
|
|
||||||
|
Your data is safe:
|
||||||
|
|
||||||
|
- Local-first storage
|
||||||
|
- Optional cloud backup
|
||||||
|
- No telemetry or tracking
|
||||||
|
- Open source codebase
|
||||||
|
|
||||||
107
frontend/docs/src/guide/getting-started.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
Learn the basics of using voidraft and create your first document.
|
||||||
|
|
||||||
|
## The Editor Interface
|
||||||
|
|
||||||
|
When you open voidraft, you'll see:
|
||||||
|
|
||||||
|
- **Main Editor**: The central area where you write and edit
|
||||||
|
- **Toolbar**: Quick access to common actions
|
||||||
|
- **Status Bar**: Shows current block language and other info
|
||||||
|
|
||||||
|
## Creating Code Blocks
|
||||||
|
|
||||||
|
voidraft uses a block-based editing system. Each block can have a different language:
|
||||||
|
|
||||||
|
1. Press `Ctrl+Enter` to create a new block
|
||||||
|
2. Type `∞∞∞` followed by a language name (e.g., `∞∞∞javascript`)
|
||||||
|
3. Start coding in that block
|
||||||
|
|
||||||
|
### Supported Languages
|
||||||
|
|
||||||
|
voidraft supports 30+ programming languages including:
|
||||||
|
- JavaScript, TypeScript
|
||||||
|
- Python, Go, Rust
|
||||||
|
- HTML, CSS, Sass
|
||||||
|
- SQL, YAML, JSON
|
||||||
|
- And many more...
|
||||||
|
|
||||||
|
## Basic Operations
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
- `Ctrl+Up/Down`: Move between blocks
|
||||||
|
- `Ctrl+Home/End`: Jump to first/last block
|
||||||
|
- `Ctrl+F`: Search within document
|
||||||
|
|
||||||
|
### Editing
|
||||||
|
|
||||||
|
- `Ctrl+D`: Duplicate current line
|
||||||
|
- `Ctrl+/`: Toggle comment
|
||||||
|
- `Alt+Up/Down`: Move line up/down
|
||||||
|
- `Ctrl+Shift+F`: Format code (if language supports Prettier)
|
||||||
|
|
||||||
|
### Block Management
|
||||||
|
|
||||||
|
- `Ctrl+Enter`: Create new block
|
||||||
|
- `Ctrl+Shift+Enter`: Create block above
|
||||||
|
- `Alt+Delete`: Delete current block
|
||||||
|
|
||||||
|
## Using the HTTP Client
|
||||||
|
|
||||||
|
voidraft includes a built-in HTTP client for testing APIs:
|
||||||
|
|
||||||
|
1. Create a block with HTTP language
|
||||||
|
2. Write your HTTP request:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST "https://api.example.com/users" {
|
||||||
|
content-type: "application/json"
|
||||||
|
|
||||||
|
@json {
|
||||||
|
name: "John Doe",
|
||||||
|
email: "john@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Click the run button to execute the request
|
||||||
|
4. View the response inline
|
||||||
|
|
||||||
|
## Multi-Window Support
|
||||||
|
|
||||||
|
Work on multiple documents simultaneously:
|
||||||
|
|
||||||
|
1. Go to `File > New Window` (or `Ctrl+Shift+N`)
|
||||||
|
2. Each window is independent
|
||||||
|
3. Changes are saved automatically
|
||||||
|
|
||||||
|
## Customizing Themes
|
||||||
|
|
||||||
|
Personalize your editor:
|
||||||
|
|
||||||
|
1. Open Settings (`Ctrl+,`)
|
||||||
|
2. Go to Appearance
|
||||||
|
3. Choose a theme or create your own
|
||||||
|
4. Customize colors to your preference
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
Learn essential shortcuts:
|
||||||
|
|
||||||
|
| Action | Shortcut |
|
||||||
|
|--------|----------|
|
||||||
|
| New Window | `Ctrl+Shift+N` |
|
||||||
|
| Search | `Ctrl+F` |
|
||||||
|
| Replace | `Ctrl+H` |
|
||||||
|
| Format Code | `Ctrl+Shift+F` |
|
||||||
|
| Toggle Theme | `Ctrl+Shift+T` |
|
||||||
|
| Command Palette | `Ctrl+Shift+P` |
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you know the basics:
|
||||||
|
|
||||||
|
- Explore [Features](/guide/features) in detail
|
||||||
|
|
||||||
63
frontend/docs/src/guide/installation.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
This guide will help you install voidraft on your system.
|
||||||
|
|
||||||
|
## System Requirements
|
||||||
|
|
||||||
|
- **Operating System**: Windows 10 or later (macOS and Linux support planned)
|
||||||
|
- **RAM**: 4GB minimum, 8GB recommended
|
||||||
|
- **Disk Space**: 200MB free space
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
Visit the [releases page](https://github.com/landaiqing/voidraft/releases) and download the latest version for your platform:
|
||||||
|
|
||||||
|
- **Windows**: `voidraft-windows-amd64-installer.exe`
|
||||||
|
|
||||||
|
## Installation Steps
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
1. Download the installer from the releases page
|
||||||
|
2. Run the `voidraft-windows-amd64-installer.exe` file
|
||||||
|
3. Follow the installation wizard
|
||||||
|
4. Launch voidraft from the Start menu or desktop shortcut
|
||||||
|
|
||||||
|
## First Launch
|
||||||
|
|
||||||
|
When you first launch voidraft:
|
||||||
|
|
||||||
|
1. The application will create a data directory to store your documents
|
||||||
|
2. You'll see the main editor interface with a welcome block
|
||||||
|
3. Start typing or create your first code block!
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
voidraft stores its configuration and data in:
|
||||||
|
|
||||||
|
- **Windows**: `%APPDATA%/voidraft/`
|
||||||
|
|
||||||
|
You can customize various settings including:
|
||||||
|
- Editor theme (dark/light mode)
|
||||||
|
- Code formatting preferences
|
||||||
|
- Backup settings
|
||||||
|
- Keyboard shortcuts
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
voidraft includes an auto-update feature that will notify you when new versions are available. You can:
|
||||||
|
|
||||||
|
- Check for updates manually from the settings
|
||||||
|
- Enable automatic updates
|
||||||
|
- Choose your preferred update source
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you encounter any issues during installation:
|
||||||
|
|
||||||
|
1. Make sure you have administrator privileges
|
||||||
|
2. Check that your antivirus isn't blocking the installation
|
||||||
|
3. Visit our [GitHub issues](https://github.com/landaiqing/voidraft/issues) page for help
|
||||||
|
|
||||||
|
Next: [Getting Started →](/guide/getting-started)
|
||||||
|
|
||||||
50
frontend/docs/src/guide/introduction.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
Welcome to voidraft - an elegant text snippet recording tool designed specifically for developers.
|
||||||
|
|
||||||
|
## What is voidraft?
|
||||||
|
|
||||||
|
voidraft is a modern desktop application that helps developers manage text snippets, code blocks, API responses, meeting notes, and daily to-do lists. It provides a smooth and elegant editing experience with powerful features tailored for development workflows.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Block-Based Editing
|
||||||
|
|
||||||
|
voidraft uses a unique block-based editing system inspired by Heynote. You can split your content into independent code blocks, each with:
|
||||||
|
- Different programming language settings
|
||||||
|
- Syntax highlighting
|
||||||
|
- Independent formatting
|
||||||
|
- Easy navigation between blocks
|
||||||
|
|
||||||
|
### Developer Tools
|
||||||
|
|
||||||
|
- **HTTP Client**: Test APIs directly within the editor
|
||||||
|
- **Code Formatting**: Built-in Prettier support for multiple languages
|
||||||
|
- **Syntax Highlighting**: Support for 30+ programming languages
|
||||||
|
- **Auto Language Detection**: Automatically recognizes code block language types
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
- **Custom Themes**: Create and save your own editor themes
|
||||||
|
- **Extensions**: Rich set of editor extensions including minimap, rainbow brackets, color picker, and more
|
||||||
|
- **Multi-Window**: Work on multiple documents simultaneously
|
||||||
|
|
||||||
|
### Data Management
|
||||||
|
|
||||||
|
- **Git-Based Backup**: Automatic backup using Git repositories
|
||||||
|
- **Cloud Sync**: Sync your data across devices
|
||||||
|
- **Auto-Update**: Stay up-to-date with the latest features
|
||||||
|
|
||||||
|
## Why voidraft?
|
||||||
|
|
||||||
|
- **Developer-Focused**: Built with developers' needs in mind
|
||||||
|
- **Modern Stack**: Uses cutting-edge technologies (Wails3, Vue 3, CodeMirror 6)
|
||||||
|
- **Cross-Platform**: Works on Windows (macOS and Linux support planned)
|
||||||
|
- **Open Source**: MIT licensed, community-driven development
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Ready to start? Download the latest version from our [releases page](https://github.com/landaiqing/voidraft/releases) or continue reading the documentation to learn more.
|
||||||
|
|
||||||
|
Next: [Installation →](/guide/installation)
|
||||||
|
|
||||||
56
frontend/docs/src/index.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: "voidraft"
|
||||||
|
text: "An elegant text snippet recording tool"
|
||||||
|
tagline: Designed for developers, built with modern technology
|
||||||
|
image:
|
||||||
|
src: /img/hero.png
|
||||||
|
alt: "voidraft"
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Get Started
|
||||||
|
link: https://github.com/landaiqing/voidraft/releases
|
||||||
|
- theme: alt
|
||||||
|
text: Documentation
|
||||||
|
link: /guide/introduction
|
||||||
|
|
||||||
|
features:
|
||||||
|
- icon: 📝
|
||||||
|
title: Block-Based Editing
|
||||||
|
details: Split your content into independent code blocks, each with different language settings. Inspired by Heynote's innovative design philosophy.
|
||||||
|
|
||||||
|
- icon: 🎨
|
||||||
|
title: Syntax Highlighting
|
||||||
|
details: Built-in support for 30+ programming languages with automatic language detection and Prettier integration for code formatting.
|
||||||
|
|
||||||
|
- icon: 🌐
|
||||||
|
title: HTTP Client
|
||||||
|
details: Integrated HTTP client with support for multiple request formats including JSON, FormData, XML, and more. Test APIs directly within the editor.
|
||||||
|
|
||||||
|
- icon: 🎯
|
||||||
|
title: Multi-Window Support
|
||||||
|
details: Work on multiple documents simultaneously with independent windows. Each window maintains its own state and configuration.
|
||||||
|
|
||||||
|
- icon: 🎭
|
||||||
|
title: Customizable Themes
|
||||||
|
details: Full theme customization support with dark/light modes. Create and save your own editor themes to match your preferences.
|
||||||
|
|
||||||
|
- icon: 🔧
|
||||||
|
title: Rich Extensions
|
||||||
|
details: VSCode-style search and replace, rainbow brackets, minimap, color picker, translation tool, text highlighting, and more.
|
||||||
|
|
||||||
|
- icon: 🔄
|
||||||
|
title: Auto-Update System
|
||||||
|
details: Built-in self-update mechanism with support for multiple update sources. Stay up-to-date with the latest features and improvements.
|
||||||
|
|
||||||
|
- icon: ☁️
|
||||||
|
title: Git-Based Backup
|
||||||
|
details: Automatic data backup using Git repositories. Supports GitHub, Gitea, with multiple authentication methods including SSH and tokens.
|
||||||
|
|
||||||
|
- icon: ⚡
|
||||||
|
title: Modern Architecture
|
||||||
|
details: Built with Wails3, Vue 3, and CodeMirror 6. Cross-platform desktop application with native performance and modern UI.
|
||||||
|
|
||||||
|
---
|
||||||
BIN
frontend/docs/src/public/icon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/docs/src/public/icon/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
frontend/docs/src/public/icon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
3
frontend/docs/src/public/icon/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
21
frontend/docs/src/public/icon/site.webmanifest
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "voidraft",
|
||||||
|
"short_name": "voidraft",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/img/web-app-manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/img/web-app-manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
BIN
frontend/docs/src/public/icon/web-app-manifest-192x192.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/docs/src/public/icon/web-app-manifest-512x512.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
frontend/docs/src/public/img/hero.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
163
frontend/docs/src/zh/guide/features.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# 功能特性
|
||||||
|
|
||||||
|
探索 voidraft 的强大功能,让它成为开发者的优秀工具。
|
||||||
|
|
||||||
|
## 块状编辑
|
||||||
|
|
||||||
|
voidraft 的核心功能是其块状编辑系统:
|
||||||
|
|
||||||
|
- 每个块可以有不同的编程语言
|
||||||
|
- 块之间由分隔符分隔(`∞∞∞语言`)
|
||||||
|
- 快速在块之间导航
|
||||||
|
- 独立格式化每个块
|
||||||
|
|
||||||
|
## 语法高亮
|
||||||
|
|
||||||
|
支持 30+ 种语言的专业语法高亮:
|
||||||
|
|
||||||
|
- 自动语言检测
|
||||||
|
- 可自定义配色方案
|
||||||
|
- 支持嵌套语言
|
||||||
|
- 代码折叠支持
|
||||||
|
|
||||||
|
## HTTP 客户端
|
||||||
|
|
||||||
|
用于 API 测试的内置 HTTP 客户端:
|
||||||
|
|
||||||
|
### 请求类型
|
||||||
|
- GET、POST、PUT、DELETE、PATCH
|
||||||
|
- 自定义请求头
|
||||||
|
- 多种请求体格式:JSON、FormData、URL 编码、XML、文本
|
||||||
|
|
||||||
|
### 请求变量
|
||||||
|
定义和重用变量:
|
||||||
|
|
||||||
|
```http
|
||||||
|
@var {
|
||||||
|
baseUrl: "https://api.example.com",
|
||||||
|
token: "your-api-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
GET "{{baseUrl}}/users" {
|
||||||
|
authorization: "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应处理
|
||||||
|
- 查看格式化的 JSON 响应
|
||||||
|
- 查看响应时间和大小
|
||||||
|
- 检查响应头
|
||||||
|
- 保存响应以供日后使用
|
||||||
|
|
||||||
|
## 代码格式化
|
||||||
|
|
||||||
|
集成 Prettier 支持:
|
||||||
|
|
||||||
|
- 保存时格式化(可选)
|
||||||
|
- 格式化选区或整个块
|
||||||
|
- 支持 JavaScript、TypeScript、CSS、HTML、JSON 等
|
||||||
|
- 可自定义格式化规则
|
||||||
|
|
||||||
|
## 编辑器扩展
|
||||||
|
|
||||||
|
### VSCode 风格搜索
|
||||||
|
- 查找和替换,支持正则表达式
|
||||||
|
- 区分大小写和全字匹配选项
|
||||||
|
- 跨所有块搜索
|
||||||
|
|
||||||
|
### 小地图
|
||||||
|
- 文档的鸟瞰图
|
||||||
|
- 快速导航
|
||||||
|
- 可自定义大小和位置
|
||||||
|
|
||||||
|
### 彩虹括号
|
||||||
|
- 彩色括号配对
|
||||||
|
- 更容易匹配括号
|
||||||
|
- 可自定义颜色
|
||||||
|
|
||||||
|
### 颜色选择器
|
||||||
|
- 可视化颜色选择
|
||||||
|
- 支持 hex、RGB、HSL
|
||||||
|
- 实时预览
|
||||||
|
|
||||||
|
### 翻译工具
|
||||||
|
- 翻译选定的文本
|
||||||
|
- 支持多种语言
|
||||||
|
- 快速键盘访问
|
||||||
|
|
||||||
|
### 文本高亮
|
||||||
|
- 高亮重要文本
|
||||||
|
- 多种高亮颜色
|
||||||
|
- 持久化高亮
|
||||||
|
|
||||||
|
## 多窗口支持
|
||||||
|
|
||||||
|
高效使用多个窗口:
|
||||||
|
|
||||||
|
- 每个窗口都是独立的
|
||||||
|
- 独立的文档
|
||||||
|
- 同步的设置
|
||||||
|
- 窗口状态持久化
|
||||||
|
|
||||||
|
## 主题自定义
|
||||||
|
|
||||||
|
完全控制编辑器外观:
|
||||||
|
|
||||||
|
### 内置主题
|
||||||
|
- 深色模式
|
||||||
|
- 浅色模式
|
||||||
|
- 根据系统自动切换
|
||||||
|
|
||||||
|
### 自定义主题
|
||||||
|
- 创建你自己的主题
|
||||||
|
- 自定义每种颜色
|
||||||
|
- 保存和分享主题
|
||||||
|
- 导入社区主题
|
||||||
|
|
||||||
|
## 自动更新系统
|
||||||
|
|
||||||
|
通过自动更新保持最新:
|
||||||
|
|
||||||
|
- 后台更新检查
|
||||||
|
- 新版本通知
|
||||||
|
- 一键更新
|
||||||
|
- 更新历史
|
||||||
|
- 支持多个更新源(GitHub、Gitea)
|
||||||
|
|
||||||
|
## 数据备份
|
||||||
|
|
||||||
|
使用基于 Git 的备份保护你的数据:
|
||||||
|
|
||||||
|
- 自动备份
|
||||||
|
- 手动触发备份
|
||||||
|
- 支持 GitHub 和 Gitea
|
||||||
|
- 多种认证方式(SSH、Token、密码)
|
||||||
|
- 可配置备份间隔
|
||||||
|
|
||||||
|
## 键盘快捷键
|
||||||
|
|
||||||
|
广泛的键盘支持:
|
||||||
|
|
||||||
|
- 可自定义快捷键
|
||||||
|
- Vim/Emacs 按键绑定(计划中)
|
||||||
|
- 快速命令面板
|
||||||
|
- 上下文感知快捷键
|
||||||
|
|
||||||
|
## 性能
|
||||||
|
|
||||||
|
专为速度而构建:
|
||||||
|
|
||||||
|
- 快速启动时间
|
||||||
|
- 流畅滚动
|
||||||
|
- 高效内存使用
|
||||||
|
- 支持大文件
|
||||||
|
|
||||||
|
## 隐私与安全
|
||||||
|
|
||||||
|
你的数据是安全的:
|
||||||
|
|
||||||
|
- 本地优先存储
|
||||||
|
- 可选云备份
|
||||||
|
- 无遥测或跟踪
|
||||||
|
- 开源代码库
|
||||||
|
|
||||||
107
frontend/docs/src/zh/guide/getting-started.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# 快速开始
|
||||||
|
|
||||||
|
学习使用 voidraft 的基础知识并创建你的第一个文档。
|
||||||
|
|
||||||
|
## 编辑器界面
|
||||||
|
|
||||||
|
当你打开 voidraft 时,你将看到:
|
||||||
|
|
||||||
|
- **主编辑器**:编写和编辑的中心区域
|
||||||
|
- **工具栏**:快速访问常用操作
|
||||||
|
- **状态栏**:显示当前块的语言和其他信息
|
||||||
|
|
||||||
|
## 创建代码块
|
||||||
|
|
||||||
|
voidraft 使用基于块的编辑系统。每个块可以有不同的语言:
|
||||||
|
|
||||||
|
1. 按 `Ctrl+Enter` 创建新块
|
||||||
|
2. 输入 `∞∞∞` 后跟语言名称(例如 `∞∞∞javascript`)
|
||||||
|
3. 在该块中开始编码
|
||||||
|
|
||||||
|
### 支持的语言
|
||||||
|
|
||||||
|
voidraft 支持 30+ 种编程语言,包括:
|
||||||
|
- JavaScript、TypeScript
|
||||||
|
- Python、Go、Rust
|
||||||
|
- HTML、CSS、Sass
|
||||||
|
- SQL、YAML、JSON
|
||||||
|
- 以及更多...
|
||||||
|
|
||||||
|
## 基本操作
|
||||||
|
|
||||||
|
### 导航
|
||||||
|
|
||||||
|
- `Ctrl+Up/Down`:在块之间移动
|
||||||
|
- `Ctrl+Home/End`:跳转到第一个/最后一个块
|
||||||
|
- `Ctrl+F`:在文档中搜索
|
||||||
|
|
||||||
|
### 编辑
|
||||||
|
|
||||||
|
- `Ctrl+D`:复制当前行
|
||||||
|
- `Ctrl+/`:切换注释
|
||||||
|
- `Alt+Up/Down`:向上/向下移动行
|
||||||
|
- `Ctrl+Shift+F`:格式化代码(如果语言支持 Prettier)
|
||||||
|
|
||||||
|
### 块管理
|
||||||
|
|
||||||
|
- `Ctrl+Enter`:创建新块
|
||||||
|
- `Ctrl+Shift+Enter`:在上方创建块
|
||||||
|
- `Alt+Delete`:删除当前块
|
||||||
|
|
||||||
|
## 使用 HTTP 客户端
|
||||||
|
|
||||||
|
voidraft 包含用于测试 API 的内置 HTTP 客户端:
|
||||||
|
|
||||||
|
1. 创建一个 HTTP 语言的块
|
||||||
|
2. 编写你的 HTTP 请求:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST "https://api.example.com/users" {
|
||||||
|
content-type: "application/json"
|
||||||
|
|
||||||
|
@json {
|
||||||
|
name: "张三",
|
||||||
|
email: "zhangsan@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 点击运行按钮执行请求
|
||||||
|
4. 内联查看响应
|
||||||
|
|
||||||
|
## 多窗口支持
|
||||||
|
|
||||||
|
同时处理多个文档:
|
||||||
|
|
||||||
|
1. 转到 `文件 > 新建窗口`(或 `Ctrl+Shift+N`)
|
||||||
|
2. 每个窗口都是独立的
|
||||||
|
3. 更改会自动保存
|
||||||
|
|
||||||
|
## 自定义主题
|
||||||
|
|
||||||
|
个性化你的编辑器:
|
||||||
|
|
||||||
|
1. 打开设置(`Ctrl+,`)
|
||||||
|
2. 转到外观
|
||||||
|
3. 选择主题或创建自己的主题
|
||||||
|
4. 根据你的偏好自定义颜色
|
||||||
|
|
||||||
|
## 键盘快捷键
|
||||||
|
|
||||||
|
学习基本快捷键:
|
||||||
|
|
||||||
|
| 操作 | 快捷键 |
|
||||||
|
|-----|--------|
|
||||||
|
| 新建窗口 | `Ctrl+Shift+N` |
|
||||||
|
| 搜索 | `Ctrl+F` |
|
||||||
|
| 替换 | `Ctrl+H` |
|
||||||
|
| 格式化代码 | `Ctrl+Shift+F` |
|
||||||
|
| 切换主题 | `Ctrl+Shift+T` |
|
||||||
|
| 命令面板 | `Ctrl+Shift+P` |
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
现在你已经了解了基础知识:
|
||||||
|
|
||||||
|
- 详细探索[功能特性](/zh/guide/features)
|
||||||
|
|
||||||
63
frontend/docs/src/zh/guide/installation.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# 安装
|
||||||
|
|
||||||
|
本指南将帮助你在系统上安装 voidraft。
|
||||||
|
|
||||||
|
## 系统要求
|
||||||
|
|
||||||
|
- **操作系统**:Windows 10 或更高版本(macOS 和 Linux 支持计划中)
|
||||||
|
- **内存**:最低 4GB,推荐 8GB
|
||||||
|
- **磁盘空间**:200MB 可用空间
|
||||||
|
|
||||||
|
## 下载
|
||||||
|
|
||||||
|
访问[发布页面](https://github.com/landaiqing/voidraft/releases)并下载适合你平台的最新版本:
|
||||||
|
|
||||||
|
- **Windows**:`voidraft-windows-amd64-installer.exe`
|
||||||
|
|
||||||
|
## 安装步骤
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
1. 从发布页面下载安装程序
|
||||||
|
2. 运行 `voidraft-windows-amd64-installer.exe` 文件
|
||||||
|
3. 按照安装向导操作
|
||||||
|
4. 从开始菜单或桌面快捷方式启动 voidraft
|
||||||
|
|
||||||
|
## 首次启动
|
||||||
|
|
||||||
|
首次启动 voidraft 时:
|
||||||
|
|
||||||
|
1. 应用程序将创建一个数据目录来存储你的文档
|
||||||
|
2. 你将看到带有欢迎块的主编辑器界面
|
||||||
|
3. 开始输入或创建你的第一个代码块!
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
voidraft 将其配置和数据存储在:
|
||||||
|
|
||||||
|
- **Windows**:`%APPDATA%/voidraft/`
|
||||||
|
|
||||||
|
你可以自定义各种设置,包括:
|
||||||
|
- 编辑器主题(深色/浅色模式)
|
||||||
|
- 代码格式化偏好
|
||||||
|
- 备份设置
|
||||||
|
- 键盘快捷键
|
||||||
|
|
||||||
|
## 更新
|
||||||
|
|
||||||
|
voidraft 包含自动更新功能,会在有新版本时通知你。你可以:
|
||||||
|
|
||||||
|
- 从设置中手动检查更新
|
||||||
|
- 启用自动更新
|
||||||
|
- 选择首选的更新源
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
如果在安装过程中遇到任何问题:
|
||||||
|
|
||||||
|
1. 确保你有管理员权限
|
||||||
|
2. 检查杀毒软件是否阻止了安装
|
||||||
|
3. 访问我们的 [GitHub issues](https://github.com/landaiqing/voidraft/issues) 页面寻求帮助
|
||||||
|
|
||||||
|
下一步:[快速开始 →](/zh/guide/getting-started)
|
||||||
|
|
||||||
50
frontend/docs/src/zh/guide/introduction.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 简介
|
||||||
|
|
||||||
|
欢迎使用 voidraft —— 一个专为开发者设计的优雅文本片段记录工具。
|
||||||
|
|
||||||
|
## 什么是 voidraft?
|
||||||
|
|
||||||
|
voidraft 是一个现代化的桌面应用程序,帮助开发者管理文本片段、代码块、API 响应、会议笔记和日常待办事项。它为开发工作流程提供了流畅而优雅的编辑体验和强大的功能。
|
||||||
|
|
||||||
|
## 核心特性
|
||||||
|
|
||||||
|
### 块状编辑模式
|
||||||
|
|
||||||
|
voidraft 使用受 Heynote 启发的独特块状编辑系统。你可以将内容分割为独立的代码块,每个块具有:
|
||||||
|
- 不同的编程语言设置
|
||||||
|
- 语法高亮
|
||||||
|
- 独立格式化
|
||||||
|
- 轻松在块之间导航
|
||||||
|
|
||||||
|
### 开发者工具
|
||||||
|
|
||||||
|
- **HTTP 客户端**:直接在编辑器中测试 API
|
||||||
|
- **代码格式化**:内置 Prettier 支持多种语言
|
||||||
|
- **语法高亮**:支持 30+ 种编程语言
|
||||||
|
- **自动语言检测**:自动识别代码块语言类型
|
||||||
|
|
||||||
|
### 自定义
|
||||||
|
|
||||||
|
- **自定义主题**:创建并保存你自己的编辑器主题
|
||||||
|
- **扩展功能**:丰富的编辑器扩展,包括小地图、彩虹括号、颜色选择器等
|
||||||
|
- **多窗口**:同时处理多个文档
|
||||||
|
|
||||||
|
### 数据管理
|
||||||
|
|
||||||
|
- **Git 备份**:使用 Git 仓库自动备份
|
||||||
|
- **云同步**:跨设备同步你的数据
|
||||||
|
- **自动更新**:及时获取最新功能
|
||||||
|
|
||||||
|
## 为什么选择 voidraft?
|
||||||
|
|
||||||
|
- **专注开发者**:考虑开发者需求而构建
|
||||||
|
- **现代技术栈**:使用前沿技术(Wails3、Vue 3、CodeMirror 6)
|
||||||
|
- **跨平台**:支持 Windows(macOS 和 Linux 支持计划中)
|
||||||
|
- **开源**:MIT 许可证,社区驱动开发
|
||||||
|
|
||||||
|
## 开始使用
|
||||||
|
|
||||||
|
准备好开始了吗?从我们的[发布页面](https://github.com/landaiqing/voidraft/releases)下载最新版本,或继续阅读文档了解更多。
|
||||||
|
|
||||||
|
下一步:[安装 →](/zh/guide/installation)
|
||||||
|
|
||||||
56
frontend/docs/src/zh/index.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: "voidraft"
|
||||||
|
text: "优雅的文本片段记录工具"
|
||||||
|
tagline: 为开发者设计,用现代技术打造
|
||||||
|
image:
|
||||||
|
src: /img/hero.png
|
||||||
|
alt: "voidraft"
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: 开始使用
|
||||||
|
link: https://github.com/landaiqing/voidraft/releases
|
||||||
|
- theme: alt
|
||||||
|
text: 使用文档
|
||||||
|
link: /zh/guide/introduction
|
||||||
|
|
||||||
|
features:
|
||||||
|
- icon: 📝
|
||||||
|
title: 块状编辑模式
|
||||||
|
details: 将内容分割为独立的代码块,每个块可设置不同语言。继承了 Heynote 优雅的块状编辑理念。
|
||||||
|
|
||||||
|
- icon: 🎨
|
||||||
|
title: 语法高亮
|
||||||
|
details: 内置支持 30+ 种编程语言的语法高亮,自动语言检测,集成 Prettier 代码格式化工具。
|
||||||
|
|
||||||
|
- icon: 🌐
|
||||||
|
title: HTTP 客户端
|
||||||
|
details: 集成 HTTP 客户端,支持 JSON、FormData、XML 等多种请求格式。直接在编辑器中测试 API。
|
||||||
|
|
||||||
|
- icon: 🎯
|
||||||
|
title: 多窗口支持
|
||||||
|
details: 同时编辑多个文档,每个窗口独立维护自己的状态和配置。
|
||||||
|
|
||||||
|
- icon: 🎭
|
||||||
|
title: 主题自定义
|
||||||
|
details: 完整的主题自定义支持,支持深色/浅色模式。创建并保存你自己的编辑器主题。
|
||||||
|
|
||||||
|
- icon: 🔧
|
||||||
|
title: 丰富的扩展
|
||||||
|
details: VSCode 风格搜索替换、彩虹括号、小地图、颜色选择器、翻译工具、文本高亮等实用扩展。
|
||||||
|
|
||||||
|
- icon: 🔄
|
||||||
|
title: 自动更新系统
|
||||||
|
details: 内置自我更新机制,支持多个更新源。及时获取最新功能和改进。
|
||||||
|
|
||||||
|
- icon: ☁️
|
||||||
|
title: Git 备份
|
||||||
|
details: 基于 Git 的自动数据备份。支持 GitHub、Gitea,提供 SSH、Token 等多种认证方式。
|
||||||
|
|
||||||
|
- icon: ⚡
|
||||||
|
title: 现代化架构
|
||||||
|
details: 采用 Wails3、Vue 3 和 CodeMirror 6 构建。跨平台桌面应用,原生性能,现代化界面。
|
||||||
|
|
||||||
|
---
|
||||||
@@ -50,7 +50,11 @@ export default defineConfig([
|
|||||||
'.local',
|
'.local',
|
||||||
'/bin',
|
'/bin',
|
||||||
'Dockerfile',
|
'Dockerfile',
|
||||||
'**/bindings/'
|
'**/bindings/',
|
||||||
|
'*.js',
|
||||||
|
'**/*.js',
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/*.mjs',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
2108
frontend/package-lock.json
generated
@@ -9,82 +9,89 @@
|
|||||||
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vue-tsc && cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --mode production",
|
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vue-tsc && cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --mode production",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"lint:fix": "eslint --fix"
|
"lint:fix": "eslint --fix",
|
||||||
|
"build:lang-parser": "node src/views/editor/extensions/codeblock/lang-parser/build-parser.js",
|
||||||
|
"test": "vitest",
|
||||||
|
"docs:dev": "vitepress dev docs",
|
||||||
|
"docs:build": "vitepress build docs",
|
||||||
|
"docs:preview": "vitepress preview docs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.18.7",
|
"@codemirror/autocomplete": "^6.19.1",
|
||||||
"@codemirror/commands": "^6.8.1",
|
"@codemirror/commands": "^6.10.0",
|
||||||
"@codemirror/lang-angular": "^0.1.4",
|
"@codemirror/lang-angular": "^0.1.4",
|
||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
"@codemirror/lang-go": "^6.0.1",
|
"@codemirror/lang-go": "^6.0.1",
|
||||||
"@codemirror/lang-html": "^6.4.10",
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
"@codemirror/lang-java": "^6.0.2",
|
"@codemirror/lang-java": "^6.0.2",
|
||||||
"@codemirror/lang-javascript": "^6.2.4",
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
"@codemirror/lang-less": "^6.0.2",
|
"@codemirror/lang-less": "^6.0.2",
|
||||||
"@codemirror/lang-lezer": "^6.0.2",
|
"@codemirror/lang-lezer": "^6.0.2",
|
||||||
"@codemirror/lang-liquid": "^6.3.0",
|
"@codemirror/lang-liquid": "^6.3.0",
|
||||||
"@codemirror/lang-markdown": "^6.3.4",
|
"@codemirror/lang-markdown": "^6.5.0",
|
||||||
"@codemirror/lang-php": "^6.0.2",
|
"@codemirror/lang-php": "^6.0.2",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@codemirror/lang-rust": "^6.0.2",
|
"@codemirror/lang-rust": "^6.0.2",
|
||||||
"@codemirror/lang-sass": "^6.0.2",
|
"@codemirror/lang-sass": "^6.0.2",
|
||||||
"@codemirror/lang-sql": "^6.9.1",
|
"@codemirror/lang-sql": "^6.10.0",
|
||||||
"@codemirror/lang-vue": "^0.1.3",
|
"@codemirror/lang-vue": "^0.1.3",
|
||||||
"@codemirror/lang-wast": "^6.0.2",
|
"@codemirror/lang-wast": "^6.0.2",
|
||||||
"@codemirror/lang-yaml": "^6.1.2",
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.11.3",
|
"@codemirror/language": "^6.11.3",
|
||||||
"@codemirror/language-data": "^6.5.1",
|
"@codemirror/language-data": "^6.5.2",
|
||||||
"@codemirror/legacy-modes": "^6.5.1",
|
"@codemirror/legacy-modes": "^6.5.2",
|
||||||
"@codemirror/lint": "^6.8.5",
|
"@codemirror/lint": "^6.9.1",
|
||||||
"@codemirror/search": "^6.5.11",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/view": "^6.38.2",
|
"@codemirror/view": "^6.38.6",
|
||||||
"@cospaia/prettier-plugin-clojure": "^0.0.2",
|
"@cospaia/prettier-plugin-clojure": "^0.0.2",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.3",
|
||||||
"@lezer/lr": "^1.4.2",
|
"@lezer/lr": "^1.4.2",
|
||||||
"@prettier/plugin-xml": "^3.4.2",
|
"@prettier/plugin-xml": "^3.4.2",
|
||||||
|
"@replit/codemirror-lang-svelte": "^6.0.0",
|
||||||
"@toml-tools/lexer": "^1.0.0",
|
"@toml-tools/lexer": "^1.0.0",
|
||||||
"@toml-tools/parser": "^1.0.0",
|
"@toml-tools/parser": "^1.0.0",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"codemirror-lang-elixir": "^4.0.0",
|
"codemirror-lang-elixir": "^4.0.0",
|
||||||
"colors-named": "^1.0.2",
|
"colors-named": "^1.0.2",
|
||||||
"colors-named-hex": "^1.0.2",
|
"colors-named-hex": "^1.0.2",
|
||||||
"franc-min": "^6.2.0",
|
|
||||||
"groovy-beautify": "^0.0.17",
|
"groovy-beautify": "^0.0.17",
|
||||||
"hsl-matcher": "^1.2.4",
|
"hsl-matcher": "^1.2.4",
|
||||||
"java-parser": "^3.0.1",
|
"java-parser": "^3.0.1",
|
||||||
"jsox": "^1.2.123",
|
"jsox": "^1.2.123",
|
||||||
"linguist-languages": "^9.0.0",
|
"linguist-languages": "^9.1.0",
|
||||||
"php-parser": "^3.2.5",
|
"php-parser": "^3.2.5",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.5.0",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"remarkable": "^2.0.1",
|
"remarkable": "^2.0.1",
|
||||||
"sass": "^1.92.1",
|
"sass": "^1.93.3",
|
||||||
"vue": "^3.5.21",
|
"vue": "^3.5.22",
|
||||||
"vue-i18n": "^11.1.12",
|
"vue-i18n": "^11.1.12",
|
||||||
"vue-pick-colors": "^1.8.0",
|
"vue-pick-colors": "^1.8.0",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.35.0",
|
"@eslint/js": "^9.39.0",
|
||||||
"@lezer/generator": "^1.8.0",
|
"@lezer/generator": "^1.8.0",
|
||||||
"@types/node": "^24.3.1",
|
"@types/node": "^24.9.2",
|
||||||
"@types/remarkable": "^2.0.8",
|
"@types/remarkable": "^2.0.8",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@wailsio/runtime": "latest",
|
"@wailsio/runtime": "latest",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^10.1.0",
|
||||||
"eslint": "^9.35.0",
|
"eslint": "^9.39.0",
|
||||||
"eslint-plugin-vue": "^10.4.0",
|
"eslint-plugin-vue": "^10.5.1",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.5.0",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.43.0",
|
"typescript-eslint": "^8.46.2",
|
||||||
"unplugin-vue-components": "^29.0.0",
|
"unplugin-vue-components": "^30.0.0",
|
||||||
"vite": "^7.1.5",
|
"vite": "^7.1.12",
|
||||||
"vite-plugin-node-polyfills": "^0.24.0",
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
|
"vitepress": "^2.0.0-alpha.12",
|
||||||
|
"vitest": "^4.0.6",
|
||||||
"vue-eslint-parser": "^10.2.0",
|
"vue-eslint-parser": "^10.2.0",
|
||||||
"vue-tsc": "^3.0.6"
|
"vue-tsc": "^3.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {onMounted} from 'vue';
|
import {onBeforeMount} from 'vue';
|
||||||
import {useConfigStore} from '@/stores/configStore';
|
import {useConfigStore} from '@/stores/configStore';
|
||||||
import {useSystemStore} from '@/stores/systemStore';
|
import {useSystemStore} from '@/stores/systemStore';
|
||||||
import {useKeybindingStore} from '@/stores/keybindingStore';
|
import {useKeybindingStore} from '@/stores/keybindingStore';
|
||||||
import {useThemeStore} from '@/stores/themeStore';
|
import {useThemeStore} from '@/stores/themeStore';
|
||||||
import {useUpdateStore} from '@/stores/updateStore';
|
import {useUpdateStore} from '@/stores/updateStore';
|
||||||
import {useBackupStore} from '@/stores/backupStore';
|
|
||||||
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
|
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
|
||||||
|
import {useTranslationStore} from "@/stores/translationStore";
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const systemStore = useSystemStore();
|
const systemStore = useSystemStore();
|
||||||
const keybindingStore = useKeybindingStore();
|
const keybindingStore = useKeybindingStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const backupStore = useBackupStore();
|
const translationStore = useTranslationStore();
|
||||||
|
|
||||||
// 应用启动时加载配置和初始化系统信息
|
onBeforeMount(async () => {
|
||||||
onMounted(async () => {
|
|
||||||
// 并行初始化配置、系统信息和快捷键配置
|
// 并行初始化配置、系统信息和快捷键配置
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
configStore.initConfig(),
|
configStore.initConfig(),
|
||||||
@@ -26,10 +25,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 初始化语言和主题
|
// 初始化语言和主题
|
||||||
await configStore.initializeLanguage();
|
await configStore.initializeLanguage();
|
||||||
themeStore.initializeTheme();
|
await themeStore.initializeTheme();
|
||||||
|
await translationStore.loadTranslators();
|
||||||
// 初始化备份服务
|
|
||||||
await backupStore.initialize();
|
|
||||||
|
|
||||||
// 启动时检查更新
|
// 启动时检查更新
|
||||||
await updateStore.checkOnStartup();
|
await updateStore.checkOnStartup();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
--dark-toolbar-text: #ffffff;
|
--dark-toolbar-text: #ffffff;
|
||||||
--dark-toolbar-text-secondary: #cccccc;
|
--dark-toolbar-text-secondary: #cccccc;
|
||||||
--dark-toolbar-button-hover: #404040;
|
--dark-toolbar-button-hover: #404040;
|
||||||
|
--dark-tab-active-line: linear-gradient(90deg, #007acc 0%, #0099ff 100%);
|
||||||
--dark-bg-secondary: #0E1217;
|
--dark-bg-secondary: #0E1217;
|
||||||
--dark-text-secondary: #a0aec0;
|
--dark-text-secondary: #a0aec0;
|
||||||
--dark-text-muted: #666;
|
--dark-text-muted: #666;
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
--light-toolbar-text: #212529;
|
--light-toolbar-text: #212529;
|
||||||
--light-toolbar-text-secondary: #495057;
|
--light-toolbar-text-secondary: #495057;
|
||||||
--light-toolbar-button-hover: #e9ecef;
|
--light-toolbar-button-hover: #e9ecef;
|
||||||
|
--light-tab-active-line: linear-gradient(90deg, #0066cc 0%, #0088ff 100%);
|
||||||
--light-bg-secondary: #f7fef7;
|
--light-bg-secondary: #f7fef7;
|
||||||
--light-text-secondary: #374151;
|
--light-text-secondary: #374151;
|
||||||
--light-text-muted: #6b7280;
|
--light-text-muted: #6b7280;
|
||||||
@@ -73,6 +75,7 @@
|
|||||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||||
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
||||||
--toolbar-separator: var(--dark-toolbar-button-hover);
|
--toolbar-separator: var(--dark-toolbar-button-hover);
|
||||||
|
--tab-active-line: var(--dark-tab-active-line);
|
||||||
--bg-secondary: var(--dark-bg-secondary);
|
--bg-secondary: var(--dark-bg-secondary);
|
||||||
--text-secondary: var(--dark-text-secondary);
|
--text-secondary: var(--dark-text-secondary);
|
||||||
--text-muted: var(--dark-text-muted);
|
--text-muted: var(--dark-text-muted);
|
||||||
@@ -112,6 +115,7 @@
|
|||||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||||
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
||||||
--toolbar-separator: var(--dark-toolbar-button-hover);
|
--toolbar-separator: var(--dark-toolbar-button-hover);
|
||||||
|
--tab-active-line: var(--dark-tab-active-line);
|
||||||
--bg-secondary: var(--dark-bg-secondary);
|
--bg-secondary: var(--dark-bg-secondary);
|
||||||
--text-secondary: var(--dark-text-secondary);
|
--text-secondary: var(--dark-text-secondary);
|
||||||
--text-muted: var(--dark-text-muted);
|
--text-muted: var(--dark-text-muted);
|
||||||
@@ -149,6 +153,7 @@
|
|||||||
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
||||||
--toolbar-button-hover: var(--light-toolbar-button-hover);
|
--toolbar-button-hover: var(--light-toolbar-button-hover);
|
||||||
--toolbar-separator: var(--light-toolbar-button-hover);
|
--toolbar-separator: var(--light-toolbar-button-hover);
|
||||||
|
--tab-active-line: var(--light-tab-active-line);
|
||||||
--bg-secondary: var(--light-bg-secondary);
|
--bg-secondary: var(--light-bg-secondary);
|
||||||
--text-secondary: var(--light-text-secondary);
|
--text-secondary: var(--light-text-secondary);
|
||||||
--text-muted: var(--light-text-muted);
|
--text-muted: var(--light-text-muted);
|
||||||
@@ -185,6 +190,7 @@
|
|||||||
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
||||||
--toolbar-button-hover: var(--light-toolbar-button-hover);
|
--toolbar-button-hover: var(--light-toolbar-button-hover);
|
||||||
--toolbar-separator: var(--light-toolbar-button-hover);
|
--toolbar-separator: var(--light-toolbar-button-hover);
|
||||||
|
--tab-active-line: var(--light-tab-active-line);
|
||||||
--bg-secondary: var(--light-bg-secondary);
|
--bg-secondary: var(--light-bg-secondary);
|
||||||
--text-secondary: var(--light-text-secondary);
|
--text-secondary: var(--light-text-secondary);
|
||||||
--text-muted: var(--light-text-muted);
|
--text-muted: var(--light-text-muted);
|
||||||
@@ -220,6 +226,7 @@
|
|||||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||||
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
--toolbar-button-hover: var(--dark-toolbar-button-hover);
|
||||||
--toolbar-separator: var(--dark-toolbar-button-hover);
|
--toolbar-separator: var(--dark-toolbar-button-hover);
|
||||||
|
--tab-active-line: var(--dark-tab-active-line);
|
||||||
--bg-secondary: var(--dark-bg-secondary);
|
--bg-secondary: var(--dark-bg-secondary);
|
||||||
--text-secondary: var(--dark-text-secondary);
|
--text-secondary: var(--dark-text-secondary);
|
||||||
--text-muted: var(--dark-text-muted);
|
--text-muted: var(--dark-text-muted);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
|||||||
globalHotkey: 'general.globalHotkey',
|
globalHotkey: 'general.globalHotkey',
|
||||||
enableWindowSnap: 'general.enableWindowSnap',
|
enableWindowSnap: 'general.enableWindowSnap',
|
||||||
enableLoadingAnimation: 'general.enableLoadingAnimation',
|
enableLoadingAnimation: 'general.enableLoadingAnimation',
|
||||||
|
enableTabs: 'general.enableTabs',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
export const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
||||||
@@ -61,7 +62,8 @@ export const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
|||||||
|
|
||||||
export const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
export const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
||||||
language: 'appearance.language',
|
language: 'appearance.language',
|
||||||
systemTheme: 'appearance.systemTheme'
|
systemTheme: 'appearance.systemTheme',
|
||||||
|
currentTheme: 'appearance.currentTheme'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
export const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
||||||
@@ -113,11 +115,12 @@ export const DEFAULT_CONFIG: AppConfig = {
|
|||||||
},
|
},
|
||||||
enableWindowSnap: true,
|
enableWindowSnap: true,
|
||||||
enableLoadingAnimation: true,
|
enableLoadingAnimation: true,
|
||||||
|
enableTabs: false,
|
||||||
},
|
},
|
||||||
editing: {
|
editing: {
|
||||||
fontSize: CONFIG_LIMITS.fontSize.default,
|
fontSize: CONFIG_LIMITS.fontSize.default,
|
||||||
fontFamily: FONT_OPTIONS[0].value,
|
fontFamily: FONT_OPTIONS[0].value,
|
||||||
fontWeight: 'normal',
|
fontWeight: '400',
|
||||||
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
||||||
enableTabIndent: true,
|
enableTabIndent: true,
|
||||||
tabSize: CONFIG_LIMITS.tabSize.default,
|
tabSize: CONFIG_LIMITS.tabSize.default,
|
||||||
@@ -126,7 +129,8 @@ export const DEFAULT_CONFIG: AppConfig = {
|
|||||||
},
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
language: LanguageType.LangZhCN,
|
language: LanguageType.LangZhCN,
|
||||||
systemTheme: SystemThemeType.SystemThemeAuto
|
systemTheme: SystemThemeType.SystemThemeAuto,
|
||||||
|
currentTheme: 'default-dark'
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
|
|||||||
13
frontend/src/common/constant/editor.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 编辑器相关常量配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编辑器实例管理
|
||||||
|
export const EDITOR_CONFIG = {
|
||||||
|
/** 最多缓存的编辑器实例数量 */
|
||||||
|
MAX_INSTANCES: 5,
|
||||||
|
/** 语法树缓存过期时间(毫秒) */
|
||||||
|
SYNTAX_TREE_CACHE_TIMEOUT: 30000,
|
||||||
|
/** 加载状态延迟时间(毫秒) */
|
||||||
|
LOADING_DELAY: 500,
|
||||||
|
} as const;
|
||||||
@@ -2,12 +2,12 @@ export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
|||||||
|
|
||||||
// 支持的语言列表
|
// 支持的语言列表
|
||||||
export const SUPPORTED_LOCALES = [
|
export const SUPPORTED_LOCALES = [
|
||||||
{
|
|
||||||
code: 'zh-CN' as SupportedLocaleType,
|
|
||||||
name: '简体中文'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
code: 'en-US' as SupportedLocaleType,
|
code: 'en-US' as SupportedLocaleType,
|
||||||
name: 'English'
|
name: 'English'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'zh-CN' as SupportedLocaleType,
|
||||||
|
name: '简体中文'
|
||||||
}
|
}
|
||||||
] as const;
|
] as const;
|
||||||
49
frontend/src/common/constant/translation.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 默认翻译配置
|
||||||
|
*/
|
||||||
|
export const DEFAULT_TRANSLATION_CONFIG = {
|
||||||
|
minSelectionLength: 2,
|
||||||
|
maxTranslationLength: 5000,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译相关的错误消息
|
||||||
|
*/
|
||||||
|
export const TRANSLATION_ERRORS = {
|
||||||
|
NO_TEXT: 'no text to translate',
|
||||||
|
TRANSLATION_FAILED: 'translation failed',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译结果接口
|
||||||
|
*/
|
||||||
|
export interface TranslationResult {
|
||||||
|
translatedText: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言信息接口
|
||||||
|
*/
|
||||||
|
export interface LanguageInfo {
|
||||||
|
Code: string; // 语言代码
|
||||||
|
Name: string; // 语言名称
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译器扩展配置
|
||||||
|
*/
|
||||||
|
export interface TranslatorConfig {
|
||||||
|
/** 最小选择字符数才显示翻译按钮 */
|
||||||
|
minSelectionLength: number;
|
||||||
|
/** 最大翻译字符数 */
|
||||||
|
maxTranslationLength: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译图标SVG
|
||||||
|
*/
|
||||||
|
export const TRANSLATION_ICON_SVG = `
|
||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
||||||
|
<path d="M599.68 485.056h-8l30.592 164.672c20.352-7.04 38.72-17.344 54.912-31.104a271.36 271.36 0 0 1-40.704-64.64l32.256-4.032c8.896 17.664 19.072 33.28 30.592 46.72 23.872-27.968 42.24-65.152 55.04-111.744l-154.688 0.128z m121.92 133.76c18.368 15.36 39.36 26.56 62.848 33.472l14.784 4.416-8.64 30.336-14.72-4.352a205.696 205.696 0 0 1-76.48-41.728c-20.672 17.92-44.928 31.552-71.232 40.064l20.736 110.912H519.424l-9.984 72.512h385.152c18.112 0 32.704-14.144 32.704-31.616V295.424a32.128 32.128 0 0 0-32.704-31.552H550.528l35.2 189.696h79.424v-31.552h61.44v31.552h102.4v31.616h-42.688c-14.272 55.488-35.712 100.096-64.64 133.568zM479.36 791.68H193.472c-36.224 0-65.472-28.288-65.472-63.168V191.168C128 156.16 157.312 128 193.472 128h327.68l20.544 104.32h352.832c36.224 0 65.472 28.224 65.472 63.104v537.408c0 34.944-29.312 63.168-65.472 63.168H468.608l10.688-104.32zM337.472 548.352v-33.28H272.768v-48.896h60.16V433.28h-60.16v-41.728h64.704v-32.896h-102.4v189.632h102.4z m158.272 0V453.76c0-17.216-4.032-30.272-12.16-39.488-8.192-9.152-20.288-13.696-36.032-13.696a55.04 55.04 0 0 0-24.768 5.376 39.04 39.04 0 0 0-17.088 15.936h-1.984l-5.056-18.56h-28.352V548.48h37.12V480c0-17.088 2.304-29.376 6.912-36.736 4.608-7.424 12.16-11.072 22.528-11.072 7.616 0 13.248 2.56 16.64 7.872 3.52 5.248 5.312 13.056 5.312 23.488v84.736h36.928z" fill="currentColor"></path>
|
||||||
|
</svg>`;
|
||||||
@@ -78,7 +78,7 @@ _69: () => {
|
|||||||
_70: () => {
|
_70: () => {
|
||||||
return typeof process != "undefined" &&
|
return typeof process != "undefined" &&
|
||||||
Object.prototype.toString.call(process) == "[object process]" &&
|
Object.prototype.toString.call(process) == "[object process]" &&
|
||||||
process.platform == "win32"
|
process.platform == "win32";
|
||||||
},
|
},
|
||||||
_85: s => JSON.stringify(s),
|
_85: s => JSON.stringify(s),
|
||||||
_86: s => printToConsole(s),
|
_86: s => printToConsole(s),
|
||||||
@@ -126,7 +126,7 @@ _157: Function.prototype.call.bind(DataView.prototype.setFloat32),
|
|||||||
_158: Function.prototype.call.bind(DataView.prototype.getFloat64),
|
_158: Function.prototype.call.bind(DataView.prototype.getFloat64),
|
||||||
_159: Function.prototype.call.bind(DataView.prototype.setFloat64),
|
_159: Function.prototype.call.bind(DataView.prototype.setFloat64),
|
||||||
_165: x0 => format = x0,
|
_165: x0 => format = x0,
|
||||||
_166: f => finalizeWrapper(f, function(x0,x1,x2) { return dartInstance.exports._166(f,arguments.length,x0,x1,x2) }),
|
_166: f => finalizeWrapper(f, function(x0,x1,x2) { return dartInstance.exports._166(f,arguments.length,x0,x1,x2); }),
|
||||||
_184: (c) =>
|
_184: (c) =>
|
||||||
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
|
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
|
||||||
_187: (s, m) => {
|
_187: (s, m) => {
|
||||||
@@ -337,14 +337,14 @@ _272: (x0,x1) => x0.lastIndex = x1
|
|||||||
});
|
});
|
||||||
|
|
||||||
return dartInstance;
|
return dartInstance;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Call the main function for the instantiated module
|
// Call the main function for the instantiated module
|
||||||
// `moduleInstance` is the instantiated dart2wasm module
|
// `moduleInstance` is the instantiated dart2wasm module
|
||||||
// `args` are any arguments that should be passed into the main function.
|
// `args` are any arguments that should be passed into the main function.
|
||||||
export const invoke = (moduleInstance, ...args) => {
|
export const invoke = (moduleInstance, ...args) => {
|
||||||
moduleInstance.exports.$invokeMain(args);
|
moduleInstance.exports.$invokeMain(args);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export let format;
|
export let format;
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
"github.com/reteps/dockerfmt/lib"
|
"docker_fmt/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Format(this js.Value, args []js.Value) any {
|
func Format(this js.Value, args []js.Value) any {
|
||||||
@@ -43,7 +43,10 @@ func Format(this js.Value, args []js.Value) any {
|
|||||||
SpaceRedirects: spaceRedirects,
|
SpaceRedirects: spaceRedirects,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := lib.FormatFileLines(originalLines, c)
|
result, err := lib.FormatFileLines(originalLines, c)
|
||||||
|
if err != nil {
|
||||||
|
return []any{true, err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
return []any{false, result}
|
return []any{false, result}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ module docker_fmt
|
|||||||
|
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require github.com/reteps/dockerfmt v0.3.7
|
require (
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
|
github.com/moby/buildkit v0.24.0
|
||||||
|
mvdan.cc/sh/v3 v3.12.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
|
||||||
github.com/moby/buildkit v0.20.2 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
google.golang.org/protobuf v1.35.2 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
mvdan.cc/sh/v3 v3.11.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/moby/buildkit v0.20.2 h1:qIeR47eQ1tzI1rwz0on3Xx2enRw/1CKjFhoONVcTlMA=
|
github.com/moby/buildkit v0.20.2 h1:qIeR47eQ1tzI1rwz0on3Xx2enRw/1CKjFhoONVcTlMA=
|
||||||
github.com/moby/buildkit v0.20.2/go.mod h1:DhaF82FjwOElTftl0JUAJpH/SUIUx4UvcFncLeOtlDI=
|
github.com/moby/buildkit v0.20.2/go.mod h1:DhaF82FjwOElTftl0JUAJpH/SUIUx4UvcFncLeOtlDI=
|
||||||
|
github.com/moby/buildkit v0.24.0 h1:qYfTl7W1SIJzWDIDCcPT8FboHIZCYfi++wvySi3eyFE=
|
||||||
|
github.com/moby/buildkit v0.24.0/go.mod h1:4qovICAdR2H4C7+EGMRva5zgHW1gyhT4/flHI7F5F9k=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
@@ -59,7 +61,11 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||||
|
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||||
|
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier'
|
import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier';
|
||||||
import dockerfileInit, { format } from './docker_fmt_vite.js'
|
import dockerfileInit, { format } from './docker_fmt_vite.js';
|
||||||
|
|
||||||
// Language configuration for Dockerfile
|
// Language configuration for Dockerfile
|
||||||
const languages: SupportLanguage[] = [
|
const languages: SupportLanguage[] = [
|
||||||
@@ -11,7 +11,7 @@ const languages: SupportLanguage[] = [
|
|||||||
linguistLanguageId: 99,
|
linguistLanguageId: 99,
|
||||||
vscodeLanguageIds: ['dockerfile'],
|
vscodeLanguageIds: ['dockerfile'],
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
// Parser configuration
|
// Parser configuration
|
||||||
const parsers: Record<string, Parser<any>> = {
|
const parsers: Record<string, Parser<any>> = {
|
||||||
@@ -19,69 +19,60 @@ const parsers: Record<string, Parser<any>> = {
|
|||||||
parse: (text: string) => {
|
parse: (text: string) => {
|
||||||
// For Dockerfile, we don't need complex parsing, just return the text
|
// For Dockerfile, we don't need complex parsing, just return the text
|
||||||
// The formatting will be handled by the print function
|
// The formatting will be handled by the print function
|
||||||
return { type: 'dockerfile', value: text }
|
return { type: 'dockerfile', value: text };
|
||||||
},
|
},
|
||||||
astFormat: 'dockerfile',
|
astFormat: 'dockerfile',
|
||||||
locStart: () => 0,
|
locStart: () => 0,
|
||||||
locEnd: () => 0,
|
locEnd: () => 0,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Printer configuration
|
// Printer configuration
|
||||||
const printers: Record<string, Printer<any>> = {
|
const printers: Record<string, Printer<any>> = {
|
||||||
dockerfile: {
|
dockerfile: {
|
||||||
// @ts-expect-error -- Support async printer like shell plugin
|
// @ts-expect-error -- Support async printer like shell plugin
|
||||||
async print(path: any, options: any) {
|
async print(path: any, options: any) {
|
||||||
await ensureInitialized()
|
await ensureInitialized();
|
||||||
const text = path.getValue().value || path.getValue()
|
const text = path.getValue().value || path.getValue();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formatted = format(text, {
|
const formatted = format(text, {
|
||||||
indent: options.tabWidth || 2,
|
indent: options.tabWidth || 2,
|
||||||
trailingNewline: true,
|
trailingNewline: true,
|
||||||
spaceRedirects: options.spaceRedirects !== false,
|
spaceRedirects: options.spaceRedirects !== false,
|
||||||
})
|
});
|
||||||
return formatted
|
return formatted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Dockerfile formatting error:', error)
|
console.warn('Dockerfile formatting error:', error);
|
||||||
return text
|
return text;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// WASM initialization
|
// WASM initialization
|
||||||
let isInitialized = false
|
let isInitialized = false;
|
||||||
let initPromise: Promise<void> | null = null
|
let initPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
async function ensureInitialized(): Promise<void> {
|
async function ensureInitialized(): Promise<void> {
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
return Promise.resolve()
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initPromise) {
|
if (!initPromise) {
|
||||||
initPromise = (async () => {
|
initPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
await dockerfileInit()
|
await dockerfileInit();
|
||||||
isInitialized = true
|
isInitialized = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to initialize Dockerfile WASM module:', error)
|
console.warn('Failed to initialize Dockerfile WASM module:', error);
|
||||||
initPromise = null
|
initPromise = null;
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
})()
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
return initPromise
|
return initPromise;
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration mapping function
|
|
||||||
function mapOptionsToConfig(options: any) {
|
|
||||||
return {
|
|
||||||
indent: options.tabWidth || 2,
|
|
||||||
trailingNewline: options.insertFinalNewline !== false,
|
|
||||||
spaceRedirects: options.spaceRedirects !== false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin options
|
// Plugin options
|
||||||
@@ -92,7 +83,7 @@ const options: Record<string, SupportOption> = {
|
|||||||
default: true,
|
default: true,
|
||||||
description: 'Add spaces around redirect operators',
|
description: 'Add spaces around redirect operators',
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Plugin definition
|
// Plugin definition
|
||||||
const plugin: Plugin = {
|
const plugin: Plugin = {
|
||||||
@@ -105,7 +96,7 @@ const plugin: Plugin = {
|
|||||||
useTabs: false,
|
useTabs: false,
|
||||||
spaceRedirects: true,
|
spaceRedirects: true,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export default plugin
|
export default plugin;
|
||||||
export { languages, parsers, printers, options }
|
export { languages, parsers, printers, options };
|
||||||
807
frontend/src/common/prettier/plugins/docker/lib/format.go
Normal file
@@ -0,0 +1,807 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/shlex"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/command"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExtendedNode struct {
|
||||||
|
*parser.Node
|
||||||
|
Children []*ExtendedNode
|
||||||
|
Next *ExtendedNode
|
||||||
|
OriginalMultiline string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseState struct {
|
||||||
|
CurrentLine int
|
||||||
|
Output string
|
||||||
|
// Needed to pull in comments
|
||||||
|
AllOriginalLines []string
|
||||||
|
Config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
IndentSize uint
|
||||||
|
TrailingNewline bool
|
||||||
|
SpaceRedirects bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatNode(ast *ExtendedNode, c *Config) (string, bool) {
|
||||||
|
nodeName := strings.ToLower(ast.Node.Value)
|
||||||
|
dispatch := map[string]func(*ExtendedNode, *Config) string{
|
||||||
|
command.Add: formatSpaceSeparated,
|
||||||
|
command.Arg: formatBasic,
|
||||||
|
command.Cmd: formatCmd,
|
||||||
|
command.Copy: formatSpaceSeparated,
|
||||||
|
command.Entrypoint: formatEntrypoint,
|
||||||
|
command.Env: formatEnv,
|
||||||
|
command.Expose: formatSpaceSeparated,
|
||||||
|
command.From: formatSpaceSeparated,
|
||||||
|
command.Healthcheck: formatBasic,
|
||||||
|
command.Label: formatLabel,
|
||||||
|
command.Maintainer: formatMaintainer,
|
||||||
|
command.Onbuild: FormatOnBuild,
|
||||||
|
command.Run: formatRun,
|
||||||
|
command.Shell: formatCmd,
|
||||||
|
command.StopSignal: formatBasic,
|
||||||
|
command.User: formatBasic,
|
||||||
|
command.Volume: formatBasic,
|
||||||
|
command.Workdir: formatSpaceSeparated,
|
||||||
|
}
|
||||||
|
|
||||||
|
fmtFunc := dispatch[nodeName]
|
||||||
|
if fmtFunc == nil {
|
||||||
|
return "", false
|
||||||
|
// log.Fatalf("Unknown command: %s %s\n", nodeName, ast.OriginalMultiline)
|
||||||
|
}
|
||||||
|
return fmtFunc(ast, c), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *ParseState) processNode(ast *ExtendedNode) {
|
||||||
|
|
||||||
|
// We don't want to process nodes that don't have a start or end line.
|
||||||
|
if ast.Node.StartLine == 0 || ast.Node.EndLine == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we are on the correct line,
|
||||||
|
// otherwise get the comments we are missing
|
||||||
|
if df.CurrentLine != ast.StartLine {
|
||||||
|
df.Output += FormatComments(df.AllOriginalLines[df.CurrentLine : ast.StartLine-1])
|
||||||
|
df.CurrentLine = ast.StartLine
|
||||||
|
}
|
||||||
|
// if df.Output != "" {
|
||||||
|
// // If the previous line isn't a comment or newline, add a newline
|
||||||
|
// lastTwoChars := df.Output[len(df.Output)-2 : len(df.Output)]
|
||||||
|
// lastNonTrailingNewline := strings.LastIndex(strings.TrimRight(df.Output, "\n"), "\n")
|
||||||
|
// if lastTwoChars != "\n\n" && df.Output[lastNonTrailingNewline+1] != '#' {
|
||||||
|
// df.Output += "\n"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
output, ok := FormatNode(ast, df.Config)
|
||||||
|
if ok {
|
||||||
|
df.Output += output
|
||||||
|
df.CurrentLine = ast.EndLine
|
||||||
|
}
|
||||||
|
// fmt.Printf("CurrentLine: %d, %d\n", df.CurrentLine, ast.EndLine)
|
||||||
|
// fmt.Printf("Unknown command: %s %s\n", nodeName, ast.OriginalMultiline)
|
||||||
|
|
||||||
|
for _, child := range ast.Children {
|
||||||
|
df.processNode(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("CurrentLine2: %d, %d\n", df.CurrentLine, ast.EndLine)
|
||||||
|
|
||||||
|
if ast.Node.Next != nil {
|
||||||
|
df.processNode(ast.Next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatOnBuild(n *ExtendedNode, c *Config) string {
|
||||||
|
if len(n.Node.Next.Children) == 1 {
|
||||||
|
// fmt.Printf("Onbuild: %s\n", n.Node.Next.Children[0].Value)
|
||||||
|
output, ok := FormatNode(n.Next.Children[0], c)
|
||||||
|
if ok {
|
||||||
|
return strings.ToUpper(n.Node.Value) + " " + output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.OriginalMultiline
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatFileLines(fileLines []string, c *Config) (string, error) {
|
||||||
|
result, err := parser.Parse(strings.NewReader(strings.Join(fileLines, "")))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s\n", strings.Join(fileLines, ""))
|
||||||
|
return "", fmt.Errorf("Error parsing file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseState := &ParseState{
|
||||||
|
CurrentLine: 0,
|
||||||
|
Output: "",
|
||||||
|
AllOriginalLines: fileLines,
|
||||||
|
}
|
||||||
|
rootNode := BuildExtendedNode(result.AST, fileLines)
|
||||||
|
parseState.Config = c
|
||||||
|
parseState.processNode(rootNode)
|
||||||
|
|
||||||
|
// After all directives are processed, we need to check if we have any trailing comments to add.
|
||||||
|
if parseState.CurrentLine < len(parseState.AllOriginalLines) {
|
||||||
|
// Add the rest of the file
|
||||||
|
parseState.Output += FormatComments(parseState.AllOriginalLines[parseState.CurrentLine:])
|
||||||
|
}
|
||||||
|
|
||||||
|
parseState.Output = strings.TrimRight(parseState.Output, "\n")
|
||||||
|
// Ensure the output ends with a newline if requested
|
||||||
|
if c.TrailingNewline {
|
||||||
|
parseState.Output += "\n"
|
||||||
|
}
|
||||||
|
return parseState.Output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildExtendedNode(n *parser.Node, fileLines []string) *ExtendedNode {
|
||||||
|
// Build an extended node from the parser node
|
||||||
|
// This is used to add the original multiline string to the node
|
||||||
|
// and to add the original line numbers
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the extended node with the current parser node
|
||||||
|
en := &ExtendedNode{
|
||||||
|
Node: n,
|
||||||
|
Next: nil,
|
||||||
|
Children: nil,
|
||||||
|
OriginalMultiline: "", // Default to empty string
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have valid start and end lines, construct the multiline representation
|
||||||
|
if n.StartLine > 0 && n.EndLine > 0 {
|
||||||
|
// Subtract 1 from StartLine because fileLines is 0-indexed while StartLine is 1-indexed
|
||||||
|
for i := n.StartLine - 1; i < n.EndLine; i++ {
|
||||||
|
en.OriginalMultiline += fileLines[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all children recursively
|
||||||
|
if len(n.Children) > 0 {
|
||||||
|
childrenNodes := make([]*ExtendedNode, 0, len(n.Children))
|
||||||
|
for _, child := range n.Children {
|
||||||
|
extChild := BuildExtendedNode(child, fileLines)
|
||||||
|
if extChild != nil {
|
||||||
|
childrenNodes = append(childrenNodes, extChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Replace the children with the processed ones
|
||||||
|
en.Children = childrenNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the next node recursively
|
||||||
|
if n.Next != nil {
|
||||||
|
extNext := BuildExtendedNode(n.Next, fileLines)
|
||||||
|
if extNext != nil {
|
||||||
|
en.Next = extNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return en
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatEnv(n *ExtendedNode, c *Config) string {
|
||||||
|
// Only the legacy format will have a empty 3rd child
|
||||||
|
if n.Next.Next.Next.Value == "" {
|
||||||
|
return strings.ToUpper(n.Node.Value) + " " + n.Next.Node.Value + "=" + n.Next.Next.Node.Value + "\n"
|
||||||
|
}
|
||||||
|
// Otherwise, we have a valid env command
|
||||||
|
originalTrimmed := strings.TrimLeft(n.OriginalMultiline, " \t")
|
||||||
|
content := StripWhitespace(regexp.MustCompile(" ").Split(originalTrimmed, 2)[1], true)
|
||||||
|
// Indent all lines with indentSize spaces
|
||||||
|
re := regexp.MustCompile("(?m)^ *")
|
||||||
|
content = strings.Trim(re.ReplaceAllString(content, strings.Repeat(" ", int(c.IndentSize))), " ")
|
||||||
|
return strings.ToUpper(n.Value) + " " + content
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatShell(content string, hereDoc bool, c *Config) string {
|
||||||
|
// Semicolons require special handling so we don't break the command
|
||||||
|
// Improved semicolon support: handle escaped semicolons properly
|
||||||
|
|
||||||
|
// Check for unescaped semicolons - if found, try to format them properly
|
||||||
|
if regexp.MustCompile(`[^\\];`).MatchString(content) {
|
||||||
|
// Split by unescaped semicolons and format each part separately
|
||||||
|
parts := regexp.MustCompile(`([^\\]);`).Split(content, -1)
|
||||||
|
if len(parts) > 1 {
|
||||||
|
var formattedParts []string
|
||||||
|
for i, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part != "" {
|
||||||
|
// Try to format each part individually
|
||||||
|
formatted := formatSingleCommand(part, hereDoc, c)
|
||||||
|
formattedParts = append(formattedParts, formatted)
|
||||||
|
}
|
||||||
|
// Add semicolon back except for the last part
|
||||||
|
if i < len(parts)-1 {
|
||||||
|
formattedParts[len(formattedParts)-1] += ";"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(formattedParts, " ")
|
||||||
|
}
|
||||||
|
// If splitting didn't work, fall back to original content
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
// Grouped expressions aren't formatted well
|
||||||
|
// See: https://github.com/mvdan/sh/issues/1148
|
||||||
|
if strings.Contains(content, "{ \\") {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hereDoc {
|
||||||
|
// Here lies some cursed magic. Be careful.
|
||||||
|
|
||||||
|
// Replace comments with a subshell evaluation -- they won't be run so we can do this.
|
||||||
|
content = StripWhitespace(content, true)
|
||||||
|
lineComment := regexp.MustCompile(`(\n\s*)(#.*)`)
|
||||||
|
lines := strings.SplitAfter(content, "\n")
|
||||||
|
for i := range lines {
|
||||||
|
lineTrim := strings.TrimLeft(lines[i], " \t")
|
||||||
|
if len(lineTrim) >= 1 && lineTrim[0] == '#' {
|
||||||
|
lines[i] = strings.ReplaceAll(lines[i], "`", "×")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = strings.Join(lines, "")
|
||||||
|
|
||||||
|
content = lineComment.ReplaceAllString(content, "$1`$2#`\\")
|
||||||
|
|
||||||
|
/*
|
||||||
|
```
|
||||||
|
foo \
|
||||||
|
`#comment#`\
|
||||||
|
&& bar
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
foo && \
|
||||||
|
`#comment#` \
|
||||||
|
bar
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
commentContinuation := regexp.MustCompile(`(\\(?:\s*` + "`#.*#`" + `\\){1,}\s*)&&`)
|
||||||
|
content = commentContinuation.ReplaceAllString(content, "&&$1")
|
||||||
|
|
||||||
|
// log.Printf("Content0: %s\n", content)
|
||||||
|
lines = strings.SplitAfter(content, "\n")
|
||||||
|
/**
|
||||||
|
if the next line is not a comment, and we didn't start with a continuation, don't add the `&&`.
|
||||||
|
*/
|
||||||
|
inContinuation := false
|
||||||
|
for i := range lines {
|
||||||
|
lineTrim := strings.Trim(lines[i], " \t\\\n")
|
||||||
|
// fmt.Printf("LineTrim: %s\n", lineTrim)
|
||||||
|
nextLine := ""
|
||||||
|
isComment := false
|
||||||
|
nextLineIsComment := false
|
||||||
|
if i+1 < len(lines) {
|
||||||
|
nextLine = strings.Trim(lines[i+1], " \t\\\n")
|
||||||
|
}
|
||||||
|
if len(nextLine) >= 2 && nextLine[:2] == "`#" {
|
||||||
|
nextLineIsComment = true
|
||||||
|
}
|
||||||
|
if len(lineTrim) >= 2 && lineTrim[:2] == "`#" {
|
||||||
|
isComment = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("isComment: %v, nextLineIsComment: %v, inContinuation: %v\n", isComment, nextLineIsComment, inContinuation)
|
||||||
|
if isComment && (inContinuation || nextLineIsComment) {
|
||||||
|
lines[i] = strings.Replace(lines[i], "#`\\", "#`&&\\", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lineTrim) >= 2 && !isComment && lineTrim[len(lineTrim)-2:] == "&&" {
|
||||||
|
inContinuation = true
|
||||||
|
} else if !isComment {
|
||||||
|
inContinuation = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content = strings.Join(lines, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have a valid bash-style command, we can format it with shfmt
|
||||||
|
// log.Printf("Content1: %s\n", content)
|
||||||
|
content = formatBash(content, c)
|
||||||
|
|
||||||
|
// log.Printf("Content2: %s\n", content)
|
||||||
|
|
||||||
|
if !hereDoc {
|
||||||
|
reBacktickComment := regexp.MustCompile(`([ \t]*)(?:&& )?` + "`(#.*)#` " + `\\`)
|
||||||
|
content = reBacktickComment.ReplaceAllString(content, "$1$2")
|
||||||
|
|
||||||
|
// Fixup the comment indentation
|
||||||
|
lines := strings.SplitAfter(content, "\n")
|
||||||
|
prevIsComment := false
|
||||||
|
prevCommentSpacing := ""
|
||||||
|
firstLineIsComment := false
|
||||||
|
for i := range lines {
|
||||||
|
lineTrim := strings.TrimLeft(lines[i], " \t")
|
||||||
|
// fmt.Printf("LineTrim: %s, %v\n", lineTrim, prevIsComment)
|
||||||
|
if len(lineTrim) >= 1 && lineTrim[0] == '#' {
|
||||||
|
if i == 0 {
|
||||||
|
firstLineIsComment = true
|
||||||
|
lines[i] = strings.Repeat(" ", int(c.IndentSize)) + lineTrim
|
||||||
|
}
|
||||||
|
lineParts := strings.SplitN(lines[i], "#", 2)
|
||||||
|
|
||||||
|
if prevIsComment {
|
||||||
|
lines[i] = prevCommentSpacing + "#" + lineParts[1]
|
||||||
|
} else {
|
||||||
|
prevCommentSpacing = lineParts[0]
|
||||||
|
}
|
||||||
|
prevIsComment = true
|
||||||
|
} else {
|
||||||
|
prevIsComment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: this formatting isn't perfect (see tests/out/run5.dockerfile)
|
||||||
|
if firstLineIsComment {
|
||||||
|
lines = slices.Insert(lines, 0, "\\\n")
|
||||||
|
}
|
||||||
|
content = strings.Join(lines, "")
|
||||||
|
content = strings.ReplaceAll(content, "×", "`")
|
||||||
|
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatSingleCommand formats a single shell command (used for semicolon-separated commands)
|
||||||
|
func formatSingleCommand(content string, hereDoc bool, c *Config) string {
|
||||||
|
// Grouped expressions aren't formatted well
|
||||||
|
// See: https://github.com/mvdan/sh/issues/1148
|
||||||
|
if strings.Contains(content, "{ \\") {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hereDoc {
|
||||||
|
// Here lies some cursed magic. Be careful.
|
||||||
|
|
||||||
|
// Replace comments with a subshell evaluation -- they won't be run so we can do this.
|
||||||
|
content = StripWhitespace(content, true)
|
||||||
|
content = regexp.MustCompile(`#.*`).ReplaceAllString(content, "$(: comment)")
|
||||||
|
content = strings.ReplaceAll(content, "\\\n", " ")
|
||||||
|
content = strings.ReplaceAll(content, "\n", " ")
|
||||||
|
content = regexp.MustCompile(`\s+`).ReplaceAllString(content, " ")
|
||||||
|
content = strings.TrimSpace(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatBash(content, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRun(n *ExtendedNode, c *Config) string {
|
||||||
|
// Get the original RUN command text
|
||||||
|
hereDoc := false
|
||||||
|
flags := n.Node.Flags
|
||||||
|
|
||||||
|
var content string
|
||||||
|
if len(n.Node.Heredocs) >= 1 {
|
||||||
|
content = n.Node.Heredocs[0].Content
|
||||||
|
hereDoc = true
|
||||||
|
// Check if heredoc FileDescriptor is 0 (stdin) - this is the standard for RUN commands
|
||||||
|
if n.Node.Heredocs[0].FileDescriptor != 0 {
|
||||||
|
log.Printf("Warning: heredoc FileDescriptor is %d, expected 0 for RUN command", n.Node.Heredocs[0].FileDescriptor)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We split the original multiline string by whitespace
|
||||||
|
originalText := n.OriginalMultiline
|
||||||
|
if n.OriginalMultiline == "" {
|
||||||
|
// If the original multiline string is empty, use the original value
|
||||||
|
originalText = n.Node.Original
|
||||||
|
}
|
||||||
|
|
||||||
|
originalTrimmed := strings.TrimLeft(originalText, " \t")
|
||||||
|
parts := regexp.MustCompile("[ \t]").Split(originalTrimmed, 2+len(flags))
|
||||||
|
content = parts[1+len(flags)]
|
||||||
|
}
|
||||||
|
// Try to parse as JSON
|
||||||
|
jsonItems, err := parseJSONStringArray(content)
|
||||||
|
if err == nil {
|
||||||
|
outStr := marshalStringArray(jsonItems)
|
||||||
|
outStr = strings.ReplaceAll(outStr, "\",\"", "\", \"")
|
||||||
|
content = outStr + "\n"
|
||||||
|
} else {
|
||||||
|
content = formatShell(content, hereDoc, c)
|
||||||
|
if hereDoc {
|
||||||
|
n.Node.Heredocs[0].Content = content
|
||||||
|
content, _ = GetHeredoc(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flags) > 0 {
|
||||||
|
content = strings.Join(flags, " ") + " " + content
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(n.Value) + " " + content
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHeredoc(n *ExtendedNode) (string, bool) {
|
||||||
|
if len(n.Node.Heredocs) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printAST(n, 0)
|
||||||
|
args := []string{}
|
||||||
|
cur := n.Next
|
||||||
|
for cur != nil {
|
||||||
|
if cur.Node.Value != "" {
|
||||||
|
args = append(args, cur.Node.Value)
|
||||||
|
}
|
||||||
|
cur = cur.Next
|
||||||
|
}
|
||||||
|
content := strings.Join(args, " ") + "\n" + n.Node.Heredocs[0].Content + n.Node.Heredocs[0].Name + "\n"
|
||||||
|
return content, true
|
||||||
|
}
|
||||||
|
func formatBasic(n *ExtendedNode, c *Config) string {
|
||||||
|
// Uppercases the command, and indent the following lines
|
||||||
|
originalTrimmed := strings.TrimLeft(n.OriginalMultiline, " \t")
|
||||||
|
|
||||||
|
value, success := GetHeredoc(n)
|
||||||
|
if !success {
|
||||||
|
value = regexp.MustCompile(" ").Split(originalTrimmed, 2)[1]
|
||||||
|
}
|
||||||
|
return IndentFollowingLines(strings.ToUpper(n.Value)+" "+value, c.IndentSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalStringArray manually creates a JSON array string from a slice of strings
|
||||||
|
// This avoids using encoding/json which causes reflection issues in WASM
|
||||||
|
func marshalStringArray(items []string) string {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
|
||||||
|
var result strings.Builder
|
||||||
|
result.WriteString("[")
|
||||||
|
|
||||||
|
for i, item := range items {
|
||||||
|
if i > 0 {
|
||||||
|
result.WriteString(", ")
|
||||||
|
}
|
||||||
|
result.WriteString("\"")
|
||||||
|
// Escape quotes and backslashes in the string
|
||||||
|
escaped := strings.ReplaceAll(item, "\\", "\\\\")
|
||||||
|
escaped = strings.ReplaceAll(escaped, "\"", "\\\"")
|
||||||
|
result.WriteString(escaped)
|
||||||
|
result.WriteString("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.WriteString("]")
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseJSONStringArray manually parses a JSON array string into a slice of strings
|
||||||
|
// This avoids using encoding/json which causes reflection issues in WASM
|
||||||
|
func parseJSONStringArray(jsonStr string) ([]string, error) {
|
||||||
|
jsonStr = strings.TrimSpace(jsonStr)
|
||||||
|
if !strings.HasPrefix(jsonStr, "[") || !strings.HasSuffix(jsonStr, "]") {
|
||||||
|
return nil, fmt.Errorf("not a JSON array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove brackets
|
||||||
|
content := strings.TrimSpace(jsonStr[1 : len(jsonStr)-1])
|
||||||
|
if content == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []string
|
||||||
|
var current strings.Builder
|
||||||
|
inQuotes := false
|
||||||
|
escaped := false
|
||||||
|
|
||||||
|
for i, char := range content {
|
||||||
|
if escaped {
|
||||||
|
switch char {
|
||||||
|
case '"':
|
||||||
|
current.WriteRune('"')
|
||||||
|
case '\\':
|
||||||
|
current.WriteRune('\\')
|
||||||
|
case 'n':
|
||||||
|
current.WriteRune('\n')
|
||||||
|
case 't':
|
||||||
|
current.WriteRune('\t')
|
||||||
|
case 'r':
|
||||||
|
current.WriteRune('\r')
|
||||||
|
default:
|
||||||
|
current.WriteRune('\\')
|
||||||
|
current.WriteRune(char)
|
||||||
|
}
|
||||||
|
escaped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == '\\' {
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == '"' {
|
||||||
|
inQuotes = !inQuotes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inQuotes && char == ',' {
|
||||||
|
result = append(result, current.String())
|
||||||
|
current.Reset()
|
||||||
|
// Skip whitespace after comma
|
||||||
|
for i+1 < len(content) && (content[i+1] == ' ' || content[i+1] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inQuotes {
|
||||||
|
current.WriteRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last item
|
||||||
|
if current.Len() > 0 || len(result) > 0 {
|
||||||
|
result = append(result, current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCmd(n *ExtendedNode, shouldSplitNode bool) []string {
|
||||||
|
cmd := []string{}
|
||||||
|
for node := n; node != nil; node = node.Next {
|
||||||
|
// Split value by whitespace
|
||||||
|
rawValue := strings.Trim(node.Node.Value, " \t")
|
||||||
|
if len(node.Node.Flags) > 0 {
|
||||||
|
cmd = append(cmd, node.Node.Flags...)
|
||||||
|
}
|
||||||
|
// log.Printf("ShouldSplitNode: %v\n", shouldSplitNode)
|
||||||
|
if shouldSplitNode {
|
||||||
|
parts, err := shlex.Split(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback: if splitting fails, use raw value as a single token to avoid exiting
|
||||||
|
log.Printf("Error splitting: %s: %v\n", node.Node.Value, err)
|
||||||
|
cmd = append(cmd, rawValue)
|
||||||
|
} else {
|
||||||
|
cmd = append(cmd, parts...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd = append(cmd, rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log.Printf("getCmd: %v\n", cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldRunInShell(node string) bool {
|
||||||
|
// https://docs.docker.com/reference/dockerfile/#entrypoint
|
||||||
|
parts, err := shlex.Split(node)
|
||||||
|
if err != nil {
|
||||||
|
// If we cannot reliably split, heuristically decide based on common shell operators
|
||||||
|
if strings.Contains(node, "&&") || strings.Contains(node, ";") || strings.Contains(node, "||") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
needsShell := false
|
||||||
|
// This is a simplistic check to determine if we need to run in a full shell.
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "&&" || part == ";" || part == "||" {
|
||||||
|
needsShell = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsShell
|
||||||
|
}
|
||||||
|
func formatEntrypoint(n *ExtendedNode, c *Config) string {
|
||||||
|
// this can technically change behavior. https://docs.docker.com/reference/dockerfile/#understand-how-cmd-and-entrypoint-interact
|
||||||
|
return formatCmd(n, c)
|
||||||
|
}
|
||||||
|
func formatCmd(n *ExtendedNode, c *Config) string {
|
||||||
|
// printAST(n, 0)
|
||||||
|
isJSON, ok := n.Node.Attributes["json"]
|
||||||
|
if !ok {
|
||||||
|
isJSON = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isJSON {
|
||||||
|
doNotSplit := shouldRunInShell(n.Node.Next.Value)
|
||||||
|
if doNotSplit {
|
||||||
|
n.Next.Node.Flags = append(n.Next.Node.Flags, []string{"/bin/sh", "-c"}...)
|
||||||
|
// Hacky workaround to tell getCmd to not split the command
|
||||||
|
isJSON = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := getCmd(n.Next, !isJSON)
|
||||||
|
bWithSpace := marshalStringArray(cmd)
|
||||||
|
bWithSpace = strings.ReplaceAll(bWithSpace, "\",\"", "\", \"")
|
||||||
|
return strings.ToUpper(n.Node.Value) + " " + bWithSpace + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSpaceSeparated(n *ExtendedNode, c *Config) string {
|
||||||
|
isJSON, ok := n.Node.Attributes["json"]
|
||||||
|
if !ok {
|
||||||
|
isJSON = false
|
||||||
|
}
|
||||||
|
cmd, success := GetHeredoc(n)
|
||||||
|
if !success {
|
||||||
|
cmd = strings.Join(getCmd(n.Next, isJSON), " ")
|
||||||
|
if len(n.Node.Flags) > 0 {
|
||||||
|
cmd = strings.Join(n.Node.Flags, " ") + " " + cmd
|
||||||
|
}
|
||||||
|
cmd += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(n.Node.Value) + " " + cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLabel(n *ExtendedNode, c *Config) string {
|
||||||
|
// Parse LABEL key-value pairs and sort them alphabetically by key
|
||||||
|
originalTrimmed := strings.TrimLeft(n.OriginalMultiline, " \t")
|
||||||
|
content := regexp.MustCompile(" ").Split(originalTrimmed, 2)[1]
|
||||||
|
|
||||||
|
// Parse key-value pairs
|
||||||
|
labels := make(map[string]string)
|
||||||
|
var keys []string
|
||||||
|
|
||||||
|
// Split by whitespace and parse key=value pairs
|
||||||
|
parts := strings.Fields(content)
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.Contains(part, "=") {
|
||||||
|
kv := strings.SplitN(part, "=", 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
key := strings.Trim(kv[0], "\"")
|
||||||
|
value := strings.Trim(kv[1], "\"")
|
||||||
|
labels[key] = value
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no key-value pairs found, fall back to basic formatting
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return formatBasic(n, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort keys alphabetically
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
// Build sorted output
|
||||||
|
var result strings.Builder
|
||||||
|
result.WriteString(strings.ToUpper(n.Value))
|
||||||
|
result.WriteString(" ")
|
||||||
|
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
result.WriteString(" ")
|
||||||
|
}
|
||||||
|
result.WriteString(key)
|
||||||
|
result.WriteString("=\"")
|
||||||
|
result.WriteString(labels[key])
|
||||||
|
result.WriteString("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.WriteString("\n")
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMaintainer(n *ExtendedNode, c *Config) string {
|
||||||
|
|
||||||
|
// Get text between quotes
|
||||||
|
maintainer := strings.Trim(n.Next.Node.Value, "\"")
|
||||||
|
return "LABEL org.opencontainers.image.authors=\"" + maintainer + "\"\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileLines(fileName string) ([]string, error) {
|
||||||
|
// Open the file
|
||||||
|
f, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Read the file contents
|
||||||
|
b := new(strings.Builder)
|
||||||
|
io.Copy(b, f)
|
||||||
|
fileLines := strings.SplitAfter(b.String(), "\n")
|
||||||
|
|
||||||
|
return fileLines, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StripWhitespace(lines string, rightOnly bool) string {
|
||||||
|
// Split the string into lines by newlines
|
||||||
|
// log.Printf("Lines: .%s.\n", lines)
|
||||||
|
linesArray := strings.SplitAfter(lines, "\n")
|
||||||
|
// Create a new slice to hold the stripped lines
|
||||||
|
var strippedLines string
|
||||||
|
// Iterate over each line
|
||||||
|
for _, line := range linesArray {
|
||||||
|
// Trim leading and trailing whitespace
|
||||||
|
// log.Printf("Line .%s.\n", line)
|
||||||
|
hadNewline := len(line) > 0 && line[len(line)-1] == '\n'
|
||||||
|
if rightOnly {
|
||||||
|
// Only trim trailing whitespace
|
||||||
|
line = strings.TrimRight(line, " \t\n")
|
||||||
|
} else {
|
||||||
|
// Trim both leading and trailing whitespace
|
||||||
|
line = strings.Trim(line, " \t\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("Line2 .%s.", line)
|
||||||
|
if hadNewline {
|
||||||
|
line += "\n"
|
||||||
|
}
|
||||||
|
strippedLines += line
|
||||||
|
}
|
||||||
|
return strippedLines
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatComments(lines []string) string {
|
||||||
|
// Adds lines to the output, collapsing multiple newlines into a single newline
|
||||||
|
// and removing leading / trailing whitespace. We can do this because
|
||||||
|
// we are adding comments and we don't care about the formatting.
|
||||||
|
missingContent := StripWhitespace(strings.Join(lines, ""), false)
|
||||||
|
// Replace multiple newlines with a single newline
|
||||||
|
re := regexp.MustCompile(`\n{3,}`)
|
||||||
|
return re.ReplaceAllString(missingContent, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IndentFollowingLines(lines string, indentSize uint) string {
|
||||||
|
// Split the input by lines
|
||||||
|
allLines := strings.SplitAfter(lines, "\n")
|
||||||
|
|
||||||
|
// If there's only one line or no lines, return the original
|
||||||
|
if len(allLines) <= 1 {
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the first line as is
|
||||||
|
result := allLines[0]
|
||||||
|
// Indent all subsequent lines
|
||||||
|
for i := 1; i < len(allLines); i++ {
|
||||||
|
if allLines[i] != "" { // Skip empty lines
|
||||||
|
// Remove existing indentation and add new indentation
|
||||||
|
trimmedLine := strings.TrimLeft(allLines[i], " \t")
|
||||||
|
allLines[i] = strings.Repeat(" ", int(indentSize)) + trimmedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to result (with newline except for the last line)
|
||||||
|
result += allLines[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBash(s string, c *Config) string {
|
||||||
|
r := strings.NewReader(s)
|
||||||
|
f, err := syntax.NewParser(syntax.KeepComments(true)).Parse(r, "")
|
||||||
|
if err != nil {
|
||||||
|
// On parse failure, return original input to avoid crashing the WASM runtime
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
syntax.NewPrinter(
|
||||||
|
syntax.Minify(false),
|
||||||
|
syntax.SingleLine(false),
|
||||||
|
syntax.SpaceRedirects(c.SpaceRedirects),
|
||||||
|
syntax.Indent(c.IndentSize),
|
||||||
|
syntax.BinaryNextLine(true),
|
||||||
|
).Print(buf, f)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
@@ -60,7 +60,7 @@ function initGofmt(): Promise<void> {
|
|||||||
// Printer configuration
|
// Printer configuration
|
||||||
const goPrinter: Printer<string> = {
|
const goPrinter: Printer<string> = {
|
||||||
// @ts-expect-error -- Support async printer like shell plugin
|
// @ts-expect-error -- Support async printer like shell plugin
|
||||||
async print(path, options) {
|
async print(path, _options) {
|
||||||
try {
|
try {
|
||||||
// Wait for initialization to complete
|
// Wait for initialization to complete
|
||||||
await initGofmt();
|
await initGofmt();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const groovyPrinter: Printer<string> = {
|
|||||||
return groovyBeautify(path.node, {
|
return groovyBeautify(path.node, {
|
||||||
width: options.printWidth || 80,
|
width: options.printWidth || 80,
|
||||||
}).trim();
|
}).trim();
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
return path.node;
|
return path.node;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ type ModifierNode = JavaNonTerminal & {
|
|||||||
annotation?: AnnotationCstNode[];
|
annotation?: AnnotationCstNode[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type IsTuple<T> = T extends [] ? true : T extends [infer First, ...infer Remain] ? IsTuple<Remain> : false;
|
type IsTuple<T> = T extends [] ? true : T extends [infer _First, ...infer Remain] ? IsTuple<Remain> : false;
|
||||||
type IndexProperties<T extends {
|
type IndexProperties<T extends {
|
||||||
length: number;
|
length: number;
|
||||||
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
|
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export function format(input: string, filename: string, config?: Config): string;
|
export function format(input: string, filename: string, config?: Config): string;
|
||||||
|
|
||||||
interface LayoutConfig {
|
interface LayoutConfig {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
||||||
export const __wbindgen_export_0: (a: number, b: number) => number;
|
export const __wbindgen_export_0: (a: number, b: number) => number;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export function format(input: string, path?: string, config?: Config): string;
|
export function format(input: string, path?: string, config?: Config): string;
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
||||||
export const __wbindgen_export_0: (a: number, b: number) => number;
|
export const __wbindgen_export_0: (a: number, b: number) => number;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export function format(input: string, config?: Config | null): string;
|
export function format(input: string, config?: Config | null): string;
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const format: (a: number, b: number, c: number, d: number) => void;
|
export const format: (a: number, b: number, c: number, d: number) => void;
|
||||||
export const __wbindgen_export_0: (a: number, b: number) => number;
|
export const __wbindgen_export_0: (a: number, b: number) => number;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _fs from 'node:fs'
|
import _fs from 'node:fs';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace globalThis {
|
namespace globalThis {
|
||||||
var fs: typeof _fs
|
let fs: typeof _fs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export function format(input: string, filename: string, config?: Config): string;
|
export function format(input: string, filename: string, config?: Config): string;
|
||||||
|
|
||||||
interface LayoutConfig {
|
interface LayoutConfig {
|
||||||
|
|||||||