💡 Add docs
This commit is contained in:
75
docs/changelog.html
Normal file
75
docs/changelog.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<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 更新日志</h1>
|
||||||
|
<div class="card-controls">
|
||||||
|
<button id="theme-toggle" class="btn btn-secondary" title="切换主题">
|
||||||
|
<i class="fas fa-sun"></i> 主题
|
||||||
|
</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="首页">首页</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="源代码">源代码</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载中提示 -->
|
||||||
|
<div id="loading" class="loading-container">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<p data-en="Loading releases..." data-zh="正在加载版本信息...">正在加载版本信息...</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="加载版本信息失败,请稍后再试。">加载版本信息失败,请稍后再试。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<footer class="footer">
|
||||||
|
<p class="footer-text" data-en="© 2023-2024 VoidRaft - An elegant text snippet recording tool designed for developers" data-zh="© 2023-2024 VoidRaft - 专为开发者打造的优雅文本片段记录工具">© 2023-2024 VoidRaft - 专为开发者打造的优雅文本片段记录工具</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="问题反馈">问题反馈</a>
|
||||||
|
<a href="https://github.com/landaiqing/voidraft/releases" target="_blank" class="footer-link" data-en="Releases" data-zh="版本发布">版本发布</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
<script src="js/changelog.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
312
docs/css/changelog.css
Normal file
312
docs/css/changelog.css
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
/* 更新日志页面样式 */
|
||||||
|
.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-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动设备响应式优化 */
|
||||||
|
@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;
|
||||||
|
}
|
717
docs/css/styles.css
Normal file
717
docs/css/styles.css
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Space+Mono&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap');
|
||||||
|
|
||||||
|
/* 浅色主题 */
|
||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
BIN
docs/img/favicon.ico
Normal file
BIN
docs/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
BIN
docs/img/logo.png
Normal file
BIN
docs/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
BIN
docs/img/screenshot-dark.png
Normal file
BIN
docs/img/screenshot-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
BIN
docs/img/screenshot-light.png
Normal file
BIN
docs/img/screenshot-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
205
docs/index.html
Normal file
205
docs/index.html
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<!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 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">VoidRaft</h1>
|
||||||
|
<div class="card-controls">
|
||||||
|
<button id="theme-toggle" class="btn btn-secondary" title="切换主题">
|
||||||
|
<i class="fas fa-sun"></i> 主题
|
||||||
|
</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="优雅的文本片段记录工具">优雅的文本片段记录工具</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="专为开发者打造,随时随地记录、整理和管理各种文本片段。">专为开发者打造,随时随地记录、整理和管理各种文本片段。</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> 下载
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/landaiqing/voidraft" class="btn btn-secondary" data-en="Source Code" data-zh="源代码">
|
||||||
|
<i class="fab fa-github"></i> 源代码
|
||||||
|
</a>
|
||||||
|
<a href="changelog.html" class="btn btn-secondary" data-en="Changelog" data-zh="更新日志">
|
||||||
|
<i class="fas fa-history"></i> 更新日志
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 特性部分 -->
|
||||||
|
<h2 data-en="Core Features" data-zh="核心特性">核心特性</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="开发者友好">开发者友好</h3>
|
||||||
|
<p class="feature-desc" data-en="Multi-language code blocks with syntax highlighting for 30+ programming languages" data-zh="多语言代码块支持,为30+种编程语言提供语法高亮">多语言代码块支持,为30+种编程语言提供语法高亮</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="代码格式化">代码格式化</h3>
|
||||||
|
<p class="feature-desc" data-en="Built-in Prettier support for one-click code beautification" data-zh="内置Prettier支持,一键美化代码">内置Prettier支持,一键美化代码</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="自定义主题">自定义主题</h3>
|
||||||
|
<p class="feature-desc" data-en="Dark/Light themes with full customization options" data-zh="深色/浅色主题,支持完全自定义">深色/浅色主题,支持完全自定义</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="多窗口支持">多窗口支持</h3>
|
||||||
|
<p class="feature-desc" data-en="Edit multiple documents simultaneously" data-zh="同时编辑多个文档">同时编辑多个文档</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="块状编辑">块状编辑</h3>
|
||||||
|
<p class="feature-desc" data-en="Split content into independent code blocks with different language settings" data-zh="将内容分割为独立的代码块,每个块可设置不同语言">将内容分割为独立的代码块,每个块可设置不同语言</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="丰富扩展">丰富扩展</h3>
|
||||||
|
<p class="feature-desc" data-en="Rainbow brackets, VSCode-style search, color picker, translation tool, and more" data-zh="彩虹括号、VSCode风格搜索、颜色选择器、翻译工具等多种扩展">彩虹括号、VSCode风格搜索、颜色选择器、翻译工具等多种扩展</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预览部分 -->
|
||||||
|
<h2 data-en="Preview" data-zh="预览">预览</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="技术栈">技术栈</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="跨平台桌面应用框架">跨平台桌面应用框架</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="快速高效的后端语言">快速高效的后端语言</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="现代化前端框架">现代化前端框架</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="支持扩展的现代化代码编辑器">支持扩展的现代化代码编辑器</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="轻量级文档存储数据库">轻量级文档存储数据库</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<footer class="footer">
|
||||||
|
<p class="footer-text" data-en="© 2023-2024 VoidRaft - An elegant text snippet recording tool designed for developers" data-zh="© 2023-2024 VoidRaft - 专为开发者打造的优雅文本片段记录工具">© 2023-2024 VoidRaft - 专为开发者打造的优雅文本片段记录工具</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="问题反馈">问题反馈</a>
|
||||||
|
<a href="https://github.com/landaiqing/voidraft/releases" target="_blank" class="footer-link" data-en="Releases" data-zh="版本发布">版本发布</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
494
docs/js/changelog.js
Normal file
494
docs/js/changelog.js
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
/**
|
||||||
|
* 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 '';
|
||||||
|
|
||||||
|
// Links - [text](url)
|
||||||
|
markdown = markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||||
|
|
||||||
|
// Headings - # Heading
|
||||||
|
markdown = markdown.replace(/^### (.*$)/gm, '<h3>$1</h3>');
|
||||||
|
markdown = markdown.replace(/^## (.*$)/gm, '<h2>$1</h2>');
|
||||||
|
markdown = markdown.replace(/^# (.*$)/gm, '<h1>$1</h1>');
|
||||||
|
|
||||||
|
// Bold - **text**
|
||||||
|
markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||||
|
|
||||||
|
// Italic - *text*
|
||||||
|
markdown = markdown.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||||
|
|
||||||
|
// Code blocks - ```code```
|
||||||
|
markdown = markdown.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||||
|
|
||||||
|
// Inline code - `code`
|
||||||
|
markdown = markdown.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||||
|
|
||||||
|
// Lists - * item
|
||||||
|
markdown = markdown.replace(/^\* (.*$)/gm, '<ul><li>$1</li></ul>');
|
||||||
|
|
||||||
|
// Lists - 1. item
|
||||||
|
markdown = markdown.replace(/^\d+\. (.*$)/gm, '<ol><li>$1</li></ol>');
|
||||||
|
|
||||||
|
// Merge adjacent list items
|
||||||
|
markdown = markdown.replace(/<\/ul>\s*<ul>/g, '');
|
||||||
|
markdown = markdown.replace(/<\/ol>\s*<ol>/g, '');
|
||||||
|
|
||||||
|
// Paragraphs - blank line
|
||||||
|
markdown = markdown.replace(/\n\n/g, '</p><p>');
|
||||||
|
|
||||||
|
// Line breaks - two spaces at end of line
|
||||||
|
markdown = markdown.replace(/ \n/g, '<br>');
|
||||||
|
|
||||||
|
return `<p>${markdown}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
138
docs/js/script.js
Normal file
138
docs/js/script.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 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 browser preference
|
||||||
|
const savedLang = localStorage.getItem('lang');
|
||||||
|
const userLang = navigator.language || navigator.userLanguage;
|
||||||
|
const defaultLang = userLang.includes('zh') ? 'zh' : 'en';
|
||||||
|
const lang = savedLang || defaultLang;
|
||||||
|
|
||||||
|
// 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