broadfield-dev commited on
Commit
1b3ff93
·
verified ·
1 Parent(s): ee89208

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +257 -0
templates/index.html ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>News Feed Hub</title>
7
+ <style>
8
+ body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; color: #333; }
9
+ h1 { text-align: center; color: #2c3e50; }
10
+ .search-container { text-align: center; margin: 20px 0; }
11
+ .search-bar { width: 50%; padding: 10px; border: 2px solid #3498db; border-radius: 25px; }
12
+ .category-section { margin: 20px 0; }
13
+ .category-title { background-color: #3498db; color: white; padding: 10px; border-radius: 5px; cursor: pointer; }
14
+ .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
15
+ .article-tile { background: white; height: 350px; overflow-y: clip; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
16
+ .article-tile img, .article-tile svg { width: 100%; height: 150px; object-fit: cover; border-radius: 5px; }
17
+ .title a { font-size: 1.1em; color: #2c3e50; text-decoration: none; }
18
+ .title a:hover { color: #3498db; }
19
+ .description { color: #555; font-size: 0.9em; height: 150px; overflow-y: clip; }
20
+ .published { font-size: 0.8em; color: #95a5a6; }
21
+ .no-articles { text-align: center; color: #2c3e50; margin-top: 20px; }
22
+ .loading-message { text-align: center; color: #3498db; margin: 10px 0; }
23
+ .loading-spinner { display: none; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; margin-left: 10px; }
24
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ <h1>News Feed Hub</h1>
29
+ <div class="search-container">
30
+ <form method="POST" action="/search" id="searchForm">
31
+ <input type="text" name="search" class="search-bar" placeholder="Search news...">
32
+ </form>
33
+ <button id="backButton" style="display: none; margin-top: 10px;" onclick="window.location.href='/back_to_main'">Back to Main</button>
34
+ </div>
35
+ {% if loading %}
36
+ <div class="loading-message">Fetching new RSS feeds in the background...</div>
37
+ {% endif %}
38
+ {% if has_articles %}
39
+ {% for category, articles in categorized_articles.items() %}
40
+ <div class="category-section">
41
+ <div class="category-title" onclick="toggleCategory('{{ category }}')">{{ category }} <span class="loading-spinner" id="spinner-{{ category }}"></span></div>
42
+ <div class="tiles" id="category-{{ category }}" data-last-update="0">
43
+ {% for article in articles %}
44
+ <div class="article-tile" data-published="{{ article.published }}" data-id="{{ loop.index }}">
45
+ {% if article.image != "svg" %}
46
+ <img src="{{ article.image }}" alt="Article Image">
47
+ {% else %}
48
+ <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
49
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
50
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
51
+ </svg>
52
+ {% endif %}
53
+ <div class="title"><a href="{{ article.link }}" target="_blank">{{ article.title }}</a></div>
54
+ <div class="description">{{ article.description }}</div>
55
+ <div class="published">Published: {{ article.published }}</div>
56
+ </div>
57
+ {% endfor %}
58
+ </div>
59
+ <div class="tiles" id="all-{{ category }}" style="display: none;"></div>
60
+ </div>
61
+ {% endfor %}
62
+ {% else %}
63
+ <div class="no-articles">No articles available yet. Loading new feeds...</div>
64
+ {% endif %}
65
+
66
+ {% if loading %}
67
+ <script>
68
+ let lastUpdate = 0;
69
+ let expandedCategories = new Set();
70
+ function toggleCategory(category) {
71
+ const spinner = document.getElementById(`spinner-${category}`);
72
+ const tilesDiv = document.getElementById(`category-${category}`);
73
+ const allTilesDiv = document.getElementById(`all-${category}`);
74
+ if (expandedCategories.has(category)) {
75
+ // Collapse: show only 10 articles
76
+ tilesDiv.style.display = 'grid';
77
+ allTilesDiv.style.display = 'none';
78
+ expandedCategories.delete(category);
79
+ } else {
80
+ // Expand: load all articles
81
+ spinner.style.display = 'inline-block';
82
+ fetch(`/get_all_articles/${category}`)
83
+ .then(response => response.json())
84
+ .then(data => {
85
+ spinner.style.display = 'none';
86
+ if (data.articles.length > 0) {
87
+ let html = '';
88
+ data.articles.sort((a, b) => new Date(b.published) - new Date(a.published));
89
+ data.articles.forEach((article, index) => {
90
+ html += `
91
+ <div class="article-tile" data-published="${article.published}" data-id="${index}">
92
+ ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
93
+ <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
94
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
95
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
96
+ </svg>
97
+ `}
98
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
99
+ <div class="description">${article.description}</div>
100
+ <div class="published">Published: ${article.published}</div>
101
+ </div>
102
+ `;
103
+ });
104
+ tilesDiv.style.display = 'none';
105
+ allTilesDiv.innerHTML = html;
106
+ allTilesDiv.style.display = 'grid';
107
+ expandedCategories.add(category);
108
+ } else {
109
+ alert(`No additional articles found for ${category}.`);
110
+ }
111
+ })
112
+ .catch(error => {
113
+ spinner.style.display = 'none';
114
+ console.error(`Error loading all articles for ${category}:`, error);
115
+ alert(`Failed to load all articles for ${category}. Please try again.`);
116
+ });
117
+ }
118
+ }
119
+ function updateArticles() {
120
+ fetch('/get_updates')
121
+ .then(response => response.json())
122
+ .then(data => {
123
+ if (data.articles && data.last_update > lastUpdate) {
124
+ lastUpdate = data.last_update;
125
+ const newArticles = data.articles;
126
+ for (const [category, articles] of Object.entries(newArticles)) {
127
+ const tilesDiv = document.getElementById(`category-${category}`);
128
+ const allTilesDiv = document.getElementById(`all-${category}`);
129
+ if (tilesDiv) {
130
+ const existingArticles = Array.from(tilesDiv.querySelectorAll('.article-tile'));
131
+ let currentIds = new Set(existingArticles.map(a => a.dataset.id));
132
+ let newHtml = '';
133
+ // Sort new articles by published date
134
+ articles.sort((a, b) => new Date(b.published) - new Date(a.published));
135
+ // Add new articles to limited view (up to 10, keeping most recent)
136
+ articles.slice(0, 10).forEach((article, index) => {
137
+ if (!currentIds.has(index.toString())) {
138
+ newHtml += `
139
+ <div class="article-tile" data-published="${article.published}" data-id="${index}">
140
+ ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
141
+ <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
142
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
143
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
144
+ </svg>
145
+ `}
146
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
147
+ <div class="description">${article.description}</div>
148
+ <div class="published">Published: ${article.published}</div>
149
+ </div>
150
+ `;
151
+ }
152
+ });
153
+ // Update limited view (up to 10)
154
+ if (newHtml) {
155
+ tilesDiv.innerHTML = (existingArticles.map(a => a.outerHTML).join('') + newHtml);
156
+ const allLimited = Array.from(tilesDiv.querySelectorAll('.article-tile'));
157
+ tilesDiv.innerHTML = allLimited.sort((a, b) => new Date(b.dataset.published) - new Date(a.dataset.published)).slice(0, 10).map(a => a.outerHTML).join('');
158
+ }
159
+ // Update expanded view if visible
160
+ if (allTilesDiv.style.display === 'grid') {
161
+ let allNewHtml = '';
162
+ articles.forEach((article, index) => {
163
+ allNewHtml += `
164
+ <div class="article-tile" data-published="${article.published}" data-id="${index}">
165
+ ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
166
+ <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
167
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
168
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
169
+ </svg>
170
+ `}
171
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
172
+ <div class="description">${article.description}</div>
173
+ <div class="published">Published: ${article.published}</div>
174
+ </div>
175
+ `;
176
+ });
177
+ allTilesDiv.innerHTML = (Array.from(allTilesDiv.querySelectorAll('.article-tile')).map(a => a.outerHTML).join('') + allNewHtml);
178
+ const allExpanded = Array.from(allTilesDiv.querySelectorAll('.article-tile'));
179
+ allTilesDiv.innerHTML = allExpanded.sort((a, b) => new Date(b.dataset.published) - new Date(a.dataset.published)).map(a => a.outerHTML).join('');
180
+ }
181
+ }
182
+ }
183
+ document.querySelector('.loading-message').style.display = 'none';
184
+ }
185
+ setTimeout(updateArticles, 2000); // Check every 2 seconds
186
+ })
187
+ .catch(error => {
188
+ console.error('Error updating articles:', error);
189
+ setTimeout(updateArticles, 2000); // Retry on error
190
+ });
191
+ }
192
+ document.addEventListener('DOMContentLoaded', () => {
193
+ const tiles = document.querySelectorAll('.tiles');
194
+ tiles.forEach(tile => {
195
+ lastUpdate = Math.max(lastUpdate, parseFloat(tile.dataset.lastUpdate) || 0);
196
+ });
197
+ updateArticles();
198
+ // Handle semantic search
199
+ const form = document.getElementById('searchForm');
200
+ const backButton = document.getElementById('backButton');
201
+ form.addEventListener('submit', (event) => {
202
+ event.preventDefault();
203
+ fetch('/search', {
204
+ method: 'POST',
205
+ body: new FormData(form)
206
+ })
207
+ .then(response => response.json())
208
+ .then(data => {
209
+ if (data.has_articles) {
210
+ document.querySelector('.search-container').style.display = 'none';
211
+ backButton.style.display = 'block';
212
+ const categoriesHtml = Object.entries(data.categorized_articles).map(([cat, articles]) => `
213
+ <div class="category-section">
214
+ <div class="category-title" onclick="toggleCategory('${cat}')">${cat} <span class="loading-spinner" id="spinner-${cat}"></span></div>
215
+ <div class="tiles" id="category-${cat}" data-last-update="0">
216
+ ${articles.map((article, index) => `
217
+ <div class="article-tile" data-published="${article.published}" data-id="${index}">
218
+ ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
219
+ <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
220
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
221
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
222
+ </svg>
223
+ `}
224
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
225
+ <div class="description">${article.description}</div>
226
+ <div class="published">Published: ${article.published}</div>
227
+ </div>
228
+ `).join('')}
229
+ </div>
230
+ <div class="tiles" id="all-${cat}" style="display: none;"></div>
231
+ </div>
232
+ `).join('');
233
+ document.querySelector('body').innerHTML = `
234
+ <h1>News Feed Hub</h1>
235
+ <div class="search-container" style="display: none;">
236
+ <form method="POST" action="/search" id="searchForm">
237
+ <input type="text" name="search" class="search-bar" placeholder="Search news...">
238
+ </form>
239
+ <button id="backButton" style="display: block; margin-top: 10px;" onclick="window.location.href='/back_to_main'">Back to Main</button>
240
+ </div>
241
+ ${categoriesHtml}
242
+ ${loading ? '<div class="loading-message">Fetching new RSS feeds in the background...</div>' : ''}
243
+ `;
244
+ } else {
245
+ alert('No results found.');
246
+ }
247
+ })
248
+ .catch(error => {
249
+ console.error('Error performing search:', error);
250
+ alert('Failed to perform search. Please try again.');
251
+ });
252
+ });
253
+ });
254
+ </script>
255
+ {% endif %}
256
+ </body>
257
+ </html>