Compare commits
2
Commits
v1.2.0
..
80c8ecb4cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80c8ecb4cf | ||
|
|
737f83cd5f |
No files matched your search
@@ -0,0 +1,75 @@
|
|||||||
|
<!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>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
/* 更新日志页面样式 */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,717 @@
|
|||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
+203
@@ -0,0 +1,203 @@
|
|||||||
|
<!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>
|
||||||
|
<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">
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,552 @@
|
|||||||
|
/**
|
||||||
|
* VoidRaft - Changelog Script
|
||||||
|
* Fetches release information from GitHub API with Gitea fallback
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Repository information
|
||||||
|
const 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'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error messages with i18n support
|
||||||
|
const 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: '预发布'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Element references
|
||||||
|
const elements = {
|
||||||
|
loading: document.getElementById('loading'),
|
||||||
|
changelog: document.getElementById('changelog'),
|
||||||
|
error: document.getElementById('error-message')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the changelog
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
// Try GitHub API first
|
||||||
|
fetchReleases('github')
|
||||||
|
.catch(() => fetchReleases('gitea'))
|
||||||
|
.catch(error => {
|
||||||
|
elements.loading.style.display = 'none';
|
||||||
|
showError(MESSAGES.fetchError[getCurrentLang()]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current language
|
||||||
|
*/
|
||||||
|
function getCurrentLang() {
|
||||||
|
return window.currentLang || 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch releases from specified source
|
||||||
|
* @param {string} source - 'github' or 'gitea'
|
||||||
|
*/
|
||||||
|
async function fetchReleases(source) {
|
||||||
|
const apiUrl = REPOS[source].apiUrl;
|
||||||
|
const errorMessageKey = source === 'github' ? 'githubApiError' : 'giteaApiError';
|
||||||
|
|
||||||
|
// Setup timeout for GitHub
|
||||||
|
const options = {
|
||||||
|
headers: { 'Accept': 'application/json' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (source === 'github') {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||||
|
options.signal = controller.signal;
|
||||||
|
options.headers['Accept'] = 'application/vnd.github.v3+json';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl, options);
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${MESSAGES[errorMessageKey][getCurrentLang()]}${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const releases = await response.json();
|
||||||
|
|
||||||
|
if (!releases || releases.length === 0) {
|
||||||
|
throw new Error(MESSAGES.noReleases[getCurrentLang()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display releases
|
||||||
|
elements.loading.style.display = 'none';
|
||||||
|
displayReleases(releases, source);
|
||||||
|
elements.changelog.style.display = 'block';
|
||||||
|
|
||||||
|
return releases;
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await fetch(apiUrl, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${MESSAGES[errorMessageKey][getCurrentLang()]}${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const releases = await response.json();
|
||||||
|
|
||||||
|
// Hide loading indicator
|
||||||
|
elements.loading.style.display = 'none';
|
||||||
|
|
||||||
|
if (!releases || releases.length === 0) {
|
||||||
|
throw new Error(MESSAGES.noReleases[getCurrentLang()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display releases
|
||||||
|
displayReleases(releases, source);
|
||||||
|
elements.changelog.style.display = 'block';
|
||||||
|
|
||||||
|
return releases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message
|
||||||
|
*/
|
||||||
|
function showError(message) {
|
||||||
|
const errorMessageElement = elements.error.querySelector('p');
|
||||||
|
errorMessageElement.textContent = message;
|
||||||
|
elements.error.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display releases
|
||||||
|
* @param {Array} releases - Array of release objects
|
||||||
|
* @param {string} source - 'github' or 'gitea'
|
||||||
|
*/
|
||||||
|
function displayReleases(releases, source) {
|
||||||
|
// Clear existing content
|
||||||
|
elements.changelog.innerHTML = '';
|
||||||
|
|
||||||
|
// Add data source indicator
|
||||||
|
const sourceElement = createSourceElement(source);
|
||||||
|
elements.changelog.appendChild(sourceElement);
|
||||||
|
|
||||||
|
// Create release elements
|
||||||
|
releases.forEach(release => {
|
||||||
|
const releaseElement = createReleaseElement(release, source);
|
||||||
|
elements.changelog.appendChild(releaseElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create source element
|
||||||
|
*/
|
||||||
|
function createSourceElement(source) {
|
||||||
|
const sourceElement = document.createElement('div');
|
||||||
|
sourceElement.className = 'data-source';
|
||||||
|
|
||||||
|
// Create source label with i18n support
|
||||||
|
const sourceLabel = document.createElement('span');
|
||||||
|
sourceLabel.setAttribute('data-en', MESSAGES.dataSource.en);
|
||||||
|
sourceLabel.setAttribute('data-zh', MESSAGES.dataSource.zh);
|
||||||
|
sourceLabel.textContent = MESSAGES.dataSource[getCurrentLang()];
|
||||||
|
|
||||||
|
// Create link
|
||||||
|
const sourceLink = document.createElement('a');
|
||||||
|
sourceLink.href = REPOS[source].releasesUrl;
|
||||||
|
sourceLink.textContent = source === 'github' ? 'GitHub' : 'Gitea';
|
||||||
|
sourceLink.target = '_blank';
|
||||||
|
|
||||||
|
// Assemble elements
|
||||||
|
sourceElement.appendChild(sourceLabel);
|
||||||
|
sourceElement.appendChild(sourceLink);
|
||||||
|
|
||||||
|
return sourceElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create release element
|
||||||
|
* @param {Object} release - Release data
|
||||||
|
* @param {string} source - 'github' or 'gitea'
|
||||||
|
*/
|
||||||
|
function createReleaseElement(release, source) {
|
||||||
|
const releaseElement = document.createElement('div');
|
||||||
|
releaseElement.className = 'release';
|
||||||
|
|
||||||
|
// Format release date
|
||||||
|
const releaseDate = new Date(release.published_at || release.created_at);
|
||||||
|
const formattedDate = formatDate(releaseDate);
|
||||||
|
|
||||||
|
// Create header
|
||||||
|
const headerElement = createReleaseHeader(release, formattedDate);
|
||||||
|
releaseElement.appendChild(headerElement);
|
||||||
|
|
||||||
|
// Add release description
|
||||||
|
if (release.body) {
|
||||||
|
const descriptionElement = document.createElement('div');
|
||||||
|
descriptionElement.className = 'release-description markdown-content';
|
||||||
|
descriptionElement.innerHTML = parseMarkdown(release.body);
|
||||||
|
releaseElement.appendChild(descriptionElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add download assets
|
||||||
|
const assets = getAssetsFromRelease(release, source);
|
||||||
|
if (assets && assets.length > 0) {
|
||||||
|
const assetsElement = createAssetsElement(assets);
|
||||||
|
releaseElement.appendChild(assetsElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return releaseElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create release header
|
||||||
|
*/
|
||||||
|
function createReleaseHeader(release, formattedDate) {
|
||||||
|
const headerElement = document.createElement('div');
|
||||||
|
headerElement.className = 'release-header';
|
||||||
|
|
||||||
|
// Version element
|
||||||
|
const versionElement = document.createElement('div');
|
||||||
|
versionElement.className = 'release-version';
|
||||||
|
|
||||||
|
// Version text
|
||||||
|
const versionText = document.createElement('span');
|
||||||
|
versionText.textContent = release.name || release.tag_name;
|
||||||
|
versionElement.appendChild(versionText);
|
||||||
|
|
||||||
|
// Pre-release badge
|
||||||
|
if (release.prerelease) {
|
||||||
|
const preReleaseTag = document.createElement('span');
|
||||||
|
preReleaseTag.className = 'release-badge pre-release';
|
||||||
|
preReleaseTag.setAttribute('data-en', MESSAGES.preRelease.en);
|
||||||
|
preReleaseTag.setAttribute('data-zh', MESSAGES.preRelease.zh);
|
||||||
|
preReleaseTag.textContent = MESSAGES.preRelease[getCurrentLang()];
|
||||||
|
versionElement.appendChild(preReleaseTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date element
|
||||||
|
const dateElement = document.createElement('div');
|
||||||
|
dateElement.className = 'release-date';
|
||||||
|
dateElement.textContent = formattedDate;
|
||||||
|
|
||||||
|
headerElement.appendChild(versionElement);
|
||||||
|
headerElement.appendChild(dateElement);
|
||||||
|
|
||||||
|
return headerElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get assets from release based on source
|
||||||
|
*/
|
||||||
|
function getAssetsFromRelease(release, source) {
|
||||||
|
let assets = [];
|
||||||
|
|
||||||
|
if (source === 'github') {
|
||||||
|
assets = release.assets || [];
|
||||||
|
} else { // Gitea
|
||||||
|
assets = release.assets || [];
|
||||||
|
// Check for Gitea-specific asset structure
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create assets element
|
||||||
|
*/
|
||||||
|
function createAssetsElement(assets) {
|
||||||
|
const assetsElement = document.createElement('div');
|
||||||
|
assetsElement.className = 'release-assets';
|
||||||
|
|
||||||
|
// Assets title
|
||||||
|
const assetsTitle = document.createElement('div');
|
||||||
|
assetsTitle.className = 'release-assets-title';
|
||||||
|
assetsTitle.setAttribute('data-en', MESSAGES.downloads.en);
|
||||||
|
assetsTitle.setAttribute('data-zh', MESSAGES.downloads.zh);
|
||||||
|
assetsTitle.textContent = MESSAGES.downloads[getCurrentLang()];
|
||||||
|
|
||||||
|
// Asset list
|
||||||
|
const assetList = document.createElement('ul');
|
||||||
|
assetList.className = 'asset-list';
|
||||||
|
|
||||||
|
// Add each asset
|
||||||
|
assets.forEach(asset => {
|
||||||
|
const assetItem = createAssetItem(asset);
|
||||||
|
assetList.appendChild(assetItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
assetsElement.appendChild(assetsTitle);
|
||||||
|
assetsElement.appendChild(assetList);
|
||||||
|
|
||||||
|
return assetsElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create asset item
|
||||||
|
*/
|
||||||
|
function createAssetItem(asset) {
|
||||||
|
const assetItem = document.createElement('li');
|
||||||
|
assetItem.className = 'asset-item';
|
||||||
|
|
||||||
|
// File icon
|
||||||
|
const iconElement = document.createElement('i');
|
||||||
|
iconElement.className = `asset-icon fas fa-${getFileIcon(asset.name)}`;
|
||||||
|
|
||||||
|
// File name
|
||||||
|
const nameElement = document.createElement('span');
|
||||||
|
nameElement.className = 'asset-name';
|
||||||
|
nameElement.textContent = asset.name;
|
||||||
|
|
||||||
|
// File size
|
||||||
|
const sizeElement = document.createElement('span');
|
||||||
|
sizeElement.className = 'asset-size';
|
||||||
|
sizeElement.textContent = formatFileSize(asset.size);
|
||||||
|
|
||||||
|
// Download link
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.className = 'download-btn';
|
||||||
|
downloadLink.href = asset.browser_download_url;
|
||||||
|
downloadLink.target = '_blank';
|
||||||
|
downloadLink.setAttribute('data-en', MESSAGES.download.en);
|
||||||
|
downloadLink.setAttribute('data-zh', MESSAGES.download.zh);
|
||||||
|
downloadLink.textContent = MESSAGES.download[getCurrentLang()];
|
||||||
|
|
||||||
|
// Assemble asset item
|
||||||
|
assetItem.appendChild(iconElement);
|
||||||
|
assetItem.appendChild(nameElement);
|
||||||
|
assetItem.appendChild(sizeElement);
|
||||||
|
assetItem.appendChild(downloadLink);
|
||||||
|
|
||||||
|
return assetItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file icon based on extension
|
||||||
|
*/
|
||||||
|
function getFileIcon(filename) {
|
||||||
|
const ext = filename.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case 'zip':
|
||||||
|
case 'gz':
|
||||||
|
case 'tar':
|
||||||
|
case '7z':
|
||||||
|
return 'file-archive';
|
||||||
|
case 'exe':
|
||||||
|
return 'file-code';
|
||||||
|
case 'dmg':
|
||||||
|
return 'apple';
|
||||||
|
case 'deb':
|
||||||
|
case 'rpm':
|
||||||
|
return 'linux';
|
||||||
|
case 'json':
|
||||||
|
case 'xml':
|
||||||
|
return 'file-alt';
|
||||||
|
default:
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format file size
|
||||||
|
*/
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date
|
||||||
|
*/
|
||||||
|
function formatDate(date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Markdown parser
|
||||||
|
* Note: This is a very basic implementation that handles only common Markdown syntax
|
||||||
|
*/
|
||||||
|
function 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update translations when language changes
|
||||||
|
window.addEventListener('languageChanged', updateUI);
|
||||||
|
|
||||||
|
// Initial UI update based on current language
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update UI elements with current language
|
||||||
|
*/
|
||||||
|
function updateUI() {
|
||||||
|
const lang = getCurrentLang();
|
||||||
|
|
||||||
|
// Update all i18n elements
|
||||||
|
document.querySelectorAll('[data-en][data-zh]').forEach(el => {
|
||||||
|
if (el.hasAttribute(`data-${lang}`)) {
|
||||||
|
el.textContent = el.getAttribute(`data-${lang}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* VoidRaft - Website Script
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Initialize components
|
||||||
|
initThemeToggle();
|
||||||
|
initLanguageToggle();
|
||||||
|
initCardEffects();
|
||||||
|
|
||||||
|
// Console branding
|
||||||
|
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;');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize theme toggle functionality
|
||||||
|
*/
|
||||||
|
function initThemeToggle() {
|
||||||
|
const themeToggle = document.getElementById('theme-toggle');
|
||||||
|
if (!themeToggle) return;
|
||||||
|
|
||||||
|
// Get initial theme from local storage or system preference
|
||||||
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
const theme = savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light');
|
||||||
|
|
||||||
|
// Apply initial theme
|
||||||
|
setTheme(theme);
|
||||||
|
|
||||||
|
// Add click handler
|
||||||
|
themeToggle.addEventListener('click', () => {
|
||||||
|
document.body.classList.add('theme-transition');
|
||||||
|
|
||||||
|
const currentTheme = document.body.classList.contains('theme-dark') ? 'dark' : 'light';
|
||||||
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||||
|
|
||||||
|
setTheme(newTheme);
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
|
|
||||||
|
// Remove transition class after animation completes
|
||||||
|
setTimeout(() => document.body.classList.remove('theme-transition'), 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set theme to dark or light
|
||||||
|
* @param {string} theme - 'dark' or 'light'
|
||||||
|
*/
|
||||||
|
function setTheme(theme) {
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
|
// Update body class
|
||||||
|
document.body.classList.toggle('theme-dark', isDark);
|
||||||
|
document.body.classList.toggle('theme-light', !isDark);
|
||||||
|
|
||||||
|
// Update toggle icon
|
||||||
|
const themeToggle = document.getElementById('theme-toggle');
|
||||||
|
if (themeToggle) {
|
||||||
|
const icon = themeToggle.querySelector('i');
|
||||||
|
if (icon) {
|
||||||
|
icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize language toggle functionality
|
||||||
|
*/
|
||||||
|
function initLanguageToggle() {
|
||||||
|
const langToggle = document.getElementById('lang-toggle');
|
||||||
|
if (!langToggle) return;
|
||||||
|
|
||||||
|
// Get initial language from local storage or default to English
|
||||||
|
const savedLang = localStorage.getItem('lang');
|
||||||
|
const lang = savedLang || 'en';
|
||||||
|
|
||||||
|
// Set current language and apply it
|
||||||
|
window.currentLang = lang;
|
||||||
|
setLanguage(lang);
|
||||||
|
|
||||||
|
// Add click handler
|
||||||
|
langToggle.addEventListener('click', () => {
|
||||||
|
document.body.classList.add('lang-transition');
|
||||||
|
|
||||||
|
const newLang = window.currentLang === 'zh' ? 'en' : 'zh';
|
||||||
|
setLanguage(newLang);
|
||||||
|
window.currentLang = newLang;
|
||||||
|
localStorage.setItem('lang', newLang);
|
||||||
|
|
||||||
|
// Notify other components about language change
|
||||||
|
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang: newLang } }));
|
||||||
|
|
||||||
|
// Remove transition class after animation completes
|
||||||
|
setTimeout(() => document.body.classList.remove('lang-transition'), 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set page language
|
||||||
|
* @param {string} lang - 'zh' or 'en'
|
||||||
|
*/
|
||||||
|
function setLanguage(lang) {
|
||||||
|
// Update all elements with data-zh and data-en attributes
|
||||||
|
document.querySelectorAll('[data-zh][data-en]').forEach(el => {
|
||||||
|
el.textContent = el.getAttribute(`data-${lang}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update HTML language attribute
|
||||||
|
document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en';
|
||||||
|
|
||||||
|
// Update toggle button text
|
||||||
|
const langToggle = document.getElementById('lang-toggle');
|
||||||
|
if (langToggle) {
|
||||||
|
const text = lang === 'zh' ? 'EN/中' : '中/EN';
|
||||||
|
langToggle.innerHTML = `<i class="fas fa-language"></i> ${text}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize card hover effects
|
||||||
|
*/
|
||||||
|
function initCardEffects() {
|
||||||
|
const cards = document.querySelectorAll('.feature-card');
|
||||||
|
|
||||||
|
cards.forEach(card => {
|
||||||
|
card.addEventListener('mouseenter', () => {
|
||||||
|
card.style.transform = 'translateY(-8px)';
|
||||||
|
card.style.boxShadow = '7px 7px 0 var(--shadow-color)';
|
||||||
|
});
|
||||||
|
|
||||||
|
card.addEventListener('mouseleave', () => {
|
||||||
|
card.style.transform = 'translateY(0)';
|
||||||
|
card.style.boxShadow = '5px 5px 0 var(--shadow-color)';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -213,8 +213,29 @@ const handleDelete = async (doc: Document, event: Event) => {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (deleteConfirmId.value === doc.id) {
|
if (deleteConfirmId.value === doc.id) {
|
||||||
// 确认删除
|
// 确认删除前检查文档是否在其他窗口打开
|
||||||
try {
|
try {
|
||||||
|
const hasOpen = await windowStore.isDocumentWindowOpen(doc.id);
|
||||||
|
if (hasOpen) {
|
||||||
|
// 设置错误状态并启动定时器
|
||||||
|
alreadyOpenDocId.value = doc.id;
|
||||||
|
|
||||||
|
// 清除之前的定时器(如果存在)
|
||||||
|
if (errorMessageTimer.value) {
|
||||||
|
clearTimeout(errorMessageTimer.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置新的定时器,3秒后清除错误信息
|
||||||
|
errorMessageTimer.value = window.setTimeout(() => {
|
||||||
|
alreadyOpenDocId.value = null;
|
||||||
|
errorMessageTimer.value = null;
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// 取消删除确认状态
|
||||||
|
deleteConfirmId.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await documentStore.deleteDocument(doc.id);
|
await documentStore.deleteDocument(doc.id);
|
||||||
await documentStore.updateDocuments();
|
await documentStore.updateDocuments();
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user