Compare commits
5
Commits
v1.2.0
...
6d8fdf62f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8fdf62f1 | ||
|
|
9f53d7421d | ||
|
|
80c8ecb4cf | ||
|
|
d10059a82d | ||
|
|
737f83cd5f |
No files matched your search
@@ -0,0 +1 @@
|
||||
voidraft.landaiqing.cn
|
||||
@@ -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 |
+256
@@ -0,0 +1,256 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VoidRaft - An elegant text snippet recording tool designed for developers.</title>
|
||||
<meta name="description" content="VoidRaft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
||||
<meta name="keywords" content="text editor, code snippets, developer tools, syntax highlighting, code formatting, multi-language, VoidRaft">
|
||||
<meta name="author" content="VoidRaft Team">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="https://landaiqing.github.io/voidraft/">
|
||||
|
||||
<!-- Internationalization / hreflang -->
|
||||
<link rel="alternate" hreflang="en" href="https://landaiqing.github.io/voidraft/">
|
||||
<link rel="alternate" hreflang="zh" href="https://landaiqing.github.io/voidraft/?lang=zh">
|
||||
<link rel="alternate" hreflang="x-default" href="https://landaiqing.github.io/voidraft/">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://landaiqing.github.io/voidraft/">
|
||||
<meta property="og:title" content="VoidRaft - An elegant text snippet recording tool designed for developers">
|
||||
<meta property="og:description" content="VoidRaft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
||||
<meta property="og:image" content="https://landaiqing.github.io/voidraft/img/screenshot-dark.png">
|
||||
<meta property="og:site_name" content="VoidRaft">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="https://landaiqing.github.io/voidraft/">
|
||||
<meta property="twitter:title" content="VoidRaft - An elegant text snippet recording tool designed for developers">
|
||||
<meta property="twitter:description" content="VoidRaft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.">
|
||||
<meta property="twitter:image" content="https://landaiqing.github.io/voidraft/img/screenshot-dark.png">
|
||||
|
||||
<link rel="stylesheet" href="./css/styles.css">
|
||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "VoidRaft",
|
||||
"description": "An elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.",
|
||||
"url": "https://landaiqing.github.io/voidraft/",
|
||||
"downloadUrl": "https://github.com/landaiqing/voidraft/releases",
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "VoidRaft"
|
||||
},
|
||||
"operatingSystem": ["Windows", "macOS", "Linux"],
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"screenshot": "https://landaiqing.github.io/voidraft/img/screenshot-dark.png",
|
||||
"softwareVersion": "Latest",
|
||||
"programmingLanguage": ["Go", "TypeScript", "Vue.js"],
|
||||
"codeRepository": "https://github.com/landaiqing/voidraft"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="theme-dark">
|
||||
<div class="container">
|
||||
<!-- 主卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="card-title">VoidRaft</h1>
|
||||
<div class="card-controls">
|
||||
<button id="theme-toggle" class="btn btn-secondary" title="切换主题">
|
||||
<i class="fas fa-sun"></i> <span data-en="Theme" data-zh="主题">Theme</span>
|
||||
</button>
|
||||
<button id="lang-toggle" class="btn btn-secondary">
|
||||
<i class="fas fa-language"></i> 中/EN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<!-- Logo和介绍 -->
|
||||
<div class="logo-container">
|
||||
<div class="logo-frame">
|
||||
<img src="img/logo.png" alt="VoidRaft Logo" class="logo-image">
|
||||
</div>
|
||||
<h2 class="logo-text" data-en="VoidRaft" data-zh="VoidRaft">VoidRaft</h2>
|
||||
<p class="tagline" data-en="An elegant text snippet recording tool" data-zh="优雅的文本片段记录工具">An elegant text snippet recording tool</p>
|
||||
</div>
|
||||
|
||||
<div class="intro-box">
|
||||
<p class="intro-text" data-en="Designed for developers to record, organize, and manage various text snippets anytime, anywhere." data-zh="专为开发者打造,随时随地记录、整理和管理各种文本片段。">Designed for developers to record, organize, and manage various text snippets anytime, anywhere.</p>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<a href="https://github.com/landaiqing/voidraft/releases" class="btn" data-en="Download" data-zh="下载">
|
||||
<i class="fas fa-download"></i> Download
|
||||
</a>
|
||||
<a href="https://github.com/landaiqing/voidraft" class="btn btn-secondary" data-en="Source Code" data-zh="源代码">
|
||||
<i class="fab fa-github"></i> Source Code
|
||||
</a>
|
||||
<a href="changelog.html" class="btn btn-secondary" data-en="Changelog" data-zh="更新日志">
|
||||
<i class="fas fa-history"></i> Changelog
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 特性部分 -->
|
||||
<h2 data-en="Core Features" data-zh="核心特性">Core Features</h2>
|
||||
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Developer-Friendly" data-zh="开发者友好">Developer-Friendly</h3>
|
||||
<p class="feature-desc" data-en="Multi-language code blocks with syntax highlighting for 30+ programming languages" data-zh="多语言代码块支持,为30+种编程语言提供语法高亮">Multi-language code blocks with syntax highlighting for 30+ programming languages</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-magic"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Code Formatting" data-zh="代码格式化">Code Formatting</h3>
|
||||
<p class="feature-desc" data-en="Built-in Prettier support for one-click code beautification" data-zh="内置Prettier支持,一键美化代码">Built-in Prettier support for one-click code beautification</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-palette"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Custom Themes" data-zh="自定义主题">Custom Themes</h3>
|
||||
<p class="feature-desc" data-en="Dark/Light themes with full customization options" data-zh="深色/浅色主题,支持完全自定义">Dark/Light themes with full customization options</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-clone"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Multi-Window" data-zh="多窗口支持">Multi-Window</h3>
|
||||
<p class="feature-desc" data-en="Edit multiple documents simultaneously" data-zh="同时编辑多个文档">Edit multiple documents simultaneously</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Block Editing" data-zh="块状编辑">Block Editing</h3>
|
||||
<p class="feature-desc" data-en="Split content into independent code blocks with different language settings" data-zh="将内容分割为独立的代码块,每个块可设置不同语言">Split content into independent code blocks with different language settings</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-puzzle-piece"></i>
|
||||
</div>
|
||||
<h3 class="feature-title" data-en="Extensions" data-zh="丰富扩展">Extensions</h3>
|
||||
<p class="feature-desc" data-en="Rainbow brackets, VSCode-style search, color picker, translation tool, and more" data-zh="彩虹括号、VSCode风格搜索、颜色选择器、翻译工具等多种扩展">Rainbow brackets, VSCode-style search, color picker, translation tool, and more</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览部分 -->
|
||||
<h2 data-en="Preview" data-zh="预览">Preview</h2>
|
||||
|
||||
<div class="preview-container">
|
||||
<div class="preview-window">
|
||||
<div class="preview-header">
|
||||
<div class="preview-controls">
|
||||
<span class="preview-btn"></span>
|
||||
<span class="preview-btn"></span>
|
||||
<span class="preview-btn"></span>
|
||||
</div>
|
||||
<div class="preview-title">voidraft</div>
|
||||
</div>
|
||||
<div class="preview-content">
|
||||
<div class="code-block-wrapper">
|
||||
<div class="block-header">
|
||||
<div class="block-language">javascript</div>
|
||||
</div>
|
||||
<pre class="code-block">
|
||||
<span class="keyword">function</span> <span class="function">createDocument</span>() {
|
||||
<span class="keyword">const</span> <span class="variable">doc</span> = <span class="keyword">new</span> <span class="class">Document</span>();
|
||||
|
||||
<span class="variable">doc</span>.<span class="function">addCodeBlock</span>(<span class="string">'javascript'</span>, <span class="string">`
|
||||
<span class="keyword">function</span> <span class="function">greeting</span>(<span class="parameter">name</span>) {
|
||||
<span class="keyword">return</span> <span class="string">`Hello, </span>${<span class="parameter">name</span>}<span class="string">!`</span>;
|
||||
}
|
||||
|
||||
<span class="built-in">console</span>.<span class="function">log</span>(<span class="function">greeting</span>(<span class="string">'World'</span>));
|
||||
`</span>);
|
||||
|
||||
<span class="keyword">return</span> <span class="variable">doc</span>;
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="code-block-wrapper" style="margin-top: 10px;">
|
||||
<div class="block-header">
|
||||
<div class="block-language">text</div>
|
||||
</div>
|
||||
<pre class="code-block">
|
||||
<span class="comment">// VoidRaft - An elegant text snippet recording tool</span>
|
||||
<span class="comment">// Multi-language support | Code formatting | Custom themes</span>
|
||||
<span class="comment">// A modern text editor designed for developers</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-window">
|
||||
<img src="img/screenshot-dark.png" alt="VoidRaft 界面预览" class="preview-image dark-theme-img">
|
||||
<img src="img/screenshot-light.png" alt="VoidRaft 界面预览" class="preview-image light-theme-img" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技术栈部分 -->
|
||||
<h2 data-en="Technical Stack" data-zh="技术栈">Technical Stack</h2>
|
||||
|
||||
<ul class="tech-list">
|
||||
<li class="tech-item">
|
||||
<div class="tech-icon"><i class="fas fa-desktop"></i></div>
|
||||
<span class="tech-name">Wails3</span>
|
||||
<span class="tech-desc" data-en="Cross-platform desktop application framework" data-zh="跨平台桌面应用框架">Cross-platform desktop application framework</span>
|
||||
</li>
|
||||
<li class="tech-item">
|
||||
<div class="tech-icon"><i class="fas fa-cogs"></i></div>
|
||||
<span class="tech-name">Go 1.21+</span>
|
||||
<span class="tech-desc" data-en="Fast and efficient backend language" data-zh="快速高效的后端语言">Fast and efficient backend language</span>
|
||||
</li>
|
||||
<li class="tech-item">
|
||||
<div class="tech-icon"><i class="fab fa-vuejs"></i></div>
|
||||
<span class="tech-name">Vue 3 + TypeScript</span>
|
||||
<span class="tech-desc" data-en="Modern frontend framework" data-zh="现代化前端框架">Modern frontend framework</span>
|
||||
</li>
|
||||
<li class="tech-item">
|
||||
<div class="tech-icon"><i class="fas fa-edit"></i></div>
|
||||
<span class="tech-name">CodeMirror 6</span>
|
||||
<span class="tech-desc" data-en="Modern code editor with extension support" data-zh="支持扩展的现代化代码编辑器">Modern code editor with extension support</span>
|
||||
</li>
|
||||
<li class="tech-item">
|
||||
<div class="tech-icon"><i class="fas fa-database"></i></div>
|
||||
<span class="tech-name">SQLite</span>
|
||||
<span class="tech-desc" data-en="Lightweight database for document storage" data-zh="轻量级文档存储数据库">Lightweight database for document storage</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
<p class="footer-text" data-en="© 2025 VoidRaft - An elegant text snippet recording tool designed for developers" data-zh="© 2025 VoidRaft - 专为开发者打造的优雅文本片段记录工具">© 2025 VoidRaft - An elegant text snippet recording tool designed for developers</p>
|
||||
<div class="footer-links">
|
||||
<a href="https://github.com/landaiqing/voidraft" target="_blank" class="footer-link">GitHub</a>
|
||||
<a href="https://github.com/landaiqing/voidraft/issues" target="_blank" class="footer-link" data-en="Issues" data-zh="问题反馈">Issues</a>
|
||||
<a href="https://github.com/landaiqing/voidraft/releases" target="_blank" class="footer-link" data-en="Releases" data-zh="版本发布">Releases</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,705 @@
|
||||
/**
|
||||
* VoidRaft - Changelog Script
|
||||
* 从GitHub API获取发布信息,支持Gitea备用源
|
||||
*/
|
||||
|
||||
/**
|
||||
* 仓库配置类
|
||||
*/
|
||||
class RepositoryConfig {
|
||||
constructor() {
|
||||
this.repos = {
|
||||
github: {
|
||||
owner: 'landaiqing',
|
||||
name: 'voidraft',
|
||||
apiUrl: 'https://api.github.com/repos/landaiqing/voidraft/releases',
|
||||
releasesUrl: 'https://github.com/landaiqing/voidraft/releases'
|
||||
},
|
||||
gitea: {
|
||||
owner: 'landaiqing',
|
||||
name: 'voidraft',
|
||||
domain: 'git.landaiqing.cn',
|
||||
apiUrl: 'https://git.landaiqing.cn/api/v1/repos/landaiqing/voidraft/releases',
|
||||
releasesUrl: 'https://git.landaiqing.cn/landaiqing/voidraft/releases'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取仓库配置
|
||||
* @param {string} source - 'github' 或 'gitea'
|
||||
*/
|
||||
getRepo(source) {
|
||||
return this.repos[source];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有仓库配置
|
||||
*/
|
||||
getAllRepos() {
|
||||
return this.repos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化消息管理类
|
||||
*/
|
||||
class I18nMessages {
|
||||
constructor() {
|
||||
this.messages = {
|
||||
loading: {
|
||||
en: 'Loading releases...',
|
||||
zh: '正在加载版本信息...'
|
||||
},
|
||||
noReleases: {
|
||||
en: 'No release information found',
|
||||
zh: '没有找到版本发布信息'
|
||||
},
|
||||
fetchError: {
|
||||
en: 'Failed to load release information. Please try again later.',
|
||||
zh: '无法获取版本信息,请稍后再试'
|
||||
},
|
||||
githubApiError: {
|
||||
en: 'GitHub API returned an error status: ',
|
||||
zh: 'GitHub API返回错误状态: '
|
||||
},
|
||||
giteaApiError: {
|
||||
en: 'Gitea API returned an error status: ',
|
||||
zh: 'Gitea API返回错误状态: '
|
||||
},
|
||||
dataSource: {
|
||||
en: 'Data source: ',
|
||||
zh: '数据来源: '
|
||||
},
|
||||
downloads: {
|
||||
en: 'Downloads',
|
||||
zh: '下载资源'
|
||||
},
|
||||
download: {
|
||||
en: 'Download',
|
||||
zh: '下载'
|
||||
},
|
||||
preRelease: {
|
||||
en: 'Pre-release',
|
||||
zh: '预发布'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息
|
||||
* @param {string} key - 消息键
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
getMessage(key, lang = 'en') {
|
||||
return this.messages[key] && this.messages[key][lang] || this.messages[key]['en'] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言
|
||||
*/
|
||||
getCurrentLang() {
|
||||
return window.currentLang || 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API客户端类
|
||||
*/
|
||||
class APIClient {
|
||||
constructor(repositoryConfig, i18nMessages) {
|
||||
this.repositoryConfig = repositoryConfig;
|
||||
this.i18nMessages = i18nMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定源获取发布信息
|
||||
* @param {string} source - 'github' 或 'gitea'
|
||||
*/
|
||||
async fetchReleases(source) {
|
||||
const repo = this.repositoryConfig.getRepo(source);
|
||||
const errorMessageKey = source === 'github' ? 'githubApiError' : 'giteaApiError';
|
||||
|
||||
const options = {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
};
|
||||
|
||||
if (source === 'github') {
|
||||
return this.fetchFromGitHub(repo, options, errorMessageKey);
|
||||
} else {
|
||||
return this.fetchFromGitea(repo, options, errorMessageKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从GitHub获取数据
|
||||
* @param {Object} repo - 仓库配置
|
||||
* @param {Object} options - 请求选项
|
||||
* @param {string} errorMessageKey - 错误消息键
|
||||
*/
|
||||
async fetchFromGitHub(repo, options, errorMessageKey) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
options.signal = controller.signal;
|
||||
options.headers['Accept'] = 'application/vnd.github.v3+json';
|
||||
|
||||
try {
|
||||
const response = await fetch(repo.apiUrl, options);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`);
|
||||
}
|
||||
|
||||
const releases = await response.json();
|
||||
|
||||
if (!releases || releases.length === 0) {
|
||||
throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang()));
|
||||
}
|
||||
|
||||
return releases;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Gitea获取数据
|
||||
* @param {Object} repo - 仓库配置
|
||||
* @param {Object} options - 请求选项
|
||||
* @param {string} errorMessageKey - 错误消息键
|
||||
*/
|
||||
async fetchFromGitea(repo, options, errorMessageKey) {
|
||||
const response = await fetch(repo.apiUrl, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${this.i18nMessages.getMessage(errorMessageKey, this.i18nMessages.getCurrentLang())}${response.status}`);
|
||||
}
|
||||
|
||||
const releases = await response.json();
|
||||
|
||||
if (!releases || releases.length === 0) {
|
||||
throw new Error(this.i18nMessages.getMessage('noReleases', this.i18nMessages.getCurrentLang()));
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI管理类
|
||||
*/
|
||||
class UIManager {
|
||||
constructor(i18nMessages) {
|
||||
this.i18nMessages = i18nMessages;
|
||||
this.elements = {
|
||||
loading: document.getElementById('loading'),
|
||||
changelog: document.getElementById('changelog'),
|
||||
error: document.getElementById('error-message')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载状态
|
||||
*/
|
||||
showLoading() {
|
||||
this.elements.loading.style.display = 'block';
|
||||
this.elements.error.style.display = 'none';
|
||||
this.elements.changelog.innerHTML = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载状态
|
||||
*/
|
||||
hideLoading() {
|
||||
this.elements.loading.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误消息
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
showError(message) {
|
||||
const errorMessageElement = this.elements.error.querySelector('p');
|
||||
if (errorMessageElement) {
|
||||
errorMessageElement.textContent = message;
|
||||
} else {
|
||||
this.elements.error.textContent = message;
|
||||
}
|
||||
this.elements.error.style.display = 'block';
|
||||
this.hideLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示发布信息
|
||||
* @param {Array} releases - 发布信息数组
|
||||
* @param {string} source - 数据源
|
||||
*/
|
||||
displayReleases(releases, source) {
|
||||
this.hideLoading();
|
||||
|
||||
// 清除现有内容
|
||||
this.elements.changelog.innerHTML = '';
|
||||
|
||||
// 创建数据源元素
|
||||
const sourceElement = this.createSourceElement(source);
|
||||
this.elements.changelog.appendChild(sourceElement);
|
||||
|
||||
// 创建发布信息元素
|
||||
releases.forEach(release => {
|
||||
const releaseElement = this.createReleaseElement(release, source);
|
||||
this.elements.changelog.appendChild(releaseElement);
|
||||
});
|
||||
|
||||
this.elements.changelog.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据源元素
|
||||
* @param {string} source - 数据源
|
||||
*/
|
||||
createSourceElement(source) {
|
||||
const sourceElement = document.createElement('div');
|
||||
sourceElement.className = 'data-source';
|
||||
|
||||
// 创建带有国际化支持的源标签
|
||||
const sourceLabel = document.createElement('span');
|
||||
sourceLabel.setAttribute('data-en', this.i18nMessages.getMessage('dataSource', 'en'));
|
||||
sourceLabel.setAttribute('data-zh', this.i18nMessages.getMessage('dataSource', 'zh'));
|
||||
sourceLabel.textContent = this.i18nMessages.getMessage('dataSource', this.i18nMessages.getCurrentLang());
|
||||
|
||||
// 创建链接
|
||||
const sourceLink = document.createElement('a');
|
||||
const repositoryConfig = new RepositoryConfig();
|
||||
sourceLink.href = repositoryConfig.getRepo(source).releasesUrl;
|
||||
sourceLink.textContent = source === 'github' ? 'GitHub' : 'Gitea';
|
||||
sourceLink.target = '_blank';
|
||||
|
||||
// 组装元素
|
||||
sourceElement.appendChild(sourceLabel);
|
||||
sourceElement.appendChild(sourceLink);
|
||||
|
||||
return sourceElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建发布信息元素
|
||||
* @param {Object} release - 发布信息对象
|
||||
* @param {string} source - 数据源
|
||||
*/
|
||||
createReleaseElement(release, source) {
|
||||
const releaseElement = document.createElement('div');
|
||||
releaseElement.className = 'release';
|
||||
|
||||
// 格式化发布日期
|
||||
const releaseDate = new Date(release.published_at || release.created_at);
|
||||
const formattedDate = DateFormatter.formatDate(releaseDate);
|
||||
|
||||
// 创建头部
|
||||
const headerElement = this.createReleaseHeader(release, formattedDate);
|
||||
releaseElement.appendChild(headerElement);
|
||||
|
||||
// 添加发布说明
|
||||
if (release.body) {
|
||||
const descriptionElement = document.createElement('div');
|
||||
descriptionElement.className = 'release-description markdown-content';
|
||||
descriptionElement.innerHTML = MarkdownParser.parseMarkdown(release.body);
|
||||
releaseElement.appendChild(descriptionElement);
|
||||
}
|
||||
|
||||
// 添加下载资源
|
||||
const assets = AssetManager.getAssetsFromRelease(release, source);
|
||||
if (assets && assets.length > 0) {
|
||||
const assetsElement = this.createAssetsElement(assets);
|
||||
releaseElement.appendChild(assetsElement);
|
||||
}
|
||||
|
||||
return releaseElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建发布信息头部
|
||||
*/
|
||||
createReleaseHeader(release, formattedDate) {
|
||||
const headerElement = document.createElement('div');
|
||||
headerElement.className = 'release-header';
|
||||
|
||||
// 版本元素
|
||||
const versionElement = document.createElement('div');
|
||||
versionElement.className = 'release-version';
|
||||
|
||||
// 版本文本
|
||||
const versionText = document.createElement('span');
|
||||
versionText.textContent = release.name || release.tag_name;
|
||||
versionElement.appendChild(versionText);
|
||||
|
||||
// 预发布标记
|
||||
if (release.prerelease) {
|
||||
const preReleaseTag = document.createElement('span');
|
||||
preReleaseTag.className = 'release-badge pre-release';
|
||||
preReleaseTag.setAttribute('data-en', this.i18nMessages.getMessage('preRelease', 'en'));
|
||||
preReleaseTag.setAttribute('data-zh', this.i18nMessages.getMessage('preRelease', 'zh'));
|
||||
preReleaseTag.textContent = this.i18nMessages.getMessage('preRelease', this.i18nMessages.getCurrentLang());
|
||||
versionElement.appendChild(preReleaseTag);
|
||||
}
|
||||
|
||||
// 日期元素
|
||||
const dateElement = document.createElement('div');
|
||||
dateElement.className = 'release-date';
|
||||
dateElement.textContent = formattedDate;
|
||||
|
||||
headerElement.appendChild(versionElement);
|
||||
headerElement.appendChild(dateElement);
|
||||
|
||||
return headerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建资源文件元素
|
||||
* @param {Array} assets - 资源文件数组
|
||||
*/
|
||||
createAssetsElement(assets) {
|
||||
const assetsElement = document.createElement('div');
|
||||
assetsElement.className = 'release-assets';
|
||||
|
||||
// 资源标题
|
||||
const assetsTitle = document.createElement('div');
|
||||
assetsTitle.className = 'release-assets-title';
|
||||
assetsTitle.setAttribute('data-en', this.i18nMessages.getMessage('downloads', 'en'));
|
||||
assetsTitle.setAttribute('data-zh', this.i18nMessages.getMessage('downloads', 'zh'));
|
||||
assetsTitle.textContent = this.i18nMessages.getMessage('downloads', this.i18nMessages.getCurrentLang());
|
||||
|
||||
// 资源列表
|
||||
const assetList = document.createElement('ul');
|
||||
assetList.className = 'asset-list';
|
||||
|
||||
// 添加每个资源
|
||||
assets.forEach(asset => {
|
||||
const assetItem = this.createAssetItem(asset);
|
||||
assetList.appendChild(assetItem);
|
||||
});
|
||||
|
||||
assetsElement.appendChild(assetsTitle);
|
||||
assetsElement.appendChild(assetList);
|
||||
|
||||
return assetsElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建资源文件项
|
||||
* @param {Object} asset - 资源文件对象
|
||||
*/
|
||||
createAssetItem(asset) {
|
||||
const assetItem = document.createElement('li');
|
||||
assetItem.className = 'asset-item';
|
||||
|
||||
// 文件图标
|
||||
const iconElement = document.createElement('i');
|
||||
iconElement.className = `asset-icon fas fa-${FileIconHelper.getFileIcon(asset.name)}`;
|
||||
|
||||
// 文件名
|
||||
const nameElement = document.createElement('span');
|
||||
nameElement.className = 'asset-name';
|
||||
nameElement.textContent = asset.name;
|
||||
|
||||
// 文件大小
|
||||
const sizeElement = document.createElement('span');
|
||||
sizeElement.className = 'asset-size';
|
||||
sizeElement.textContent = FileSizeFormatter.formatFileSize(asset.size);
|
||||
|
||||
// 下载链接
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.className = 'download-btn';
|
||||
downloadLink.href = asset.browser_download_url;
|
||||
downloadLink.target = '_blank';
|
||||
downloadLink.setAttribute('data-en', this.i18nMessages.getMessage('download', 'en'));
|
||||
downloadLink.setAttribute('data-zh', this.i18nMessages.getMessage('download', 'zh'));
|
||||
downloadLink.textContent = this.i18nMessages.getMessage('download', this.i18nMessages.getCurrentLang());
|
||||
|
||||
// 组装资源项
|
||||
assetItem.appendChild(iconElement);
|
||||
assetItem.appendChild(nameElement);
|
||||
assetItem.appendChild(sizeElement);
|
||||
assetItem.appendChild(downloadLink);
|
||||
|
||||
return assetItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源管理器类
|
||||
*/
|
||||
class AssetManager {
|
||||
/**
|
||||
* 从发布信息中获取资源文件
|
||||
* @param {Object} release - 发布信息对象
|
||||
* @param {string} source - 数据源
|
||||
*/
|
||||
static getAssetsFromRelease(release, source) {
|
||||
let assets = [];
|
||||
|
||||
if (source === 'github') {
|
||||
assets = release.assets || [];
|
||||
} else { // Gitea
|
||||
assets = release.assets || [];
|
||||
// 检查Gitea特定的资源结构
|
||||
if (!assets.length && release.attachments) {
|
||||
assets = release.attachments.map(attachment => ({
|
||||
name: attachment.name,
|
||||
size: attachment.size,
|
||||
browser_download_url: attachment.browser_download_url
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件图标助手类
|
||||
*/
|
||||
class FileIconHelper {
|
||||
/**
|
||||
* 根据文件扩展名获取图标
|
||||
* @param {string} filename - 文件名
|
||||
*/
|
||||
static getFileIcon(filename) {
|
||||
const extension = filename.split('.').pop().toLowerCase();
|
||||
|
||||
const iconMap = {
|
||||
'exe': 'download',
|
||||
'msi': 'download',
|
||||
'dmg': 'download',
|
||||
'pkg': 'download',
|
||||
'deb': 'download',
|
||||
'rpm': 'download',
|
||||
'tar': 'file-archive',
|
||||
'gz': 'file-archive',
|
||||
'zip': 'file-archive',
|
||||
'7z': 'file-archive',
|
||||
'rar': 'file-archive',
|
||||
'pdf': 'file-pdf',
|
||||
'txt': 'file-alt',
|
||||
'md': 'file-alt',
|
||||
'json': 'file-code',
|
||||
'xml': 'file-code',
|
||||
'yml': 'file-code',
|
||||
'yaml': 'file-code'
|
||||
};
|
||||
|
||||
return iconMap[extension] || 'file';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件大小格式化器类
|
||||
*/
|
||||
class FileSizeFormatter {
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes - 字节数
|
||||
*/
|
||||
static formatFileSize(bytes) {
|
||||
if (!bytes) return '';
|
||||
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式化器类
|
||||
*/
|
||||
class DateFormatter {
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param {Date} date - 日期对象
|
||||
*/
|
||||
static formatDate(date) {
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
};
|
||||
|
||||
const lang = window.currentLang || 'en';
|
||||
const locale = lang === 'zh' ? 'zh-CN' : 'en-US';
|
||||
|
||||
return date.toLocaleDateString(locale, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown解析器类
|
||||
*/
|
||||
class MarkdownParser {
|
||||
/**
|
||||
* 简单的Markdown解析
|
||||
* @param {string} markdown - Markdown文本
|
||||
*/
|
||||
static parseMarkdown(markdown) {
|
||||
if (!markdown) return '';
|
||||
|
||||
// 预处理:保留原始换行符,用特殊标记替换
|
||||
const preservedLineBreaks = '___LINE_BREAK___';
|
||||
markdown = markdown.replace(/\n/g, preservedLineBreaks);
|
||||
|
||||
// 引用块 - > text
|
||||
markdown = markdown.replace(/>\s*(.*?)(?=>|$)/g, '<blockquote>$1</blockquote>');
|
||||
markdown = markdown.replace(/>\s*(.*?)(?=>|$)/g, '<blockquote>$1</blockquote>');
|
||||
|
||||
// 链接 - [text](url)
|
||||
markdown = markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
|
||||
// 标题 - # Heading
|
||||
markdown = markdown.replace(/^### (.*?)(?=___LINE_BREAK___|$)/gm, '<h3>$1</h3>');
|
||||
markdown = markdown.replace(/^## (.*?)(?=___LINE_BREAK___|$)/gm, '<h2>$1</h2>');
|
||||
markdown = markdown.replace(/^# (.*?)(?=___LINE_BREAK___|$)/gm, '<h1>$1</h1>');
|
||||
|
||||
// 粗体 - **text**
|
||||
markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// 斜体 - *text*
|
||||
markdown = markdown.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||
|
||||
// 代码块 - ```code```
|
||||
markdown = markdown.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
// 行内代码 - `code`
|
||||
markdown = markdown.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
// 处理列表项
|
||||
// 先将每个列表项转换为HTML
|
||||
markdown = markdown.replace(/- (.*?)(?=___LINE_BREAK___- |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
||||
markdown = markdown.replace(/\* (.*?)(?=___LINE_BREAK___\* |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
||||
markdown = markdown.replace(/\d+\. (.*?)(?=___LINE_BREAK___\d+\. |___LINE_BREAK___$|$)/g, '<li>$1</li>');
|
||||
|
||||
// 然后将连续的列表项包装在ul或ol中
|
||||
const listItemRegex = /<li>.*?<\/li>/g;
|
||||
const listItems = markdown.match(listItemRegex) || [];
|
||||
|
||||
if (listItems.length > 0) {
|
||||
// 将连续的列表项组合在一起
|
||||
let lastIndex = 0;
|
||||
let result = '';
|
||||
let inList = false;
|
||||
|
||||
listItems.forEach(item => {
|
||||
const itemIndex = markdown.indexOf(item, lastIndex);
|
||||
|
||||
// 添加列表项之前的内容
|
||||
if (itemIndex > lastIndex) {
|
||||
result += markdown.substring(lastIndex, itemIndex);
|
||||
}
|
||||
|
||||
// 如果不在列表中,开始一个新列表
|
||||
if (!inList) {
|
||||
result += '<ul>';
|
||||
inList = true;
|
||||
}
|
||||
|
||||
// 添加列表项
|
||||
result += item;
|
||||
|
||||
// 更新lastIndex
|
||||
lastIndex = itemIndex + item.length;
|
||||
|
||||
// 检查下一个内容是否是列表项
|
||||
const nextItemIndex = markdown.indexOf('<li>', lastIndex);
|
||||
if (nextItemIndex === -1 || nextItemIndex > lastIndex + 20) { // 如果下一个列表项不紧邻
|
||||
result += '</ul>';
|
||||
inList = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 添加剩余内容
|
||||
if (lastIndex < markdown.length) {
|
||||
result += markdown.substring(lastIndex);
|
||||
}
|
||||
|
||||
markdown = result;
|
||||
}
|
||||
|
||||
// 处理水平分隔线
|
||||
markdown = markdown.replace(/---/g, '<hr>');
|
||||
|
||||
// 恢复换行符
|
||||
markdown = markdown.replace(/___LINE_BREAK___/g, '<br>');
|
||||
|
||||
// 处理段落
|
||||
markdown = markdown.replace(/<br><br>/g, '</p><p>');
|
||||
|
||||
// 包装在段落标签中
|
||||
if (!markdown.startsWith('<p>')) {
|
||||
markdown = `<p>${markdown}</p>`;
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新日志主应用类
|
||||
*/
|
||||
class ChangelogApp {
|
||||
constructor() {
|
||||
this.repositoryConfig = new RepositoryConfig();
|
||||
this.i18nMessages = new I18nMessages();
|
||||
this.apiClient = new APIClient(this.repositoryConfig, this.i18nMessages);
|
||||
this.uiManager = new UIManager(this.i18nMessages);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
init() {
|
||||
this.uiManager.showLoading();
|
||||
|
||||
// 首先尝试GitHub API
|
||||
this.apiClient.fetchReleases('github')
|
||||
.then(releases => {
|
||||
this.uiManager.displayReleases(releases, 'github');
|
||||
})
|
||||
.catch(() => {
|
||||
// GitHub失败时尝试Gitea
|
||||
return this.apiClient.fetchReleases('gitea')
|
||||
.then(releases => {
|
||||
this.uiManager.displayReleases(releases, 'gitea');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取发布信息失败:', error);
|
||||
this.uiManager.showError(this.i18nMessages.getMessage('fetchError', this.i18nMessages.getCurrentLang()));
|
||||
});
|
||||
|
||||
// 监听语言变化事件
|
||||
document.addEventListener('languageChanged', () => this.updateUI());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新UI元素(当语言变化时)
|
||||
*/
|
||||
updateUI() {
|
||||
const elementsToUpdate = document.querySelectorAll('[data-en][data-zh]');
|
||||
const currentLang = this.i18nMessages.getCurrentLang();
|
||||
|
||||
elementsToUpdate.forEach(element => {
|
||||
const text = element.getAttribute(`data-${currentLang}`);
|
||||
if (text) {
|
||||
element.textContent = text;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 当DOM加载完成时初始化应用
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ChangelogApp();
|
||||
});
|
||||
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* VoidRaft - Website Script
|
||||
*/
|
||||
|
||||
/**
|
||||
* 主题管理类
|
||||
*/
|
||||
class ThemeManager {
|
||||
constructor() {
|
||||
this.themeToggle = document.getElementById('theme-toggle');
|
||||
this.currentTheme = this.getInitialTheme();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始主题
|
||||
*/
|
||||
getInitialTheme() {
|
||||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
return savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主题管理器
|
||||
*/
|
||||
init() {
|
||||
if (!this.themeToggle) return;
|
||||
|
||||
this.setTheme(this.currentTheme);
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
*/
|
||||
bindEvents() {
|
||||
this.themeToggle.addEventListener('click', () => {
|
||||
this.toggleTheme();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换主题
|
||||
*/
|
||||
toggleTheme() {
|
||||
document.body.classList.add('theme-transition');
|
||||
|
||||
const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
|
||||
this.setTheme(newTheme);
|
||||
this.saveTheme(newTheme);
|
||||
|
||||
setTimeout(() => document.body.classList.remove('theme-transition'), 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
* @param {string} theme - 'dark' 或 'light'
|
||||
*/
|
||||
setTheme(theme) {
|
||||
this.currentTheme = theme;
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
document.body.classList.toggle('theme-dark', isDark);
|
||||
document.body.classList.toggle('theme-light', !isDark);
|
||||
|
||||
this.updateToggleIcon(isDark);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新切换按钮图标
|
||||
* @param {boolean} isDark - 是否为暗色主题
|
||||
*/
|
||||
updateToggleIcon(isDark) {
|
||||
if (this.themeToggle) {
|
||||
const icon = this.themeToggle.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存主题到本地存储
|
||||
* @param {string} theme - 主题名称
|
||||
*/
|
||||
saveTheme(theme) {
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 语言管理类
|
||||
*/
|
||||
class LanguageManager {
|
||||
constructor() {
|
||||
this.langToggle = document.getElementById('lang-toggle');
|
||||
this.currentLang = this.getInitialLanguage();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始语言
|
||||
*/
|
||||
getInitialLanguage() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlLang = urlParams.get('lang');
|
||||
const savedLang = localStorage.getItem('lang');
|
||||
const browserLang = navigator.language.startsWith('zh') ? 'zh' : 'en';
|
||||
return urlLang || savedLang || browserLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化语言管理器
|
||||
*/
|
||||
init() {
|
||||
if (!this.langToggle) return;
|
||||
|
||||
window.currentLang = this.currentLang;
|
||||
this.setLanguage(this.currentLang);
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
*/
|
||||
bindEvents() {
|
||||
this.langToggle.addEventListener('click', () => {
|
||||
this.toggleLanguage();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换语言
|
||||
*/
|
||||
toggleLanguage() {
|
||||
document.body.classList.add('lang-transition');
|
||||
|
||||
const newLang = this.currentLang === 'zh' ? 'en' : 'zh';
|
||||
this.setLanguage(newLang);
|
||||
this.saveLanguage(newLang);
|
||||
this.updateURL(newLang);
|
||||
this.notifyLanguageChange(newLang);
|
||||
|
||||
setTimeout(() => document.body.classList.remove('lang-transition'), 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置页面语言
|
||||
* @param {string} lang - 'zh' 或 'en'
|
||||
*/
|
||||
setLanguage(lang) {
|
||||
this.currentLang = lang;
|
||||
window.currentLang = lang;
|
||||
|
||||
this.updatePageElements(lang);
|
||||
this.updateHTMLLang(lang);
|
||||
this.updateToggleButton(lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新页面元素文本
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
updatePageElements(lang) {
|
||||
document.querySelectorAll('[data-zh][data-en]').forEach(el => {
|
||||
el.textContent = el.getAttribute(`data-${lang}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新HTML语言属性
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
updateHTMLLang(lang) {
|
||||
document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新切换按钮文本
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
updateToggleButton(lang) {
|
||||
if (this.langToggle) {
|
||||
const text = lang === 'zh' ? 'EN/中' : '中/EN';
|
||||
this.langToggle.innerHTML = `<i class="fas fa-language"></i> ${text}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存语言到本地存储
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
saveLanguage(lang) {
|
||||
localStorage.setItem('lang', lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新URL参数
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
updateURL(lang) {
|
||||
const newUrl = new URL(window.location);
|
||||
if (lang === 'zh') {
|
||||
newUrl.searchParams.set('lang', 'zh');
|
||||
} else {
|
||||
newUrl.searchParams.delete('lang');
|
||||
}
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知语言变更
|
||||
* @param {string} lang - 语言代码
|
||||
*/
|
||||
notifyLanguageChange(lang) {
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang } }));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言
|
||||
*/
|
||||
getCurrentLanguage() {
|
||||
return this.currentLang;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SEO管理类
|
||||
*/
|
||||
class SEOManager {
|
||||
constructor(languageManager) {
|
||||
this.languageManager = languageManager;
|
||||
this.metaTexts = {
|
||||
en: {
|
||||
description: 'VoidRaft is an elegant text snippet recording tool designed for developers. Features multi-language code blocks, syntax highlighting, code formatting, custom themes, and more.',
|
||||
title: 'VoidRaft - An elegant text snippet recording tool designed for developers.',
|
||||
ogTitle: 'VoidRaft - An elegant text snippet recording tool designed for developers'
|
||||
},
|
||||
zh: {
|
||||
description: 'VoidRaft 是专为开发者打造的优雅文本片段记录工具。支持多语言代码块、语法高亮、代码格式化、自定义主题等功能。',
|
||||
title: 'VoidRaft - 专为开发者打造的优雅文本片段记录工具',
|
||||
ogTitle: 'VoidRaft - 专为开发者打造的优雅文本片段记录工具'
|
||||
}
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化SEO管理器
|
||||
*/
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.updateMetaTags(this.languageManager.getCurrentLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
*/
|
||||
bindEvents() {
|
||||
window.addEventListener('languageChanged', (event) => {
|
||||
this.updateMetaTags(event.detail.lang);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新SEO元标签
|
||||
* @param {string} lang - 当前语言
|
||||
*/
|
||||
updateMetaTags(lang) {
|
||||
const texts = this.metaTexts[lang];
|
||||
|
||||
this.updateMetaDescription(texts.description);
|
||||
this.updateOpenGraphTags(texts.ogTitle, texts.description);
|
||||
this.updateTwitterCardTags(texts.ogTitle, texts.description);
|
||||
this.updatePageTitle(texts.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新meta描述
|
||||
* @param {string} description - 描述文本
|
||||
*/
|
||||
updateMetaDescription(description) {
|
||||
const metaDesc = document.querySelector('meta[name="description"]');
|
||||
if (metaDesc) {
|
||||
metaDesc.content = description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Open Graph标签
|
||||
* @param {string} title - 标题
|
||||
* @param {string} description - 描述
|
||||
*/
|
||||
updateOpenGraphTags(title, description) {
|
||||
const ogTitle = document.querySelector('meta[property="og:title"]');
|
||||
const ogDesc = document.querySelector('meta[property="og:description"]');
|
||||
|
||||
if (ogTitle) ogTitle.content = title;
|
||||
if (ogDesc) ogDesc.content = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Twitter Card标签
|
||||
* @param {string} title - 标题
|
||||
* @param {string} description - 描述
|
||||
*/
|
||||
updateTwitterCardTags(title, description) {
|
||||
const twitterTitle = document.querySelector('meta[property="twitter:title"]');
|
||||
const twitterDesc = document.querySelector('meta[property="twitter:description"]');
|
||||
|
||||
if (twitterTitle) twitterTitle.content = title;
|
||||
if (twitterDesc) twitterDesc.content = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新页面标题
|
||||
* @param {string} title - 标题
|
||||
*/
|
||||
updatePageTitle(title) {
|
||||
document.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI效果管理类
|
||||
*/
|
||||
class UIEffects {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化UI效果
|
||||
*/
|
||||
init() {
|
||||
this.initCardEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化卡片悬停效果
|
||||
*/
|
||||
initCardEffects() {
|
||||
const cards = document.querySelectorAll('.feature-card');
|
||||
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mouseenter', () => {
|
||||
this.animateCardHover(card, true);
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
this.animateCardHover(card, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片悬停动画
|
||||
* @param {Element} card - 卡片元素
|
||||
* @param {boolean} isHover - 是否悬停
|
||||
*/
|
||||
animateCardHover(card, isHover) {
|
||||
if (isHover) {
|
||||
card.style.transform = 'translateY(-8px)';
|
||||
card.style.boxShadow = '7px 7px 0 var(--shadow-color)';
|
||||
} else {
|
||||
card.style.transform = 'translateY(0)';
|
||||
card.style.boxShadow = '5px 5px 0 var(--shadow-color)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VoidRaft主应用类
|
||||
*/
|
||||
class VoidRaftApp {
|
||||
constructor() {
|
||||
this.themeManager = null;
|
||||
this.languageManager = null;
|
||||
this.seoManager = null;
|
||||
this.uiEffects = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
init() {
|
||||
this.initializeManagers();
|
||||
this.showConsoleBranding();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化各个管理器
|
||||
*/
|
||||
initializeManagers() {
|
||||
this.themeManager = new ThemeManager();
|
||||
this.languageManager = new LanguageManager();
|
||||
this.seoManager = new SEOManager(this.languageManager);
|
||||
this.uiEffects = new UIEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示控制台品牌信息
|
||||
*/
|
||||
showConsoleBranding() {
|
||||
console.log('%c VoidRaft', 'color: #ff006e; font-size: 20px; font-family: "Space Mono", monospace;');
|
||||
console.log('%c An elegant text snippet recording tool designed for developers.', 'color: #073B4C; font-family: "Space Mono", monospace;');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题管理器
|
||||
*/
|
||||
getThemeManager() {
|
||||
return this.themeManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言管理器
|
||||
*/
|
||||
getLanguageManager() {
|
||||
return this.languageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SEO管理器
|
||||
*/
|
||||
getSEOManager() {
|
||||
return this.seoManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UI效果管理器
|
||||
*/
|
||||
getUIEffects() {
|
||||
return this.uiEffects;
|
||||
}
|
||||
}
|
||||
|
||||
// 当DOM加载完成时初始化应用
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.voidRaftApp = new VoidRaftApp();
|
||||
});
|
||||
@@ -213,8 +213,29 @@ const handleDelete = async (doc: Document, event: Event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (deleteConfirmId.value === doc.id) {
|
||||
// 确认删除
|
||||
// 确认删除前检查文档是否在其他窗口打开
|
||||
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.updateDocuments();
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ require (
|
||||
github.com/knadh/koanf/parsers/json v1.0.0
|
||||
github.com/knadh/koanf/providers/file v1.2.0
|
||||
github.com/knadh/koanf/providers/structs v1.0.0
|
||||
github.com/knadh/koanf/v2 v2.2.1
|
||||
github.com/knadh/koanf/v2 v2.2.2
|
||||
github.com/robertkrimen/otto v0.5.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.26.0
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.11
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/sys v0.34.0
|
||||
golang.org/x/text v0.27.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -71,8 +71,8 @@ require (
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/go-gitlab v0.115.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
||||
@@ -99,8 +99,8 @@ github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmY
|
||||
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4=
|
||||
github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w=
|
||||
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
||||
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||
github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
|
||||
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -164,8 +164,8 @@ github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2
|
||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10 h1:SrxwhkBcdtaSxQ/zujJuifJN5q8hxyba5UKv5oaM/X4=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.10/go.mod h1:4LCCW7s9e4PuSmu7l9OTvfWIGMO8TaSiftSeR5NpBIc=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.11 h1:MYZk2ci8fBd3loWanLzAYgAFcmq4qTRFyNggVqHMaHY=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.11/go.mod h1:4LCCW7s9e4PuSmu7l9OTvfWIGMO8TaSiftSeR5NpBIc=
|
||||
github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8=
|
||||
github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -174,24 +174,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -203,21 +203,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
Reference in new issue
Block a user