trading-pulse / style.css
datamk's picture
Upload 9 files
5dcfd2c verified
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 = '<div class="loader">Market data sync failed. Check connection.</div>';
}
}
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 = '<div class="loader">No matches for current trading filters.</div>';
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 `
<article class="news-article" style="animation-delay: ${index * 0.02}s">
<div class="article-header">
<span class="source">${item.source}</span>
<span class="time">${timeAgo}</span>
</div>
<a href="${item.link}" target="_blank" style="text-decoration: none; color: inherit;">
<h3>${highlightedTitle}</h3>
</a>
</article>
`;
}).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 `
<div class="ticker-item">
<span class="ticker-name">${name}</span>
<div class="ticker-data">
<span class="ticker-price">${parseFloat(data.price).toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
<span class="ticker-change ${isUp ? 'up' : 'down'}">${sign}${data.changePercent}%</span>
</div>
</div>
`;
}).join('');
}
function highlightImpact(title) {
let result = title;
Object.entries(IMPACT_REGEX).forEach(([type, regex]) => {
result = result.replace(regex, `<span class="impact-word impact-${type}">$1</span>`);
});
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);