Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 286b0159d7 | |||
| cc98e556c6 | |||
| 5902f482d9 | |||
| 551e7e2cfd | |||
| e0179b5838 | |||
| df79267e16 | |||
| 1f0254822f | |||
| e9b6fef3cd | |||
| 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
|
||||
4
.gitignore
vendored
@@ -4,4 +4,6 @@ frontend/dist
|
||||
frontend/node_modules
|
||||
build/linux/appimage/build
|
||||
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 |
@@ -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
|
||||
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].
|
||||
*/
|
||||
@@ -359,85 +62,4 @@ 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 {
|
||||
|
||||
/** Creates a new WebviewWindow instance. */
|
||||
constructor($$source: Partial<WebviewWindow> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WebviewWindow instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WebviewWindow {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
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);
|
||||
export type Window = any;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service represents the notifications service
|
||||
* Service represents the dock service
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,19 @@ import * as application$0 from "../../application/models.js";
|
||||
// @ts-ignore: Unused imports
|
||||
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.
|
||||
*/
|
||||
export function RemoveBadge(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2374916939) as any;
|
||||
let $resultPromise = $Call.ByID(2752757297) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -30,7 +38,7 @@ export function RemoveBadge(): Promise<void> & { cancel(): void } {
|
||||
* ServiceName returns the name of the service.
|
||||
*/
|
||||
export function ServiceName(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2428202016) as any;
|
||||
let $resultPromise = $Call.ByID(2949906614) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -38,7 +46,7 @@ export function ServiceName(): Promise<string> & { cancel(): void } {
|
||||
* ServiceShutdown is called when the service is unloaded.
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3893755233) as any;
|
||||
let $resultPromise = $Call.ByID(307064411) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -46,7 +54,7 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
* ServiceStartup is called when the service is loaded.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -54,11 +62,22 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
|
||||
* SetBadge sets the badge label on the application icon.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as BadgeService from "./badgeservice.js";
|
||||
import * as DockService from "./dockservice.js";
|
||||
export {
|
||||
BadgeService
|
||||
DockService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
@@ -9,15 +9,18 @@ import {Create as $Create} from "@wailsio/runtime";
|
||||
// @ts-ignore: Unused imports
|
||||
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;
|
||||
"BackgroundColour": color$0.RGBA;
|
||||
"FontName": string;
|
||||
"FontSize": number;
|
||||
"SmallFontSize": number;
|
||||
|
||||
/** Creates a new Options instance. */
|
||||
constructor($$source: Partial<Options> = {}) {
|
||||
/** Creates a new BadgeOptions instance. */
|
||||
constructor($$source: Partial<BadgeOptions> = {}) {
|
||||
if (!("TextColour" in $$source)) {
|
||||
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 $$createField1_0 = $$createType0;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
@@ -50,7 +53,7 @@ export class Options {
|
||||
if ("BackgroundColour" in $$parsedSource) {
|
||||
$$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",
|
||||
|
||||
/**
|
||||
* TartuNLPTranslatorType TartuNLP翻译器
|
||||
*/
|
||||
TartuNLPTranslatorType = "tartunlp",
|
||||
};
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// @ts-ignore: Unused imports
|
||||
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 应用配置 - 按照前端设置页面分类组织
|
||||
*/
|
||||
@@ -114,6 +110,11 @@ export class AppearanceConfig {
|
||||
*/
|
||||
"systemTheme": SystemThemeType;
|
||||
|
||||
/**
|
||||
* 当前选择的预设主题名称
|
||||
*/
|
||||
"currentTheme": string;
|
||||
|
||||
/** Creates a new AppearanceConfig instance. */
|
||||
constructor($$source: Partial<AppearanceConfig> = {}) {
|
||||
if (!("language" in $$source)) {
|
||||
@@ -122,6 +123,9 @@ export class AppearanceConfig {
|
||||
if (!("systemTheme" in $$source)) {
|
||||
this["systemTheme"] = ("" as SystemThemeType);
|
||||
}
|
||||
if (!("currentTheme" in $$source)) {
|
||||
this["currentTheme"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
@@ -196,8 +200,8 @@ export class Document {
|
||||
"id": number;
|
||||
"title": string;
|
||||
"content": string;
|
||||
"createdAt": time$0.Time;
|
||||
"updatedAt": time$0.Time;
|
||||
"createdAt": string;
|
||||
"updatedAt": string;
|
||||
"is_deleted": boolean;
|
||||
|
||||
/**
|
||||
@@ -217,10 +221,10 @@ export class Document {
|
||||
this["content"] = "";
|
||||
}
|
||||
if (!("createdAt" in $$source)) {
|
||||
this["createdAt"] = null;
|
||||
this["createdAt"] = "";
|
||||
}
|
||||
if (!("updatedAt" in $$source)) {
|
||||
this["updatedAt"] = null;
|
||||
this["updatedAt"] = "";
|
||||
}
|
||||
if (!("is_deleted" in $$source)) {
|
||||
this["is_deleted"] = false;
|
||||
@@ -490,6 +494,11 @@ export class GeneralConfig {
|
||||
*/
|
||||
"enableLoadingAnimation": boolean;
|
||||
|
||||
/**
|
||||
* 是否启用标签页模式
|
||||
*/
|
||||
"enableTabs": boolean;
|
||||
|
||||
/** Creates a new GeneralConfig instance. */
|
||||
constructor($$source: Partial<GeneralConfig> = {}) {
|
||||
if (!("alwaysOnTop" in $$source)) {
|
||||
@@ -516,6 +525,9 @@ export class GeneralConfig {
|
||||
if (!("enableLoadingAnimation" in $$source)) {
|
||||
this["enableLoadingAnimation"] = false;
|
||||
}
|
||||
if (!("enableTabs" in $$source)) {
|
||||
this["enableTabs"] = false;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
@@ -1143,8 +1155,8 @@ export class Theme {
|
||||
"type": ThemeType;
|
||||
"colors": ThemeColorConfig;
|
||||
"isDefault": boolean;
|
||||
"createdAt": time$0.Time;
|
||||
"updatedAt": time$0.Time;
|
||||
"createdAt": string;
|
||||
"updatedAt": string;
|
||||
|
||||
/** Creates a new Theme instance. */
|
||||
constructor($$source: Partial<Theme> = {}) {
|
||||
@@ -1164,10 +1176,10 @@ export class Theme {
|
||||
this["isDefault"] = false;
|
||||
}
|
||||
if (!("createdAt" in $$source)) {
|
||||
this["createdAt"] = null;
|
||||
this["createdAt"] = "";
|
||||
}
|
||||
if (!("updatedAt" in $$source)) {
|
||||
this["updatedAt"] = null;
|
||||
this["updatedAt"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
@@ -1187,9 +1199,20 @@ export class Theme {
|
||||
}
|
||||
|
||||
/**
|
||||
* ThemeColorConfig 主题颜色配置
|
||||
* ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致)
|
||||
*/
|
||||
export class ThemeColorConfig {
|
||||
/**
|
||||
* 主题基本信息
|
||||
* 主题名称
|
||||
*/
|
||||
"name": string;
|
||||
|
||||
/**
|
||||
* 是否为深色主题
|
||||
*/
|
||||
"dark": boolean;
|
||||
|
||||
/**
|
||||
* 基础色调
|
||||
* 主背景色
|
||||
@@ -1197,7 +1220,7 @@ export class ThemeColorConfig {
|
||||
"background": string;
|
||||
|
||||
/**
|
||||
* 次要背景色
|
||||
* 次要背景色(用于代码块交替背景)
|
||||
*/
|
||||
"backgroundSecondary": string;
|
||||
|
||||
@@ -1207,6 +1230,17 @@ export class ThemeColorConfig {
|
||||
"surface": string;
|
||||
|
||||
/**
|
||||
* 下拉菜单背景
|
||||
*/
|
||||
"dropdownBackground": string;
|
||||
|
||||
/**
|
||||
* 下拉菜单边框
|
||||
*/
|
||||
"dropdownBorder": string;
|
||||
|
||||
/**
|
||||
* 文本颜色
|
||||
* 主文本色
|
||||
*/
|
||||
"foreground": string;
|
||||
@@ -1217,12 +1251,12 @@ export class ThemeColorConfig {
|
||||
"foregroundSecondary": string;
|
||||
|
||||
/**
|
||||
* 语法高亮
|
||||
* 注释色
|
||||
*/
|
||||
"comment": string;
|
||||
|
||||
/**
|
||||
* 语法高亮色 - 核心
|
||||
* 关键字
|
||||
*/
|
||||
"keyword": string;
|
||||
@@ -1257,6 +1291,42 @@ export class ThemeColorConfig {
|
||||
*/
|
||||
"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;
|
||||
|
||||
/**
|
||||
* 活动行号
|
||||
* 活动行号颜色
|
||||
*/
|
||||
"activeLineNumber": string;
|
||||
|
||||
/**
|
||||
* 边框分割线
|
||||
* 边框和分割线
|
||||
* 边框色
|
||||
*/
|
||||
"borderColor": string;
|
||||
@@ -1300,7 +1370,7 @@ export class ThemeColorConfig {
|
||||
"borderLight": string;
|
||||
|
||||
/**
|
||||
* 搜索匹配
|
||||
* 搜索和匹配
|
||||
* 搜索匹配
|
||||
*/
|
||||
"searchMatch": string;
|
||||
@@ -1312,6 +1382,12 @@ export class ThemeColorConfig {
|
||||
|
||||
/** Creates a new ThemeColorConfig instance. */
|
||||
constructor($$source: Partial<ThemeColorConfig> = {}) {
|
||||
if (!("name" in $$source)) {
|
||||
this["name"] = "";
|
||||
}
|
||||
if (!("dark" in $$source)) {
|
||||
this["dark"] = false;
|
||||
}
|
||||
if (!("background" in $$source)) {
|
||||
this["background"] = "";
|
||||
}
|
||||
@@ -1321,6 +1397,12 @@ export class ThemeColorConfig {
|
||||
if (!("surface" in $$source)) {
|
||||
this["surface"] = "";
|
||||
}
|
||||
if (!("dropdownBackground" in $$source)) {
|
||||
this["dropdownBackground"] = "";
|
||||
}
|
||||
if (!("dropdownBorder" in $$source)) {
|
||||
this["dropdownBorder"] = "";
|
||||
}
|
||||
if (!("foreground" in $$source)) {
|
||||
this["foreground"] = "";
|
||||
}
|
||||
@@ -1351,6 +1433,27 @@ export class ThemeColorConfig {
|
||||
if (!("type" in $$source)) {
|
||||
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)) {
|
||||
this["cursor"] = "";
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
// @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 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 models$0 from "../models/models.js";
|
||||
@@ -54,6 +57,11 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2900331732, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* StartAutoBackup 启动自动备份定时器
|
||||
*/
|
||||
|
||||
@@ -10,10 +10,17 @@
|
||||
// @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 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 models$0 from "../models/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* Get 获取配置项
|
||||
*/
|
||||
@@ -34,22 +41,6 @@ export function GetConfig(): Promise<models$0.AppConfig | null> & { cancel(): vo
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetConfigDir 获取配置目录
|
||||
*/
|
||||
export function GetConfigDir(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2275626561) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetSettingsPath 获取设置文件路径
|
||||
*/
|
||||
export function GetSettingsPath(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2175583370) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* MigrateConfig 执行配置迁移
|
||||
*/
|
||||
@@ -74,6 +65,14 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
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(3311949428, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 设置配置项
|
||||
*/
|
||||
@@ -83,34 +82,18 @@ export function Set(key: string, value: any): Promise<void> & { cancel(): void }
|
||||
}
|
||||
|
||||
/**
|
||||
* SetBackupConfigChangeCallback 设置备份配置变更回调
|
||||
* Watch 注册配置变更监听器
|
||||
*/
|
||||
export function SetBackupConfigChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3264871659, callback) as any;
|
||||
export function Watch(path: string, callback: $models.ObserverCallback): Promise<$models.CancelFunc> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1143583035, path, callback) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetDataPathChangeCallback 设置数据路径配置变更回调
|
||||
* WatchWithContext 使用 Context 注册监听器
|
||||
*/
|
||||
export function SetDataPathChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(393017412, callback) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetHotkeyChangeCallback 设置热键配置变更回调
|
||||
*/
|
||||
export function SetHotkeyChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(283872321, callback) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetWindowSnapConfigChangeCallback 设置窗口吸附配置变更回调
|
||||
*/
|
||||
export function SetWindowSnapConfigChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2324961653, callback) as any;
|
||||
export function WatchWithContext(path: string, callback: $models.ObserverCallback): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1454973098, path, callback) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
// @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 application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
|
||||
/**
|
||||
* SelectDirectory 打开目录选择对话框
|
||||
*/
|
||||
@@ -29,11 +25,3 @@ export function SelectFile(): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(37302920) as any;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* HotkeyService Windows全局热键服务
|
||||
* HotkeyService 全局热键服务
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -32,8 +32,8 @@ export function GetCurrentHotkey(): Promise<models$0.HotkeyCombo | null> & { can
|
||||
/**
|
||||
* Initialize 初始化热键服务
|
||||
*/
|
||||
export function Initialize(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3671360458, app, mainWindow) as any;
|
||||
export function Initialize(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3671360458) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ export function IsRegistered(): Promise<boolean> & { cancel(): void } {
|
||||
/**
|
||||
* RegisterHotkey 注册全局热键
|
||||
*/
|
||||
export function RegisterHotkey(hotkey: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1103945691, hotkey) as any;
|
||||
export function RegisterHotkey(combo: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1103945691, combo) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,14 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceStartup 服务启动时初始化
|
||||
*/
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3079990808, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UnregisterHotkey 取消注册全局热键
|
||||
*/
|
||||
@@ -72,8 +80,8 @@ export function UnregisterHotkey(): Promise<void> & { cancel(): void } {
|
||||
/**
|
||||
* UpdateHotkey 更新热键配置
|
||||
*/
|
||||
export function UpdateHotkey(enable: boolean, hotkey: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(823285555, enable, hotkey) as any;
|
||||
export function UpdateHotkey(enable: boolean, combo: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(823285555, enable, combo) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ExtensionService from "./extensionservice.js";
|
||||
import * as HotkeyService from "./hotkeyservice.js";
|
||||
import * as HttpClientService from "./httpclientservice.js";
|
||||
import * as KeyBindingService from "./keybindingservice.js";
|
||||
import * as MigrationService from "./migrationservice.js";
|
||||
import * as SelfUpdateService from "./selfupdateservice.js";
|
||||
@@ -26,6 +27,7 @@ export {
|
||||
DocumentService,
|
||||
ExtensionService,
|
||||
HotkeyService,
|
||||
HttpClientService,
|
||||
KeyBindingService,
|
||||
MigrationService,
|
||||
SelfUpdateService,
|
||||
|
||||
@@ -7,7 +7,118 @@ import {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";
|
||||
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";
|
||||
|
||||
/**
|
||||
* CancelFunc 取消订阅函数
|
||||
* 调用此函数可以取消对配置的监听
|
||||
*/
|
||||
export type CancelFunc = any;
|
||||
|
||||
/**
|
||||
* 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 内存统计信息
|
||||
@@ -119,6 +230,47 @@ export enum MigrationStatus {
|
||||
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>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ObserverCallback 观察者回调函数
|
||||
*/
|
||||
export type ObserverCallback = any;
|
||||
|
||||
/**
|
||||
* SelfUpdateResult 自我更新结果
|
||||
*/
|
||||
@@ -203,61 +355,63 @@ export class SelfUpdateResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowInfo 窗口信息(简化版)
|
||||
* SystemInfo 系统信息
|
||||
*/
|
||||
export class WindowInfo {
|
||||
"Window": application$0.WebviewWindow | null;
|
||||
"DocumentID": number;
|
||||
"Title": string;
|
||||
export class SystemInfo {
|
||||
"os": string;
|
||||
"arch": string;
|
||||
"debug": boolean;
|
||||
"osInfo": OSInfo | null;
|
||||
"platformInfo": { [_: string]: any };
|
||||
|
||||
/** Creates a new WindowInfo instance. */
|
||||
constructor($$source: Partial<WindowInfo> = {}) {
|
||||
if (!("Window" in $$source)) {
|
||||
this["Window"] = null;
|
||||
/** Creates a new SystemInfo instance. */
|
||||
constructor($$source: Partial<SystemInfo> = {}) {
|
||||
if (!("os" in $$source)) {
|
||||
this["os"] = "";
|
||||
}
|
||||
if (!("DocumentID" in $$source)) {
|
||||
this["DocumentID"] = 0;
|
||||
if (!("arch" in $$source)) {
|
||||
this["arch"] = "";
|
||||
}
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = "";
|
||||
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 WindowInfo instance from a string or object.
|
||||
* Creates a new SystemInfo instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WindowInfo {
|
||||
const $$createField0_0 = $$createType1;
|
||||
static createFrom($$source: any = {}): SystemInfo {
|
||||
const $$createField3_0 = $$createType5;
|
||||
const $$createField4_0 = $$createType6;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("Window" in $$parsedSource) {
|
||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||
if ("osInfo" in $$parsedSource) {
|
||||
$$parsedSource["osInfo"] = $$createField3_0($$parsedSource["osInfo"]);
|
||||
}
|
||||
return new WindowInfo($$parsedSource as Partial<WindowInfo>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WindowSnapService 窗口吸附服务
|
||||
*/
|
||||
export class WindowSnapService {
|
||||
|
||||
/** Creates a new WindowSnapService instance. */
|
||||
constructor($$source: Partial<WindowSnapService> = {}) {
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WindowSnapService instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): WindowSnapService {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new WindowSnapService($$parsedSource as Partial<WindowSnapService>);
|
||||
if ("platformInfo" in $$parsedSource) {
|
||||
$$parsedSource["platformInfo"] = $$createField4_0($$parsedSource["platformInfo"]);
|
||||
}
|
||||
return new SystemInfo($$parsedSource as Partial<SystemInfo>);
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = application$0.WebviewWindow.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
|
||||
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);
|
||||
|
||||
@@ -34,6 +34,18 @@ export function GetMemoryStats(): Promise<$models.MemoryStats> & { cancel(): voi
|
||||
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 手动触发垃圾回收
|
||||
*/
|
||||
@@ -44,3 +56,5 @@ export function TriggerGC(): Promise<void> & { cancel(): void } {
|
||||
|
||||
// Private type creation functions
|
||||
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
|
||||
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 获取所有主题
|
||||
*/
|
||||
@@ -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 } {
|
||||
let $resultPromise = $Call.ByID(3801788118) 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;
|
||||
export function GetThemeByIdOrName(id: number, ...name: string[]): Promise<models$0.Theme | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(127385338, id, name) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) 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 } {
|
||||
let $resultPromise = $Call.ByID(342461245, themeType) as any;
|
||||
export function ResetTheme(id: number, ...name: string[]): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1806334457, id, name) as any;
|
||||
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 } {
|
||||
let $resultPromise = $Call.ByID(2750902529, themeType, colors) as any;
|
||||
export function UpdateTheme(id: number, colors: models$0.ThemeColorConfig): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(70189749, id, colors) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
@@ -101,4 +78,3 @@ export function UpdateThemeColors(themeType: models$0.ThemeType, colors: models$
|
||||
const $$createType0 = models$0.Theme.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
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
|
||||
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 获取翻译器的语言列表
|
||||
* @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 } {
|
||||
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) => {
|
||||
return $$createType2($result);
|
||||
}) as any;
|
||||
@@ -73,6 +65,6 @@ export function TranslateWith(text: string, $from: string, to: string, translato
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $Create.Array($Create.Any);
|
||||
const $$createType1 = translator$0.LanguageInfo.createFrom;
|
||||
const $$createType2 = $Create.Map($Create.Any, $$createType1);
|
||||
const $$createType0 = translator$0.LanguageInfo.createFrom;
|
||||
const $$createType1 = $Create.Map($Create.Any, $$createType0);
|
||||
const $$createType2 = $Create.Array($Create.Any);
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
// @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 application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
/**
|
||||
* AutoShowHide 自动显示/隐藏主窗口
|
||||
*/
|
||||
export function AutoShowHide(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(4044219428) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* HandleWindowClose 处理窗口关闭事件
|
||||
@@ -38,14 +42,6 @@ export function MinimizeButtonClicked(): Promise<void> & { cancel(): void } {
|
||||
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 检查是否应该最小化到托盘
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* WindowService 窗口管理服务(专注于窗口生命周期管理)
|
||||
* WindowService 窗口管理服务
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -14,17 +14,13 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
// @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
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* GetOpenWindows 获取所有打开的窗口信息
|
||||
* GetOpenWindows 获取所有打开的文档窗口
|
||||
*/
|
||||
export function GetOpenWindows(): Promise<$models.WindowInfo[]> & { cancel(): void } {
|
||||
export function GetOpenWindows(): Promise<application$0.Window[]> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1464997251) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
return $$createType0($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
@@ -55,21 +51,12 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
}
|
||||
|
||||
/**
|
||||
* SetAppReferences 设置应用和主窗口引用
|
||||
* ServiceStartup 服务启动时初始化
|
||||
*/
|
||||
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 设置窗口吸附服务引用
|
||||
*/
|
||||
export function SetWindowSnapService(snapService: $models.WindowSnapService | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1105193745, snapService) as any;
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2432987694, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.WindowInfo.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
const $$createType0 = $Create.Array($Create.Any);
|
||||
|
||||
8
frontend/components.d.ts
vendored
@@ -1,8 +1,11 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
@@ -16,6 +19,9 @@ declare module 'vue' {
|
||||
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
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']
|
||||
WindowsTitleBar: typeof import('./src/components/titlebar/WindowsTitleBar.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',
|
||||
'/bin',
|
||||
'Dockerfile',
|
||||
'**/bindings/'
|
||||
'**/bindings/',
|
||||
'*.js',
|
||||
'**/*.js',
|
||||
'**/*.cjs',
|
||||
'**/*.mjs',
|
||||
],
|
||||
}
|
||||
]);
|
||||
2108
frontend/package-lock.json
generated
@@ -9,82 +9,94 @@
|
||||
"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",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
"lint:fix": "eslint --fix",
|
||||
"build:lang-parser": "node src/views/editor/extensions/codeblock/lang-parser/build-parser.js",
|
||||
"build:mermaid-parser": "node src/views/editor/language/mermaid/build-parsers.js",
|
||||
"test": "vitest",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs",
|
||||
"app:dev": "cd .. &&wails3 dev",
|
||||
"app:build": "cd .. && wails3 task build",
|
||||
"app:package": "cd .. && wails3 package",
|
||||
"app:generate": "cd .. && wails3 generate bindings -ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.7",
|
||||
"@codemirror/commands": "^6.8.1",
|
||||
"@codemirror/autocomplete": "^6.19.1",
|
||||
"@codemirror/commands": "^6.10.0",
|
||||
"@codemirror/lang-angular": "^0.1.4",
|
||||
"@codemirror/lang-cpp": "^6.0.3",
|
||||
"@codemirror/lang-css": "^6.3.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-javascript": "^6.2.4",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-less": "^6.0.2",
|
||||
"@codemirror/lang-lezer": "^6.0.2",
|
||||
"@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-python": "^6.2.1",
|
||||
"@codemirror/lang-rust": "^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-wast": "^6.0.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.11.3",
|
||||
"@codemirror/language-data": "^6.5.1",
|
||||
"@codemirror/legacy-modes": "^6.5.1",
|
||||
"@codemirror/lint": "^6.8.5",
|
||||
"@codemirror/language-data": "^6.5.2",
|
||||
"@codemirror/legacy-modes": "^6.5.2",
|
||||
"@codemirror/lint": "^6.9.1",
|
||||
"@codemirror/search": "^6.5.11",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.38.2",
|
||||
"@codemirror/view": "^6.38.6",
|
||||
"@cospaia/prettier-plugin-clojure": "^0.0.2",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.2",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@replit/codemirror-lang-svelte": "^6.0.0",
|
||||
"@toml-tools/lexer": "^1.0.0",
|
||||
"@toml-tools/parser": "^1.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"codemirror-lang-elixir": "^4.0.0",
|
||||
"colors-named": "^1.0.2",
|
||||
"colors-named-hex": "^1.0.2",
|
||||
"franc-min": "^6.2.0",
|
||||
"groovy-beautify": "^0.0.17",
|
||||
"hsl-matcher": "^1.2.4",
|
||||
"java-parser": "^3.0.1",
|
||||
"jsox": "^1.2.123",
|
||||
"linguist-languages": "^9.0.0",
|
||||
"linguist-languages": "^9.1.0",
|
||||
"php-parser": "^3.2.5",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"prettier": "^3.6.2",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.92.1",
|
||||
"vue": "^3.5.21",
|
||||
"sass": "^1.93.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@eslint/js": "^9.39.0",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/node": "^24.3.1",
|
||||
"@types/node": "^24.9.2",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@wailsio/runtime": "latest",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"globals": "^16.4.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.5",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-plugin-vue": "^10.5.1",
|
||||
"globals": "^16.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vitepress": "^2.0.0-alpha.12",
|
||||
"vitest": "^4.0.6",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.6"
|
||||
"vue-tsc": "^3.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted} from 'vue';
|
||||
import {onBeforeMount} from 'vue';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useSystemStore} from '@/stores/systemStore';
|
||||
import {useKeybindingStore} from '@/stores/keybindingStore';
|
||||
import {useThemeStore} from '@/stores/themeStore';
|
||||
import {useUpdateStore} from '@/stores/updateStore';
|
||||
import {useBackupStore} from '@/stores/backupStore';
|
||||
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
|
||||
import {useTranslationStore} from "@/stores/translationStore";
|
||||
|
||||
const configStore = useConfigStore();
|
||||
const systemStore = useSystemStore();
|
||||
const keybindingStore = useKeybindingStore();
|
||||
const themeStore = useThemeStore();
|
||||
const updateStore = useUpdateStore();
|
||||
const backupStore = useBackupStore();
|
||||
const translationStore = useTranslationStore();
|
||||
|
||||
// 应用启动时加载配置和初始化系统信息
|
||||
onMounted(async () => {
|
||||
onBeforeMount(async () => {
|
||||
// 并行初始化配置、系统信息和快捷键配置
|
||||
await Promise.all([
|
||||
configStore.initConfig(),
|
||||
@@ -26,11 +25,9 @@ onMounted(async () => {
|
||||
|
||||
// 初始化语言和主题
|
||||
await configStore.initializeLanguage();
|
||||
themeStore.initializeTheme();
|
||||
|
||||
// 初始化备份服务
|
||||
await backupStore.initialize();
|
||||
|
||||
await themeStore.initializeTheme();
|
||||
await translationStore.loadTranslators();
|
||||
|
||||
// 启动时检查更新
|
||||
await updateStore.checkOnStartup();
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
--dark-toolbar-text: #ffffff;
|
||||
--dark-toolbar-text-secondary: #cccccc;
|
||||
--dark-toolbar-button-hover: #404040;
|
||||
--dark-tab-active-line: linear-gradient(90deg, #007acc 0%, #0099ff 100%);
|
||||
--dark-bg-secondary: #0E1217;
|
||||
--dark-text-secondary: #a0aec0;
|
||||
--dark-text-muted: #666;
|
||||
@@ -40,6 +41,7 @@
|
||||
--light-toolbar-text: #212529;
|
||||
--light-toolbar-text-secondary: #495057;
|
||||
--light-toolbar-button-hover: #e9ecef;
|
||||
--light-tab-active-line: linear-gradient(90deg, #0066cc 0%, #0088ff 100%);
|
||||
--light-bg-secondary: #f7fef7;
|
||||
--light-text-secondary: #374151;
|
||||
--light-text-muted: #6b7280;
|
||||
@@ -73,6 +75,7 @@
|
||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||
--toolbar-button-hover: 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);
|
||||
--text-secondary: var(--dark-text-secondary);
|
||||
--text-muted: var(--dark-text-muted);
|
||||
@@ -112,6 +115,7 @@
|
||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||
--toolbar-button-hover: 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);
|
||||
--text-secondary: var(--dark-text-secondary);
|
||||
--text-muted: var(--dark-text-muted);
|
||||
@@ -149,6 +153,7 @@
|
||||
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
||||
--toolbar-button-hover: 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);
|
||||
--text-secondary: var(--light-text-secondary);
|
||||
--text-muted: var(--light-text-muted);
|
||||
@@ -185,6 +190,7 @@
|
||||
--toolbar-text-secondary: var(--light-toolbar-text-secondary);
|
||||
--toolbar-button-hover: 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);
|
||||
--text-secondary: var(--light-text-secondary);
|
||||
--text-muted: var(--light-text-muted);
|
||||
@@ -220,6 +226,7 @@
|
||||
--toolbar-text-secondary: var(--dark-toolbar-text-secondary);
|
||||
--toolbar-button-hover: 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);
|
||||
--text-secondary: var(--dark-text-secondary);
|
||||
--text-muted: var(--dark-text-muted);
|
||||
|
||||
@@ -46,6 +46,7 @@ export const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
||||
globalHotkey: 'general.globalHotkey',
|
||||
enableWindowSnap: 'general.enableWindowSnap',
|
||||
enableLoadingAnimation: 'general.enableLoadingAnimation',
|
||||
enableTabs: 'general.enableTabs',
|
||||
} as const;
|
||||
|
||||
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 = {
|
||||
language: 'appearance.language',
|
||||
systemTheme: 'appearance.systemTheme'
|
||||
systemTheme: 'appearance.systemTheme',
|
||||
currentTheme: 'appearance.currentTheme'
|
||||
} as const;
|
||||
|
||||
export const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
||||
@@ -113,11 +115,12 @@ export const DEFAULT_CONFIG: AppConfig = {
|
||||
},
|
||||
enableWindowSnap: true,
|
||||
enableLoadingAnimation: true,
|
||||
enableTabs: false,
|
||||
},
|
||||
editing: {
|
||||
fontSize: CONFIG_LIMITS.fontSize.default,
|
||||
fontFamily: FONT_OPTIONS[0].value,
|
||||
fontWeight: 'normal',
|
||||
fontWeight: '400',
|
||||
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
||||
enableTabIndent: true,
|
||||
tabSize: CONFIG_LIMITS.tabSize.default,
|
||||
@@ -126,7 +129,8 @@ export const DEFAULT_CONFIG: AppConfig = {
|
||||
},
|
||||
appearance: {
|
||||
language: LanguageType.LangZhCN,
|
||||
systemTheme: SystemThemeType.SystemThemeAuto
|
||||
systemTheme: SystemThemeType.SystemThemeAuto,
|
||||
currentTheme: 'default-dark'
|
||||
},
|
||||
updates: {
|
||||
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 = [
|
||||
{
|
||||
code: 'zh-CN' as SupportedLocaleType,
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
code: 'en-US' as SupportedLocaleType,
|
||||
name: 'English'
|
||||
},
|
||||
{
|
||||
code: 'zh-CN' as SupportedLocaleType,
|
||||
name: '简体中文'
|
||||
}
|
||||
] 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: () => {
|
||||
return typeof process != "undefined" &&
|
||||
Object.prototype.toString.call(process) == "[object process]" &&
|
||||
process.platform == "win32"
|
||||
process.platform == "win32";
|
||||
},
|
||||
_85: s => JSON.stringify(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),
|
||||
_159: Function.prototype.call.bind(DataView.prototype.setFloat64),
|
||||
_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) =>
|
||||
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
|
||||
_187: (s, m) => {
|
||||
@@ -337,14 +337,14 @@ _272: (x0,x1) => x0.lastIndex = x1
|
||||
});
|
||||
|
||||
return dartInstance;
|
||||
}
|
||||
};
|
||||
|
||||
// Call the main function for the instantiated module
|
||||
// `moduleInstance` is the instantiated dart2wasm module
|
||||
// `args` are any arguments that should be passed into the main function.
|
||||
export const invoke = (moduleInstance, ...args) => {
|
||||
moduleInstance.exports.$invokeMain(args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export let format;
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/reteps/dockerfmt/lib"
|
||||
"docker_fmt/lib"
|
||||
)
|
||||
|
||||
func Format(this js.Value, args []js.Value) any {
|
||||
@@ -43,7 +43,10 @@ func Format(this js.Value, args []js.Value) any {
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ module docker_fmt
|
||||
|
||||
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 (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // 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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
mvdan.cc/sh/v3 v3.11.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // 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/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.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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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=
|
||||
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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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.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 dockerfileInit, { format } from './docker_fmt_vite.js'
|
||||
import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier';
|
||||
import dockerfileInit, { format } from './docker_fmt_vite.js';
|
||||
|
||||
// Language configuration for Dockerfile
|
||||
const languages: SupportLanguage[] = [
|
||||
@@ -11,7 +11,7 @@ const languages: SupportLanguage[] = [
|
||||
linguistLanguageId: 99,
|
||||
vscodeLanguageIds: ['dockerfile'],
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
// Parser configuration
|
||||
const parsers: Record<string, Parser<any>> = {
|
||||
@@ -19,69 +19,60 @@ const parsers: Record<string, Parser<any>> = {
|
||||
parse: (text: string) => {
|
||||
// For Dockerfile, we don't need complex parsing, just return the text
|
||||
// The formatting will be handled by the print function
|
||||
return { type: 'dockerfile', value: text }
|
||||
return { type: 'dockerfile', value: text };
|
||||
},
|
||||
astFormat: 'dockerfile',
|
||||
locStart: () => 0,
|
||||
locEnd: () => 0,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Printer configuration
|
||||
const printers: Record<string, Printer<any>> = {
|
||||
dockerfile: {
|
||||
// @ts-expect-error -- Support async printer like shell plugin
|
||||
async print(path: any, options: any) {
|
||||
await ensureInitialized()
|
||||
const text = path.getValue().value || path.getValue()
|
||||
await ensureInitialized();
|
||||
const text = path.getValue().value || path.getValue();
|
||||
|
||||
try {
|
||||
const formatted = format(text, {
|
||||
indent: options.tabWidth || 2,
|
||||
trailingNewline: true,
|
||||
spaceRedirects: options.spaceRedirects !== false,
|
||||
})
|
||||
return formatted
|
||||
});
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
console.warn('Dockerfile formatting error:', error)
|
||||
return text
|
||||
console.warn('Dockerfile formatting error:', error);
|
||||
return text;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// WASM initialization
|
||||
let isInitialized = false
|
||||
let initPromise: Promise<void> | null = null
|
||||
let isInitialized = false;
|
||||
let initPromise: Promise<void> | null = null;
|
||||
|
||||
async function ensureInitialized(): Promise<void> {
|
||||
if (isInitialized) {
|
||||
return Promise.resolve()
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!initPromise) {
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
await dockerfileInit()
|
||||
isInitialized = true
|
||||
await dockerfileInit();
|
||||
isInitialized = true;
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize Dockerfile WASM module:', error)
|
||||
initPromise = null
|
||||
throw error
|
||||
console.warn('Failed to initialize Dockerfile WASM module:', error);
|
||||
initPromise = null;
|
||||
throw error;
|
||||
}
|
||||
})()
|
||||
})();
|
||||
}
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
// Configuration mapping function
|
||||
function mapOptionsToConfig(options: any) {
|
||||
return {
|
||||
indent: options.tabWidth || 2,
|
||||
trailingNewline: options.insertFinalNewline !== false,
|
||||
spaceRedirects: options.spaceRedirects !== false,
|
||||
}
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
// Plugin options
|
||||
@@ -92,7 +83,7 @@ const options: Record<string, SupportOption> = {
|
||||
default: true,
|
||||
description: 'Add spaces around redirect operators',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Plugin definition
|
||||
const plugin: Plugin = {
|
||||
@@ -105,7 +96,7 @@ const plugin: Plugin = {
|
||||
useTabs: false,
|
||||
spaceRedirects: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default plugin
|
||||
export { languages, parsers, printers, options }
|
||||
export default plugin;
|
||||
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
|
||||
const goPrinter: Printer<string> = {
|
||||
// @ts-expect-error -- Support async printer like shell plugin
|
||||
async print(path, options) {
|
||||
async print(path, _options) {
|
||||
try {
|
||||
// Wait for initialization to complete
|
||||
await initGofmt();
|
||||
|
||||
@@ -39,7 +39,7 @@ const groovyPrinter: Printer<string> = {
|
||||
return groovyBeautify(path.node, {
|
||||
width: options.printWidth || 80,
|
||||
}).trim();
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return path.node;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ type ModifierNode = JavaNonTerminal & {
|
||||
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 {
|
||||
length: number;
|
||||
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function format(input: string, filename: string, config?: Config): string;
|
||||
|
||||
interface LayoutConfig {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export const memory: WebAssembly.Memory;
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function format(input: string, path?: string, config?: Config): string;
|
||||
|
||||
export interface Config {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export const memory: WebAssembly.Memory;
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function format(input: string, config?: Config | null): string;
|
||||
|
||||
export interface Config {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const format: (a: number, b: number, c: number, d: number) => void;
|
||||
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 {
|
||||
namespace globalThis {
|
||||
var fs: typeof _fs
|
||||
let fs: typeof _fs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function format(input: string, filename: string, config?: Config): string;
|
||||
|
||||
interface LayoutConfig {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export const memory: WebAssembly.Memory;
|
||||
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;
|
||||
|
||||