let allNews = []; let sentimentTrend = []; let currentFilter = 'all'; let currentSource = 'all'; let searchQuery = ''; let renderedCount = 0; let currentRenderedFiltered = []; const IMPACT_KEYWORDS = { high: ['crash', 'crisis', 'urgent', 'breaking', 'collapsed', 'warns', 'surge', 'plunge', 'lockdown', 'rating', 'result', 'focus'], pos: ['profit', 'growth', 'record', 'dividend', 'buyback', 'partnership', 'acquired', 'expansion', 'jump', 'bullish', 'buy'], neg: ['loss', 'fall', 'slump', 'down', 'decline', 'investigation', 'scam', 'penalty', 'lawsuit', 'bearish', 'sell'] }; const IMPACT_REGEX = Object.fromEntries( Object.entries(IMPACT_KEYWORDS).map(([type, words]) => [ type, new RegExp(`\\b(${words.join('|')})\\b`, 'gi') ]) ); async function fetchNews() { try { const response = await fetch('/api/news'); const data = await response.json(); allNews = data.news; sentimentTrend = data.trend; renderDashboard(); renderIndices(data.indices); } catch (error) { console.error('Error fetching news:', error); const feed = document.getElementById('news-feed'); if (feed) feed.innerHTML = '
Market data sync failed. Check connection.
'; } } async function fetchIndicesOnly() { try { const response = await fetch('/api/indices'); const data = await response.json(); renderIndices(data.indices); } catch (error) { console.error('Error updating indices:', error); } } function renderDashboard() { renderFeed(); } function renderFeed() { const feed = document.getElementById('news-feed'); if (!feed) return; let filtered = allNews; if (currentFilter !== 'all') filtered = filtered.filter(n => n.sentiment.label === currentFilter); if (currentSource !== 'all') filtered = filtered.filter(n => n.source === currentSource); if (searchQuery) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(n => n.title.toLowerCase().includes(query) || n.source.toLowerCase().includes(query) ); } // Filter out headlines with less than 5 words filtered = filtered.filter(n => { const words = n.title.trim().split(/\s+/).filter(w => /[a-zA-Z0-9]/.test(w)); return words.length >= 5; }); currentRenderedFiltered = filtered; renderedCount = 0; if (filtered.length === 0) { feed.innerHTML = '
No matches for current trading filters.
'; return; } feed.innerHTML = ''; renderMoreItems(); } function renderMoreItems() { const feed = document.getElementById('news-feed'); if (!feed) return; const oldTrigger = document.getElementById('load-more-trigger'); if (oldTrigger) oldTrigger.remove(); const nextCount = Math.min(renderedCount + 100, currentRenderedFiltered.length); const itemsToRender = currentRenderedFiltered.slice(renderedCount, nextCount); const html = itemsToRender.map((item, index) => { const timeAgo = getTimeAgo(new Date(item.pubDate)); const highlightedTitle = highlightImpact(item.title); return `
${item.source} ${timeAgo}

${highlightedTitle}

`; }).join(''); feed.insertAdjacentHTML('beforeend', html); renderedCount = nextCount; if (renderedCount < currentRenderedFiltered.length) { const trigger = document.createElement('div'); trigger.id = 'load-more-trigger'; trigger.className = 'loader'; trigger.textContent = 'Scroll for more...'; feed.appendChild(trigger); setupObserver(trigger); } } function setupObserver(trigger) { const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { observer.disconnect(); renderMoreItems(); } }); observer.observe(trigger); } function renderIndices(indices) { const container = document.getElementById('index-ticker'); if (!container || !indices) return; container.innerHTML = Object.entries(indices).map(([name, data]) => { const isUp = parseFloat(data.change) >= 0; const sign = isUp ? '+' : ''; return `
${name}
${parseFloat(data.price).toLocaleString('en-IN', { minimumFractionDigits: 2 })} ${sign}${data.changePercent}%
`; }).join(''); } function highlightImpact(title) { let result = title; Object.entries(IMPACT_REGEX).forEach(([type, regex]) => { result = result.replace(regex, `$1`); }); return result; } function getTimeAgo(date) { const seconds = Math.floor((new Date() - date) / 1000); let interval = seconds / 3600; if (interval > 1) return Math.floor(interval) + 'h ago'; interval = seconds / 60; if (interval > 1) return Math.floor(interval) + 'm ago'; return 'Just now'; } function filterBySentiment(sentiment) { currentFilter = sentiment; document.querySelectorAll('.filter-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.sentiment === sentiment); }); renderFeed(); } let searchTimeout; function handleSearch(val) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { searchQuery = val; renderFeed(); }, 300); } function openSupportModal() { document.getElementById('support-modal').classList.add('active'); history.pushState({ modal: 'support' }, ''); } function closeSupportModal() { document.getElementById('support-modal').classList.remove('active'); } window.addEventListener('popstate', function (e) { if (document.getElementById('support-modal').classList.contains('active')) { closeSupportModal(); } }); fetchNews(); setInterval(fetchNews, 30000); setInterval(fetchIndicesOnly, 10000);