mirror of
https://github.com/timeshiftsauce/CeruMusic.git
synced 2025-11-25 11:29:42 +08:00
576 lines
18 KiB
JavaScript
576 lines
18 KiB
JavaScript
// Smooth scrolling functions
|
|
function scrollToDownload() {
|
|
document.getElementById('download').scrollIntoView({
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
|
|
function scrollToFeatures() {
|
|
document.getElementById('features').scrollIntoView({
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
|
|
// GitHub repository configuration
|
|
const GITHUB_REPO = 'timeshiftsauce/CeruMusic';
|
|
const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
|
|
// Cache for release data
|
|
let releaseData = null;
|
|
let releaseDataTimestamp = null;
|
|
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
|
|
// Download functionality
|
|
async function downloadApp(platform) {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
// Show loading state
|
|
button.innerHTML = `
|
|
<svg class="btn-icon spinning" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M21 12a9 9 0 11-6.219-8.56"/>
|
|
</svg>
|
|
获取下载链接...
|
|
`;
|
|
button.disabled = true;
|
|
|
|
try {
|
|
// Get latest release data
|
|
const release = await getLatestRelease();
|
|
|
|
if (!release) {
|
|
throw new Error('无法获取最新版本信息');
|
|
}
|
|
|
|
// Find the appropriate download asset
|
|
const downloadUrl = findDownloadAsset(release.assets, platform);
|
|
|
|
if (!downloadUrl) {
|
|
throw new Error(`暂无 ${getPlatformName(platform)} 版本下载`);
|
|
}
|
|
|
|
// Show success notification
|
|
showNotification(`正在下载 ${getPlatformName(platform)} 版本 v${release.tag_name}...`, 'success');
|
|
|
|
// Start download
|
|
window.open('https://gh.bugdey.us.kg/'+downloadUrl, '_blank');
|
|
|
|
// Track download
|
|
trackDownload(platform, release.tag_name);
|
|
|
|
} catch (error) {
|
|
console.error('Download error:', error);
|
|
showNotification(`下载失败: ${error.message}`, 'error');
|
|
|
|
// Fallback to GitHub releases page
|
|
setTimeout(() => {
|
|
showNotification('正在跳转到GitHub下载页面...', 'info');
|
|
window.open(`https://gh.bugdey.us.kg/https://github.com/${GITHUB_REPO}/releases/latest`, '_blank');
|
|
}, 2000);
|
|
} finally {
|
|
// Restore button state
|
|
setTimeout(() => {
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Get latest release from GitHub API
|
|
async function getLatestRelease() {
|
|
// Check cache first
|
|
const now = Date.now();
|
|
if (releaseData && releaseDataTimestamp && (now - releaseDataTimestamp) < CACHE_DURATION) {
|
|
return releaseData;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(GITHUB_API_URL);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`GitHub API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Cache the data
|
|
releaseData = data;
|
|
releaseDataTimestamp = now;
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Failed to fetch release data:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Find appropriate download asset based on platform
|
|
function findDownloadAsset(assets, platform) {
|
|
if (!assets || !Array.isArray(assets)) {
|
|
return null;
|
|
}
|
|
|
|
// Define file patterns for each platform
|
|
const patterns = {
|
|
windows: [
|
|
/\.exe$/i,
|
|
/windows.*\.zip$/i,
|
|
/win32.*\.zip$/i,
|
|
/win.*x64.*\.zip$/i
|
|
],
|
|
macos: [
|
|
/\.dmg$/i,
|
|
/darwin.*\.zip$/i,
|
|
/macos.*\.zip$/i,
|
|
/mac.*\.zip$/i,
|
|
/osx.*\.zip$/i
|
|
],
|
|
linux: [
|
|
/\.AppImage$/i,
|
|
/linux.*\.zip$/i,
|
|
/linux.*\.tar\.gz$/i,
|
|
/\.deb$/i,
|
|
/\.rpm$/i
|
|
]
|
|
};
|
|
|
|
const platformPatterns = patterns[platform] || [];
|
|
|
|
// Try to find exact match
|
|
for (const pattern of platformPatterns) {
|
|
const asset = assets.find(asset => pattern.test(asset.name));
|
|
if (asset) {
|
|
return asset.browser_download_url;
|
|
}
|
|
}
|
|
|
|
// Fallback: look for any asset that might match the platform
|
|
const fallbackPatterns = {
|
|
windows: /win|exe/i,
|
|
macos: /mac|darwin|dmg/i,
|
|
linux: /linux|appimage|deb|rpm/i
|
|
};
|
|
|
|
const fallbackPattern = fallbackPatterns[platform];
|
|
if (fallbackPattern) {
|
|
const asset = assets.find(asset => fallbackPattern.test(asset.name));
|
|
if (asset) {
|
|
return asset.browser_download_url;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getPlatformName(platform) {
|
|
const names = {
|
|
windows: 'Windows',
|
|
macos: 'macOS',
|
|
linux: 'Linux'
|
|
};
|
|
return names[platform] || platform;
|
|
}
|
|
|
|
// Notification system
|
|
function showNotification(message, type = 'info') {
|
|
// Remove existing notifications
|
|
const existingNotifications = document.querySelectorAll('.notification');
|
|
existingNotifications.forEach(notification => notification.remove());
|
|
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.innerHTML = `
|
|
<div class="notification-content">
|
|
<span class="notification-message">${message}</span>
|
|
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
// Add notification styles if not already added
|
|
if (!document.querySelector('#notification-styles')) {
|
|
const styles = document.createElement('style');
|
|
styles.id = 'notification-styles';
|
|
styles.textContent = `
|
|
.notification {
|
|
position: fixed;
|
|
top: 90px;
|
|
right: 20px;
|
|
background: white;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--border-radius);
|
|
box-shadow: var(--shadow-lg);
|
|
z-index: 1001;
|
|
min-width: 300px;
|
|
animation: slideInRight 0.3s ease-out;
|
|
}
|
|
|
|
.notification-info {
|
|
border-left: 4px solid var(--primary-color);
|
|
}
|
|
|
|
.notification-success {
|
|
border-left: 4px solid #10b981;
|
|
}
|
|
|
|
.notification-error {
|
|
border-left: 4px solid #ef4444;
|
|
}
|
|
|
|
.notification-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.notification-message {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.notification-close {
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
color: var(--text-muted);
|
|
padding: 0.25rem;
|
|
border-radius: 4px;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.notification-close:hover {
|
|
background: var(--surface);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.notification-close svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
@keyframes slideInRight {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.notification {
|
|
right: 10px;
|
|
left: 10px;
|
|
min-width: auto;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(styles);
|
|
}
|
|
|
|
// Add to page
|
|
document.body.appendChild(notification);
|
|
|
|
// Auto remove after 5 seconds
|
|
setTimeout(() => {
|
|
if (notification.parentElement) {
|
|
notification.style.animation = 'slideInRight 0.3s ease-out reverse';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// Navbar scroll effect
|
|
function handleNavbarScroll() {
|
|
const navbar = document.querySelector('.navbar');
|
|
if (window.scrollY > 50) {
|
|
navbar.style.background = 'rgba(255, 255, 255, 0.98)';
|
|
navbar.style.boxShadow = 'var(--shadow)';
|
|
} else {
|
|
navbar.style.background = 'rgba(255, 255, 255, 0.95)';
|
|
navbar.style.boxShadow = 'none';
|
|
}
|
|
}
|
|
|
|
// Intersection Observer for animations
|
|
function setupAnimations() {
|
|
const observerOptions = {
|
|
threshold: 0.1,
|
|
rootMargin: '0px 0px -50px 0px'
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.style.animation = 'fadeInUp 0.6s ease-out forwards';
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
// Observe feature cards and download cards
|
|
document.querySelectorAll('.feature-card, .download-card').forEach(card => {
|
|
card.style.opacity = '0';
|
|
card.style.transform = 'translateY(30px)';
|
|
observer.observe(card);
|
|
});
|
|
}
|
|
|
|
// Auto-detect user's operating system
|
|
function detectOS() {
|
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
if (userAgent.includes('win')) return 'windows';
|
|
if (userAgent.includes('mac')) return 'macos';
|
|
if (userAgent.includes('linux')) return 'linux';
|
|
return 'windows'; // default
|
|
}
|
|
|
|
// Highlight user's OS download option
|
|
function highlightUserOS() {
|
|
const userOS = detectOS();
|
|
const downloadCards = document.querySelectorAll('.download-card');
|
|
|
|
downloadCards.forEach((card, index) => {
|
|
const platforms = ['windows', 'macos', 'linux'];
|
|
if (platforms[index] === userOS) {
|
|
card.style.border = '2px solid var(--primary-color)';
|
|
card.style.transform = 'scale(1.02)';
|
|
|
|
// Add "推荐" badge
|
|
const badge = document.createElement('div');
|
|
badge.className = 'recommended-badge';
|
|
badge.textContent = '推荐';
|
|
badge.style.cssText = `
|
|
position: absolute;
|
|
top: -10px;
|
|
right: 20px;
|
|
background: var(--primary-color);
|
|
color: white;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
`;
|
|
card.style.position = 'relative';
|
|
card.appendChild(badge);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Keyboard navigation
|
|
function setupKeyboardNavigation() {
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
// Close notifications
|
|
document.querySelectorAll('.notification').forEach(notification => {
|
|
notification.remove();
|
|
});
|
|
}
|
|
|
|
if (e.key === 'Enter' && e.target.classList.contains('btn')) {
|
|
e.target.click();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Performance optimization: Lazy load images
|
|
function setupLazyLoading() {
|
|
const images = document.querySelectorAll('img[data-src]');
|
|
const imageObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute('data-src');
|
|
imageObserver.unobserve(img);
|
|
}
|
|
});
|
|
});
|
|
|
|
images.forEach(img => imageObserver.observe(img));
|
|
}
|
|
|
|
// Initialize everything when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Setup scroll effects
|
|
window.addEventListener('scroll', handleNavbarScroll);
|
|
|
|
// Setup animations
|
|
setupAnimations();
|
|
|
|
// Highlight user's OS
|
|
highlightUserOS();
|
|
|
|
// Setup keyboard navigation
|
|
setupKeyboardNavigation();
|
|
|
|
// Setup lazy loading
|
|
setupLazyLoading();
|
|
|
|
// Add GitHub links
|
|
addGitHubLinks();
|
|
|
|
// Update version information from GitHub
|
|
await updateVersionInfo();
|
|
|
|
// Add smooth scrolling to all anchor links
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
const target = document.querySelector(this.getAttribute('href'));
|
|
if (target) {
|
|
target.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Remove the old download button click handlers since downloadApp now handles everything
|
|
// The downloadApp function is called directly from the HTML onclick attributes
|
|
});
|
|
|
|
// Add spinning animation for loading state
|
|
const spinningStyles = document.createElement('style');
|
|
spinningStyles.textContent = `
|
|
.spinning {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
`;
|
|
document.head.appendChild(spinningStyles);
|
|
|
|
// Error handling for failed downloads
|
|
window.addEventListener('error', (e) => {
|
|
console.error('页面错误:', e.error);
|
|
});
|
|
|
|
// Update version information on page
|
|
async function updateVersionInfo() {
|
|
try {
|
|
const release = await getLatestRelease();
|
|
if (release) {
|
|
const versionElement = document.querySelector('.version');
|
|
const versionInfoElement = document.querySelector('.version-info p');
|
|
|
|
if (versionElement) {
|
|
versionElement.textContent = release.tag_name;
|
|
}
|
|
|
|
if (versionInfoElement) {
|
|
const publishDate = new Date(release.published_at);
|
|
const formattedDate = publishDate.toLocaleDateString('zh-CN', {
|
|
year: 'numeric',
|
|
month: 'long'
|
|
});
|
|
versionInfoElement.innerHTML = `当前版本: <span class="version">${release.tag_name}</span> | 更新时间: ${formattedDate}`;
|
|
}
|
|
|
|
// Update download button text with file sizes if available
|
|
updateDownloadButtonsWithAssets(release.assets);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update version info:', error);
|
|
}
|
|
}
|
|
|
|
// Update download buttons with asset information
|
|
function updateDownloadButtonsWithAssets(assets) {
|
|
if (!assets || !Array.isArray(assets)) return;
|
|
|
|
const downloadCards = document.querySelectorAll('.download-card');
|
|
const platforms = ['windows', 'macos', 'linux'];
|
|
|
|
downloadCards.forEach((card, index) => {
|
|
const platform = platforms[index];
|
|
const asset = findAssetForPlatform(assets, platform);
|
|
|
|
if (asset) {
|
|
const button = card.querySelector('.btn-download');
|
|
const sizeText = formatFileSize(asset.size);
|
|
const originalText = button.innerHTML;
|
|
|
|
// Add file size info
|
|
button.innerHTML = originalText.replace(/下载 \.(.*?)$/, `下载 .${getFileExtension(asset.name)} (${sizeText})`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Helper function to find asset for platform
|
|
function findAssetForPlatform(assets, platform) {
|
|
const patterns = {
|
|
windows: [/\.exe$/i, /windows.*\.zip$/i, /win32.*\.zip$/i],
|
|
macos: [/\.dmg$/i, /darwin.*\.zip$/i, /macos.*\.zip$/i],
|
|
linux: [/\.AppImage$/i, /linux.*\.zip$/i, /\.deb$/i]
|
|
};
|
|
|
|
const platformPatterns = patterns[platform] || [];
|
|
|
|
for (const pattern of platformPatterns) {
|
|
const asset = assets.find(asset => pattern.test(asset.name));
|
|
if (asset) return asset;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Helper function to get file extension
|
|
function getFileExtension(filename) {
|
|
return filename.split('.').pop();
|
|
}
|
|
|
|
// Helper function to format file size
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// Analytics tracking (placeholder)
|
|
function trackDownload(platform, version) {
|
|
// Add your analytics tracking code here
|
|
console.log(`Download tracked: ${platform} v${version}`);
|
|
|
|
// Example: Google Analytics
|
|
// gtag('event', 'download', {
|
|
// 'event_category': 'software',
|
|
// 'event_label': platform,
|
|
// 'value': version
|
|
// });
|
|
}
|
|
|
|
// Add GitHub link functionality
|
|
function addGitHubLinks() {
|
|
// Add GitHub link to footer if not exists
|
|
const footerSection = document.querySelector('.footer-section:nth-child(3) ul');
|
|
if (footerSection) {
|
|
const githubLink = document.createElement('li');
|
|
githubLink.innerHTML = `<a href="https://github.com/${GITHUB_REPO}" target="_blank">GitHub 仓库</a>`;
|
|
footerSection.appendChild(githubLink);
|
|
}
|
|
|
|
// Add "查看所有版本" link to download section
|
|
const versionInfo = document.querySelector('.version-info');
|
|
if (versionInfo) {
|
|
const allVersionsLink = document.createElement('p');
|
|
allVersionsLink.innerHTML = `<a href="https://github.com/${GITHUB_REPO}/releases" target="_blank" style="color: var(--primary-color); text-decoration: none;">查看所有版本 →</a>`;
|
|
allVersionsLink.style.marginTop = '1rem';
|
|
versionInfo.appendChild(allVersionsLink);
|
|
}
|
|
} |