Spaces:
Running
Running
File size: 7,225 Bytes
cf1fb4e 7e454f3 7b84ed4 40c26bb 7e454f3 40c26bb 7b84ed4 6c20801 5d4d8d0 6c20801 5d4d8d0 6c20801 7e454f3 6c20801 40c26bb 6c20801 7b84ed4 5d4d8d0 7b84ed4 5d4d8d0 dda464b 5d4d8d0 7b84ed4 5d4d8d0 7b84ed4 5d4d8d0 7b84ed4 dda464b 7b84ed4 5d4d8d0 7b84ed4 6c20801 40c26bb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
<!DOCTYPE html>
<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> |