Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>News Feed Hub</title> | |
<style> | |
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; color: #333; } | |
h1 { text-align: center; color: #2c3e50; } | |
.search-container { text-align: center; margin: 20px 0; } | |
.search-bar { width: 50%; padding: 10px; border: 2px solid #3498db; border-radius: 25px; } | |
.category-section { margin: 20px 0; } | |
.category-title { background-color: #3498db; color: white; padding: 10px; border-radius: 5px; } | |
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; } | |
.article-tile { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } | |
.article-tile img, .article-tile svg { width: 100%; height: 150px; object-fit: cover; border-radius: 5px; } | |
.title a { font-size: 1.1em; color: #2c3e50; text-decoration: none; } | |
.title a:hover { color: #3498db; } | |
.description { color: #555; font-size: 0.9em; } | |
.published { font-size: 0.8em; color: #95a5a6; } | |
.no-articles { text-align: center; color: #2c3e50; margin-top: 20px; } | |
.loading-message { text-align: center; color: #3498db; margin: 10px 0; } | |
</style> | |
</head> | |
<body> | |
<h1>News Feed Hub</h1> | |
<div class="search-container"> | |
<form method="POST" action="/search"> | |
<input type="text" name="search" class="search-bar" placeholder="Search news..."> | |
</form> | |
</div> | |
{% if loading %} | |
<div class="loading-message">Fetching new RSS feeds in the background...</div> | |
{% endif %} | |
{% if has_articles %} | |
{% for category, articles in categorized_articles.items() %} | |
<div class="category-section"> | |
<div class="category-title">{{ category }}</div> | |
<div class="tiles" id="category-{{ category }}" data-last-update="0"> | |
{% for article in articles %} | |
<div class="article-tile" data-published="{{ article.published }}" data-id="{{ loop.index }}"> | |
{% if article.image != "svg" %} | |
<img src="{{ article.image }}" alt="Article Image"> | |
{% else %} | |
<svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg"> | |
<rect width="100%" height="100%" fill="#e0e0e0"/> | |
<text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text> | |
</svg> | |
{% endif %} | |
<div class="title"><a href="{{ article.link }}" target="_blank">{{ article.title }}</a></div> | |
<div class="description">{{ article.description }}</div> | |
<div class="published">Published: {{ article.published }}</div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
{% endfor %} | |
{% else %} | |
<div class="no-articles">No articles available yet. Loading new feeds...</div> | |
{% endif %} | |
{% if loading %} | |
<script> | |
let lastUpdate = 0; | |
function updateArticles() { | |
fetch('/get_updates') | |
.then(response => response.json()) | |
.then(data => { | |
if (data.articles && data.last_update > lastUpdate) { | |
lastUpdate = data.last_update; | |
const newArticles = data.articles; | |
for (const [category, articles] of Object.entries(newArticles)) { | |
const tilesDiv = document.getElementById(`category-${category}`); | |
if (tilesDiv) { | |
const existingArticles = Array.from(tilesDiv.querySelectorAll('.article-tile')); | |
let currentIds = new Set(existingArticles.map(a => a.dataset.id)); | |
let newHtml = ''; | |
// Sort new articles by published date | |
articles.sort((a, b) => new Date(b.published) - new Date(a.published)); | |
// Add new articles (up to 10 total, keeping most recent) | |
articles.forEach((article, index) => { | |
if (index < 10 && !currentIds.has(index.toString())) { | |
newHtml += ` | |
<div class="article-tile" data-published="${article.published}" data-id="${index}"> | |
${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : ` | |
<svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg"> | |
<rect width="100%" height="100%" fill="#e0e0e0"/> | |
<text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text> | |
</svg> | |
`} | |
<div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div> | |
<div class="description">${article.description}</div> | |
<div class="published">Published: ${article.published}</div> | |
</div> | |
`; | |
} | |
}); | |
// Append new articles, maintaining limit of 10 | |
if (newHtml) { | |
tilesDiv.innerHTML += newHtml; | |
const allArticles = Array.from(tilesDiv.querySelectorAll('.article-tile')); | |
allArticles.sort((a, b) => new Date(b.dataset.published) - new Date(a.dataset.published)); | |
tilesDiv.innerHTML = allArticles.slice(0, 10).map(a => a.outerHTML).join(''); | |
} | |
} | |
} | |
document.querySelector('.loading-message').style.display = 'none'; | |
} | |
setTimeout(updateArticles, 2000); // Check every 2 seconds | |
}) | |
.catch(error => { | |
console.error('Error updating articles:', error); | |
setTimeout(updateArticles, 2000); // Retry on error | |
}); | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
const tiles = document.querySelectorAll('.tiles'); | |
tiles.forEach(tile => { | |
lastUpdate = Math.max(lastUpdate, parseFloat(tile.dataset.lastUpdate) || 0); | |
}); | |
updateArticles(); | |
}); | |
</script> | |
{% endif %} | |
</body> | |
</html> |