广告位 1
广告位 2
加载中...
`; mediaHtml += ``; } } // Content and link (no title) html += `
${mediaHtml}${ad.content ? `
${ad.content}
` : ''}${ad.link ? `了解更多 →` : ''}
`; } else { html += `
广告位 ${i}
`; } } html += ''; container.innerHTML = html; } // Carousel navigation function window.goToSlide = function(carouselId, index) { const carousel = document.getElementById(carouselId); if (!carousel) return; const images = carousel.querySelectorAll('.carousel-img'); const dots = carousel.querySelectorAll('.carousel-dot'); const total = parseInt(carousel.dataset.total); images.forEach((img, idx) => { img.style.opacity = idx === index ? '1' : '0'; }); dots.forEach((dot, idx) => { dot.style.background = idx === index ? '#1a73e8' : '#ccc'; dot.classList.toggle('active', idx === index); }); // Reset auto-play if (window.adCarousels && window.adCarousels[carouselId]) { clearInterval(window.adCarousels[carouselId]); window.adCarousels[carouselId] = setInterval(() => { const currentActive = carousel.querySelector('.carousel-dot.active'); const currentIdx = currentActive ? parseInt(currentActive.dataset.index) : 0; const nextIdx = (currentIdx + 1) % total; goToSlide(carouselId, nextIdx); }, 3000); } }; window.startAdCarousel = function(carouselId, totalImages) { if (!window.adCarousels) window.adCarousels = {}; if (window.adCarousels[carouselId]) { clearInterval(window.adCarousels[carouselId]); } window.adCarousels[carouselId] = setInterval(() => { const carousel = document.getElementById(carouselId); if (!carousel) { clearInterval(window.adCarousels[carouselId]); delete window.adCarousels[carouselId]; return; } const currentActive = carousel.querySelector('.carousel-dot.active'); const currentIdx = currentActive ? parseInt(currentActive.dataset.index) : 0; const nextIdx = (currentIdx + 1) % totalImages; goToSlide(carouselId, nextIdx); }, 3000); // 3 seconds interval } function renderCategories() { const nav = document.getElementById('categoryNav'); let html = `全部`; categories.forEach(cat => { if (cat.isActive) { const isActive = currentCategory === cat.name ? 'active' : ''; html += `${cat.icon} ${cat.name}`; } }); nav.innerHTML = html; } function renderLocations() { const nav = document.getElementById('locationNav'); const locationBar = document.getElementById('locationBar'); // 如果选择了具体分类,显示地区筛选 if (currentCategory !== 'all') { locationBar.style.display = 'block'; let html = ''; locations.forEach(loc => { const isActive = currentLocation === loc.name ? 'active' : ''; html += `${loc.name}`; }); nav.innerHTML = html; // Mobile: auto scroll after rendering (for category selection) if (window.innerWidth <= 768) { setTimeout(function() { // All categories scroll to location bar locationBar.scrollIntoView({behavior: 'smooth', block: 'start'}); }, 100); } } else { locationBar.style.display = 'none'; currentLocation = 'all'; } } function selectLocation(location) { currentLocation = location; // Save current scroll position const scrollPos = window.scrollY || window.pageYOffset; // Update active state document.querySelectorAll('.location-link').forEach(link => { link.classList.remove('active'); const linkLoc = decodeURIComponent(link.dataset.location); if (linkLoc === location || (location === 'all' && link.dataset.location === 'all')) { link.classList.add('active'); } }); updatePageHeader(); loadPosts(); // Restore scroll position after posts load requestAnimationFrame(() => { setTimeout(() => { window.scrollTo({top: scrollPos, behavior: 'instant'}); }, 150); }); // 更新 URL 但不刷新页面 const url = new URL(window.location); url.searchParams.set('location', location); window.history.pushState({}, '', url); return false; } function updatePageHeader() { const titleEl = document.getElementById('categoryTitle'); const subtitleEl = document.getElementById('categorySubtitle'); if (currentCategory === 'all') { titleEl.textContent = '最新信息'; subtitleEl.textContent = '浏览纽约华人社区最新资讯'; } else { let title = currentCategory; let subtitle = `浏览${currentCategory}相关信息`; if (currentLocation !== 'all') { title = `${currentCategory} - ${currentLocation}`; subtitle = `浏览${currentLocation}地区的${currentCategory}信息`; } titleEl.textContent = title; subtitleEl.textContent = subtitle; } } function initFromURL() { const params = new URLSearchParams(location.search); currentCategory = params.get('category') || 'all'; currentJobType = params.get('jobType') || 'all'; currentLocation = params.get('location') || 'all'; // Auto-scroll disabled on page load // Update category nav document.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active')); const categoryLinks = document.querySelectorAll('.nav-link'); categoryLinks.forEach(link => { if ((currentCategory === 'all' && link.getAttribute('href') === '/') || link.getAttribute('href') === `/?category=${encodeURIComponent(currentCategory)}`) { link.classList.add('active'); } }); updatePageHeader(); } async function loadPosts() { const list = document.getElementById('postsList'); list.innerHTML = '
加载中...
'; try { let url = `${API_BASE}/posts?category=${currentCategory}`; if (currentJobType !== 'all') { url += `&jobType=${encodeURIComponent(currentJobType)}`; } if (currentLocation !== 'all') { url += `&location=${encodeURIComponent(currentLocation)}`; } const res = await fetch(url); const data = await res.json(); // 前端再次过滤确保只显示选中地区的帖子 let posts = data.posts || []; if (currentLocation !== 'all') { posts = posts.filter(p => p.location === currentLocation); } if (data.success && posts.length) { list.innerHTML = posts.map(p => `
${p.jobType ? `${p.jobType}` : ''} ${p.location ? `${p.location}` : ''}
${formatTime(p.createdAt)}${p.isPinned ? '置顶' : ''}

${escapeHtml(p.title)}

${escapeHtml(p.content)}

${p.images && p.images.length > 0 ? `
${p.images.slice(0,3).map(img => ``).join('')}${p.images.length > 3 ? `+${p.images.length-3}` : ''}
` : ''} ${p.phone ? `
📞 ${p.phone} ${p.allowCall ? `☎️ 打电话` : ''} ${p.allowMessage ? `💬 发信息` : ''}
` : ''}
`).join(''); } else { list.innerHTML = '
该分类地区暂无信息,点击发布第一条
'; } } catch (e) { list.innerHTML = '
加载失败
'; } } function formatTime(d) { const now = new Date(), t = new Date(d), diff = now - t; if (diff < 3600000) return Math.floor(diff/60000) + '分钟前'; if (diff < 86400000) return Math.floor(diff/3600000) + '小时前'; return t.toLocaleDateString(); } function escapeHtml(t) { const d = document.createElement('div'); d.textContent = t; return d.innerHTML; } function viewPost(id) { location.href = '/post/' + id; } function goToPublish() { if (!getToken()) { if (confirm('请先登录')) location.href = '/login'; } else location.href = '/publish'; } async function updateUserSection() { const section = document.getElementById('userSection'); if (!getToken()) return; try { const res = await fetch(`${API_BASE}/auth/verify`, { headers: { Authorization: 'Bearer ' + getToken() } }); const data = await res.json(); if (data.success) section.innerHTML = `👤 ${data.user.username}退出`; } catch (e) {} } function logout() { localStorage.clear(); sessionStorage.clear(); location.href = '/'; } // Initialize initFromURL(); loadCategories().then(() => { loadLocations().then(() => { renderLocations(); }); loadJobTypes(); }); loadAds(); loadPosts(); updateUserSection(); // Simple View History const VIEWED_KEY = 'nyinfo_viewed_v3'; function markViewed(postId) { const viewed = JSON.parse(localStorage.getItem(VIEWED_KEY) || '{}'); viewed[postId] = Date.now(); localStorage.setItem(VIEWED_KEY, JSON.stringify(viewed)); // Apply immediately const card = document.querySelector('.post-card[data-post-id="' + postId + '"]'); if (card) card.classList.add('post-viewed'); } function applyViewed() { const viewed = JSON.parse(localStorage.getItem(VIEWED_KEY) || '{}'); document.querySelectorAll('.post-card').forEach(card => { if (viewed[card.dataset.postId]) { card.classList.add('post-viewed'); } }); } // Run on load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', applyViewed); } else { applyViewed(); } // Also run after posts load const origLoadPosts = window.loadPosts; if (origLoadPosts) { window.loadPosts = async function() { await origLoadPosts.apply(this, arguments); setTimeout(applyViewed, 200); }; } async function loadCategories() { try { const res = await fetch(`${API_BASE}/categories`); const data = await res.json(); if (data.success && data.categories) { categories = data.categories; renderCategories(); } } catch (e) { console.error('Failed to load categories'); } } async function loadLocations() { try { const res = await fetch(`${API_BASE}/locations`); const data = await res.json(); if (data.success && data.locations) { locations = data.locations.filter(l => l.isActive); renderLocations(); } } catch (e) { console.error('Failed to load locations'); } } async function loadAds() { try { // Get ads for current category let url = `${API_BASE}/ads`; if (currentCategory !== 'all') { url += `?category=${encodeURIComponent(currentCategory)}`; } const res = await fetch(url); const data = await res.json(); if (data.success && data.ads) renderAds(data.ads); } catch (e) { console.error('Failed to load ads'); } } function renderAds(ads) { const container = document.getElementById('adsContainer'); const activeAds = ads ? ads.filter(a => a.isActive) : []; let html = '
'; for (let i = 1; i <= 2; i++) { const ad = activeAds.find(a => a.slot === i); if (ad) { // Get all images: ad.images array or single ad.image let allImages = []; if (ad.images && Array.isArray(ad.images) && ad.images.length > 0) { allImages = ad.images; } else if (ad.image) { allImages = [ad.image]; } // Filter videos and images const videos = allImages.filter(img => img && (img.match(/\.(mp4|webm|mov)$/i) || img.startsWith('data:video'))); const images = allImages.filter(img => img && !img.match(/\.(mp4|webm|mov)$/i) && !img.startsWith('data:video')); const adId = ad._id || `ad-${i}`; let mediaHtml = ''; // Handle videos - autoplay muted loop if (videos.length > 0) { videos.forEach((video, idx) => { mediaHtml += ``; }); } // Handle images with carousel if (images.length > 0) { if (images.length === 1) { // Single image mediaHtml += `广告`; } else { // Multiple images - carousel const carouselId = `carousel-${adId}`; mediaHtml += ``; } } // Content and link (no title) html += `
${mediaHtml}${ad.content ? `
${ad.content}
` : ''}${ad.link ? `了解更多 →` : ''}
`; } else { html += `
广告位 ${i}
`; } } html += '
'; container.innerHTML = html; } // Carousel navigation function window.goToSlide = function(carouselId, index) { const carousel = document.getElementById(carouselId); if (!carousel) return; const images = carousel.querySelectorAll('.carousel-img'); const dots = carousel.querySelectorAll('.carousel-dot'); const total = parseInt(carousel.dataset.total); images.forEach((img, idx) => { img.style.opacity = idx === index ? '1' : '0'; }); dots.forEach((dot, idx) => { dot.style.background = idx === index ? '#1a73e8' : '#ccc'; dot.classList.toggle('active', idx === index); }); // Reset auto-play if (window.adCarousels && window.adCarousels[carouselId]) { clearInterval(window.adCarousels[carouselId]); window.adCarousels[carouselId] = setInterval(() => { const currentActive = carousel.querySelector('.carousel-dot.active'); const currentIdx = currentActive ? parseInt(currentActive.dataset.index) : 0; const nextIdx = (currentIdx + 1) % total; goToSlide(carouselId, nextIdx); }, 3000); } }; window.startAdCarousel = function(carouselId, totalImages) { if (!window.adCarousels) window.adCarousels = {}; if (window.adCarousels[carouselId]) { clearInterval(window.adCarousels[carouselId]); } window.adCarousels[carouselId] = setInterval(() => { const carousel = document.getElementById(carouselId); if (!carousel) { clearInterval(window.adCarousels[carouselId]); delete window.adCarousels[carouselId]; return; } const currentActive = carousel.querySelector('.carousel-dot.active'); const currentIdx = currentActive ? parseInt(currentActive.dataset.index) : 0; const nextIdx = (currentIdx + 1) % totalImages; goToSlide(carouselId, nextIdx); }, 3000); // 3 seconds interval } function renderCategories() { const nav = document.getElementById('categoryNav'); let html = `全部`; categories.forEach(cat => { if (cat.isActive) { const isActive = currentCategory === cat.name ? 'active' : ''; html += `${cat.icon} ${cat.name}`; } }); nav.innerHTML = html; } function initFromURL() { const params = new URLSearchParams(location.search); currentCategory = params.get('category') || 'all'; currentJobType = params.get('jobType') || 'all'; currentLocation = params.get('location') || 'all'; // Auto-scroll disabled on page load // Update category nav document.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active')); const categoryLinks = document.querySelectorAll('.nav-link'); categoryLinks.forEach(link => { if ((currentCategory === 'all' && link.getAttribute('href') === '/') || link.getAttribute('href') === `/?category=${encodeURIComponent(currentCategory)}`) { link.classList.add('active'); } }); updatePageHeader(); } async function updateUserSection() { const section = document.getElementById('userSection'); if (!getToken()) return; try { const res = await fetch(`${API_BASE}/auth/verify`, { headers: { Authorization: 'Bearer ' + getToken() } }); const data = await res.json(); if (data.success) section.innerHTML = `👤 ${data.user.username}退出`; } catch (e) {} } function logout() { localStorage.clear(); sessionStorage.clear(); location.href = '/'; } // Initialize initFromURL(); loadCategories().then(() => { loadLocations().then(() => { renderLocations(); }); loadJobTypes(); }); loadAds(); loadPosts(); updateUserSection(); // ===== View History Feature (Button Version) ===== (function() { const VIEWED_KEY = 'nyinfo_viewed_posts'; const VIEWED_EXPIRE_DAYS = 30; function getViewedPosts() { try { const data = JSON.parse(localStorage.getItem(VIEWED_KEY) || '{}'); const now = Date.now(); const expireTime = VIEWED_EXPIRE_DAYS * 24 * 60 * 60 * 1000; const validPosts = {}; let hasExpired = false; for (const [id, timestamp] of Object.entries(data)) { if (now - timestamp < expireTime) { validPosts[id] = timestamp; } else { hasExpired = true; } } if (hasExpired) { localStorage.setItem(VIEWED_KEY, JSON.stringify(validPosts)); } return validPosts; } catch (e) { return {}; } } window.markPostAsViewed = function(postId, btn) { if (!postId) return; try { const VIEWED_KEY = 'nyinfo_viewed_v2'; const viewed = JSON.parse(localStorage.getItem(VIEWED_KEY) || '{}'); viewed[postId] = Date.now(); localStorage.setItem(VIEWED_KEY, JSON.stringify(viewed)); // 立即变色 const card = btn.closest('.post-card'); if (card) { card.classList.add('post-viewed'); } // 隐藏按钮 btn.style.display = 'none'; } catch (e) {} }; window.applyViewedStyles = function() { const VIEWED_KEY = 'nyinfo_viewed_v2'; const viewed = JSON.parse(localStorage.getItem(VIEWED_KEY) || '{}'); document.querySelectorAll('.post-card').forEach(card => { const postId = card.dataset.postId; if (postId && viewed[postId]) { card.classList.add('post-viewed'); } }); }; // Apply on DOM ready and after posts load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', window.applyViewedStyles); } else { window.applyViewedStyles(); } // Also apply after posts are loaded const originalLoadPosts = window.loadPosts; if (originalLoadPosts) { window.loadPosts = async function() { await originalLoadPosts.apply(this, arguments); setTimeout(window.applyViewedStyles, 100); }; } })(); // Mobile scroll to location filter function scrollToLocationIfMobile() { if (window.innerWidth <= 768) { setTimeout(function() { // For 招聘求职, scroll to jobtype bar if visible var jobtypeBar = document.getElementById('jobTypeBar'); if (jobtypeBar && jobtypeBar.classList.contains('visible')) { // scroll disabled return; } // Otherwise scroll to location bar var bar = document.querySelector('.location-bar'); if (bar && bar.style.display !== 'none') { // scroll disabled } }, 300); } }