/**
* OSS Community Module
* Handles rendering of categories, stats, and posts list.
* Designed to be SPA-friendly (re-initializable).
*/
// Global state variables
let currentPage = 1;
let currentCategory = '';
let currentSort = 'latest';
// Expose initialization function globally for SPA Router
window.initCommunity = function() {
// Check if we are deep-linked (e.g. ?page=2)
const params = new URLSearchParams(window.location.search);
if(params.has('page')) currentPage = parseInt(params.get('page'));
// Fetch and Render Data
loadCategories();
loadStats();
loadPosts();
// Bind Events (to the new DOM elements created by SPA)
bindCommunityEvents();
};
function bindCommunityEvents() {
const catList = document.getElementById('categoryList');
if (catList) {
catList.addEventListener('click', e => {
const li = e.target.closest('li');
if (!li) return;
document.querySelectorAll('.category-list li').forEach(el => el.classList.remove('active'));
li.classList.add('active');
currentCategory = li.dataset.id || '';
currentPage = 1;
document.getElementById('currentCategory').textContent = li.querySelector('span:nth-child(2)').textContent + '帖子';
loadPosts();
});
}
const sortOptions = document.querySelector('.sort-options');
if (sortOptions) {
sortOptions.addEventListener('click', e => {
if (!e.target.classList.contains('sort-btn')) return;
document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active'));
e.target.classList.add('active');
currentSort = e.target.dataset.sort;
currentPage = 1;
loadPosts();
});
}
}
// --- Data Loading Functions ---
const API = './api/index.php'; // Relative to community/ directory
async function loadCategories() {
try {
const res = await fetch(`${API}?action=categories`);
const data = await res.json();
const list = document.getElementById('categoryList');
if (!list) return;
// Keep the "All" item (first child) if it exists
const allItem = list.firstElementChild;
list.innerHTML = '';
if (allItem) list.appendChild(allItem);
data.categories.forEach(cat => {
const li = document.createElement('li');
li.dataset.id = cat.id;
li.innerHTML = `${getIconSvg(cat.icon)}${cat.name}`;
list.appendChild(li);
});
} catch (e) {
console.error('Failed to load categories', e);
}
}
async function loadStats() {
try {
const res = await fetch(`${API}?action=stats`);
const data = await res.json();
animateValue('statPosts', 0, data.posts, 1000);
animateValue('statReplies', 0, data.replies, 1000);
animateValue('statUsers', 0, data.users, 1000);
const countAll = document.getElementById('countAll');
if (countAll) countAll.textContent = data.posts;
} catch (e) {
console.error('Failed to load stats', e);
}
}
async function loadPosts() {
const list = document.getElementById('postsList');
if (!list) return;
list.innerHTML = '
';
try {
const params = new URLSearchParams({ action: 'posts', page: currentPage });
if (currentCategory) params.append('category_id', currentCategory);
const res = await fetch(`${API}?${params}`);
const data = await res.json();
list.innerHTML = '';
if (data.posts.length === 0) {
list.innerHTML = `
`;
return;
}
data.posts.forEach((post, index) => {
const card = document.createElement('div');
card.className = 'post-card';
card.dataset.postId = post.id;
card.style.animationDelay = `${index * 0.1}s`;
card.innerHTML = `
${escapeHtml(post.title)}
${escapeHtml(post.content.substring(0, 150))}...
${post.views}
${post.likes}
${post.reply_count || 0}
`;
list.appendChild(card);
});
renderPagination(data.pages);
} catch (e) {
list.innerHTML = '';
}
}
function renderPagination(pages) {
const container = document.getElementById('pagination');
if (!container) return;
container.innerHTML = '';
if (pages <= 1) return;
for (let i = 1; i <= pages; i++) {
const btn = document.createElement('button');
btn.className = `page-btn ${i === currentPage ? 'active' : ''}`;
btn.textContent = i;
btn.onclick = () => {
currentPage = i;
loadPosts();
window.scrollTo({ top: 0, behavior: 'smooth' });
};
container.appendChild(btn);
}
}
// --- Utilities ---
function animateValue(id, start, end, duration) {
const obj = document.getElementById(id);
if (!obj) return;
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
obj.textContent = Math.floor(progress * (end - start) + start);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
function timeAgo(dateStr) {
const now = new Date();
const date = new Date(dateStr);
const seconds = Math.floor((now - date) / 1000);
const intervals = [
[31536000, '年'], [2592000, '个月'], [604800, '周'],
[86400, '天'], [3600, '小时'], [60, '分钟']
];
for (const [secondsCount, label] of intervals) {
const count = Math.floor(seconds / secondsCount);
if (count >= 1) return `${count}${label}前`;
}
return '刚刚';
}
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function getIconSvg(name) {
const icons = {
megaphone: '',
question: '',
chat: '',
puzzle: '',
bug: ''
};
return icons[name] || icons['chat'];
}
// showCreateModal 函数已移至 post-modal.php 中定义
// 此处仅保留兼容性封装
window.showCreateModal = window.showCreateModal || function() {
if (typeof window.showCreatePostModal === 'function') {
window.showCreatePostModal();
} else {
console.warn('发帖模态框未加载');
}
};
// Auto-init if not loaded via SPA (Hard refresh case)
document.addEventListener('DOMContentLoaded', () => {
if (typeof AppRouter === 'undefined') {
window.initCommunity();
}
});