Files
Your Name 6d4368fbd1 Initial commit: TechBlog Pro WordPress theme
Features:
- Modern UI with dark mode support
- Code highlighting with copy button
- Auto table of contents
- Reading time & views counter
- Responsive design
- Admin settings page
- AJAX search
2026-08-18 16:05:14 +08:00

335 lines
12 KiB
JavaScript
Executable File

/**
* TechBlog Pro - Main JavaScript
*
* @package TechBlog Pro
* @version 1.0.0
*/
(function() {
'use strict';
// ==================== Theme Toggle ====================
const ThemeManager = {
init() {
this.toggle = document.getElementById('theme-toggle');
this.sunIcon = this.toggle?.querySelector('.sun-icon');
this.moonIcon = this.toggle?.querySelector('.moon-icon');
if (!this.toggle) return;
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
this.setTheme(savedTheme);
// Toggle event
this.toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
this.setTheme(current === 'dark' ? 'light' : 'dark');
});
},
setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
if (this.sunIcon && this.moonIcon) {
this.sunIcon.style.display = theme === 'dark' ? 'none' : 'block';
this.moonIcon.style.display = theme === 'dark' ? 'block' : 'none';
}
}
};
// ==================== Search Overlay ====================
const SearchManager = {
init() {
this.overlay = document.getElementById('search-overlay');
this.toggle = document.getElementById('search-toggle');
this.input = this.overlay?.querySelector('.search-input');
if (!this.overlay || !this.toggle) return;
// Open search
this.toggle.addEventListener('click', () => this.open());
// Close on overlay click
this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) this.close();
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// CMD/CTRL + K to open
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
this.open();
}
// ESC to close
if (e.key === 'Escape') {
this.close();
}
});
},
open() {
this.overlay.classList.add('active');
setTimeout(() => this.input?.focus(), 100);
document.body.style.overflow = 'hidden';
},
close() {
this.overlay.classList.remove('active');
document.body.style.overflow = '';
}
};
// ==================== Mobile Menu ====================
const MobileMenu = {
init() {
this.toggle = document.getElementById('mobile-menu-toggle');
this.nav = document.getElementById('main-nav');
if (!this.toggle || !this.nav) return;
this.toggle.addEventListener('click', () => {
this.nav.classList.toggle('active');
this.toggle.classList.toggle('active');
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!this.nav.contains(e.target) && !this.toggle.contains(e.target)) {
this.nav.classList.remove('active');
this.toggle.classList.remove('active');
}
});
}
};
// ==================== Table of Contents ====================
const TOCManager = {
init() {
this.tocLinks = document.querySelectorAll('.toc-list a');
this.headings = [];
if (this.tocLinks.length === 0) return;
// Collect headings
this.tocLinks.forEach(link => {
const id = link.getAttribute('href')?.slice(1);
const heading = document.getElementById(id);
if (heading) {
this.headings.push({ id, element: heading, link });
}
});
// Scroll spy
this.observer = new IntersectionObserver(
(entries) => this.handleIntersection(entries),
{ rootMargin: '-80px 0px -80% 0px' }
);
this.headings.forEach(({ element }) => {
this.observer.observe(element);
});
// Smooth scroll
this.tocLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const id = link.getAttribute('href')?.slice(1);
const target = document.getElementById(id);
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
});
});
},
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Remove all active states
this.tocLinks.forEach(link => link.classList.remove('active'));
// Add active to current
const activeLink = this.headings.find(h => h.id === entry.target.id)?.link;
activeLink?.classList.add('active');
}
});
}
};
// ==================== Code Copy ====================
window.copyCode = function(button) {
const pre = button.closest('pre');
const code = pre?.querySelector('code');
if (!code) return;
const text = code.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.innerHTML;
button.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
已复制
`;
button.style.color = '#22c55e';
setTimeout(() => {
button.innerHTML = originalText;
button.style.color = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
};
// ==================== Reading Progress ====================
const ReadingProgress = {
init() {
this.progressBar = document.createElement('div');
this.progressBar.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 0%;
height: 3px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
z-index: 9999;
transition: width 0.1s ease;
`;
document.body.appendChild(this.progressBar);
window.addEventListener('scroll', () => this.update());
},
update() {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = (scrollTop / docHeight) * 100;
this.progressBar.style.width = `${Math.min(progress, 100)}%`;
}
};
// ==================== Lazy Load Images ====================
const LazyLoad = {
init() {
if ('loading' in HTMLImageElement.prototype) {
// Native lazy loading supported
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
img.src = img.dataset.src;
});
} else {
// Fallback with IntersectionObserver
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
this.observer.unobserve(img);
}
});
},
{ rootMargin: '50px' }
);
document.querySelectorAll('img[data-src]').forEach(img => {
this.observer.observe(img);
});
}
}
};
// ==================== Copy Link ====================
window.copyLink = function() {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
const btn = document.querySelector('.share-copy');
btn.classList.add('copied');
btn.title = '已复制';
setTimeout(() => {
btn.classList.remove('copied');
btn.title = '复制链接';
}, 2000);
});
};
// ==================== Back to Top ====================
const BackToTop = {
init() {
this.button = document.createElement('button');
this.button.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20">
<line x1="12" y1="19" x2="12" y2="5"/>
<polyline points="5 12 12 5 19 12"/>
</svg>
`;
this.button.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
width: 44px;
height: 44px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 100;
color: var(--text-secondary);
box-shadow: var(--shadow-md);
`;
this.button.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
document.body.appendChild(this.button);
window.addEventListener('scroll', () => this.toggle());
},
toggle() {
if (window.scrollY > 300) {
this.button.style.opacity = '1';
this.button.style.visibility = 'visible';
} else {
this.button.style.opacity = '0';
this.button.style.visibility = 'hidden';
}
}
};
// ==================== Initialize ====================
document.addEventListener('DOMContentLoaded', () => {
ThemeManager.init();
SearchManager.init();
MobileMenu.init();
TOCManager.init();
ReadingProgress.init();
LazyLoad.init();
BackToTop.init();
// Add current page class to nav
const currentPath = window.location.pathname;
document.querySelectorAll('.main-nav a').forEach(link => {
if (link.pathname === currentPath) {
link.classList.add('current');
}
});
});
})();