|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
setTimeout(() => {
|
|
|
|
initializeSearch();
|
|
|
|
initializeMarkdownRendering();
|
|
|
|
initializeScrollToTop();
|
|
|
|
initializeMobileNavigation();
|
|
}, 100);
|
|
});
|
|
|
|
|
|
function initializeSearch() {
|
|
const searchInput = document.querySelector('.search-input');
|
|
const searchResults = document.querySelector('.search-results');
|
|
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', debounce(async (event) => {
|
|
const searchTerm = event.target.value.trim().toLowerCase();
|
|
if (searchTerm.length < 2) {
|
|
searchResults.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`);
|
|
const data = await response.json();
|
|
|
|
if (data.articles.length > 0) {
|
|
displaySearchResults(data.articles, searchResults);
|
|
} else {
|
|
searchResults.innerHTML = '<div class="no-results">没有找到相关文章</div>';
|
|
}
|
|
searchResults.style.display = 'block';
|
|
} catch (error) {
|
|
console.error('搜索出错:', error);
|
|
searchResults.innerHTML = '<div class="error">搜索服务暂时不可用</div>';
|
|
}
|
|
}, 300));
|
|
|
|
|
|
document.addEventListener('click', (event) => {
|
|
if (!event.target.closest('.search-container')) {
|
|
searchResults.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
function initializeMarkdownRendering() {
|
|
if (typeof marked === 'undefined') {
|
|
console.error('Marked library not loaded');
|
|
return;
|
|
}
|
|
|
|
|
|
marked.use({
|
|
breaks: true,
|
|
gfm: true
|
|
});
|
|
|
|
const markdownElements = document.querySelectorAll('.markdown-body');
|
|
|
|
markdownElements.forEach(element => {
|
|
|
|
if (element.dataset.rendered) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
|
|
element.dataset.rendered = 'true';
|
|
|
|
|
|
if (!element.classList.contains('server-rendered')) {
|
|
const content = element.textContent;
|
|
element.innerHTML = marked(content);
|
|
}
|
|
|
|
|
|
element.querySelectorAll('pre code').forEach(block => {
|
|
if (typeof hljs !== 'undefined') {
|
|
hljs.highlightElement(block);
|
|
}
|
|
});
|
|
|
|
|
|
element.querySelectorAll('img').forEach(img => {
|
|
img.addEventListener('click', () => {
|
|
openImageViewer(img.src);
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('Markdown渲染错误:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function initializeScrollToTop() {
|
|
const scrollTopButton = document.createElement('button');
|
|
scrollTopButton.className = 'scroll-top-button';
|
|
scrollTopButton.innerHTML = '↑';
|
|
document.body.appendChild(scrollTopButton);
|
|
|
|
window.addEventListener('scroll', debounce(() => {
|
|
if (window.scrollY > 500) {
|
|
scrollTopButton.classList.add('visible');
|
|
} else {
|
|
scrollTopButton.classList.remove('visible');
|
|
}
|
|
}, 100));
|
|
|
|
scrollTopButton.addEventListener('click', () => {
|
|
window.scrollTo({
|
|
top: 0,
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
function initializeMobileNavigation() {
|
|
const menuButton = document.querySelector('.menu-button');
|
|
const navLinks = document.querySelector('.nav-links');
|
|
|
|
if (menuButton && navLinks) {
|
|
menuButton.addEventListener('click', () => {
|
|
navLinks.classList.toggle('active');
|
|
menuButton.classList.toggle('active');
|
|
});
|
|
|
|
|
|
navLinks.querySelectorAll('a').forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
navLinks.classList.remove('active');
|
|
menuButton.classList.remove('active');
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
function openImageViewer(imageSrc) {
|
|
const viewer = document.createElement('div');
|
|
viewer.className = 'image-viewer';
|
|
viewer.innerHTML = `
|
|
<div class="image-viewer-content">
|
|
<img src="${imageSrc}" alt="预览图片">
|
|
<button class="close-button">×</button>
|
|
</div>
|
|
`;
|
|
|
|
viewer.addEventListener('click', (event) => {
|
|
if (event.target === viewer || event.target.className === 'close-button') {
|
|
viewer.remove();
|
|
}
|
|
});
|
|
|
|
document.body.appendChild(viewer);
|
|
}
|
|
|
|
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
|
|
function displaySearchResults(articles, container) {
|
|
container.innerHTML = articles.map(article => `
|
|
<a href="/article/${article.slug}" class="search-result-item">
|
|
<h3>${highlightSearchTerm(article.title)}</h3>
|
|
${article.summary ? `<p>${highlightSearchTerm(article.summary)}</p>` : ''}
|
|
<span class="article-date">${formatDate(article.created_at)}</span>
|
|
</a>
|
|
`).join('');
|
|
}
|
|
|
|
|
|
function highlightSearchTerm(text, searchTerm) {
|
|
if (!searchTerm) return text;
|
|
const regex = new RegExp(searchTerm, 'gi');
|
|
return text.replace(regex, match => `<mark>${match}</mark>`);
|
|
}
|
|
|
|
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit'
|
|
});
|
|
}
|
|
|
|
|
|
function handleError(error, container) {
|
|
console.error('发生错误:', error);
|
|
container.innerHTML = `
|
|
<div class="error-message">
|
|
<p>抱歉,发生了一些错误</p>
|
|
<button onclick="window.location.reload()">刷新页面</button>
|
|
</div>
|
|
`;
|
|
} |