broadfield-dev commited on
Commit
8eb8ed2
·
verified ·
1 Parent(s): f808465

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +154 -254
templates/index.html CHANGED
@@ -5,24 +5,49 @@
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; margin-right: 10px; }
12
  .search-button { padding: 10px 20px; border: none; border-radius: 25px; background-color: #3498db; color: white; cursor: pointer; font-size: 1em; }
13
  .search-button:hover { background-color: #2980b9; }
14
  .category-section { margin: 20px 0; }
15
- .category-title { background-color: #3498db; color: white; padding: 10px; border-radius: 5px; cursor: pointer; }
16
- .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
17
- .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); }
18
- .article-tile img, .article-tile svg { width: 100%; height: 150px; object-fit: cover; border-radius: 5px; }
19
- .title a { font-size: 1.3em; color: #2c3e50; text-decoration: none; font-weight: 600; text-transform: capitalize; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  .title a:hover { color: #3498db; }
21
- .description { color: #555; font-size: 0.9em; height: 150px; overflow-y: clip; }
22
- .published { font-size: 0.8em; color: #95a5a6; }
 
 
 
 
 
 
 
 
 
23
  .no-articles { text-align: center; color: #2c3e50; margin-top: 20px; }
24
  .loading-container { text-align: center; margin: 10px 0; }
25
- .loading-spinner { display: inline-block; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; }
26
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
27
  </style>
28
  </head>
@@ -30,29 +55,27 @@
30
  <h1>News Feed Hub</h1>
31
  <div class="search-container">
32
  <form method="POST" action="/search" id="searchForm">
33
- <input type="text" name="search" class="search-bar" placeholder="Search news...">
34
  <button type="submit" class="search-button">Search</button>
35
  </form>
36
- <button id="backButton" style="display: none; margin-top: 10px;">Back to Main</button>
37
  </div>
38
  {% if loading %}
39
  <div class="loading-container" id="loadingContainer">
40
- <span class="loading-spinner"></span>
41
  </div>
42
  {% endif %}
43
- {% if has_articles %}
44
- <div id="articlesContainer">
 
45
  {% for category, articles in categorized_articles.items() %}
46
  <div class="category-section">
47
  <div class="category-title" onclick="toggleCategory('{{ category }}')">{{ category }} <span class="loading-spinner" id="spinner-{{ category }}" style="display: none;"></span></div>
48
- <div class="tiles" id="category-{{ category }}" data-last-update="0">
49
- {% set seen_articles = [] %}
50
  {% for article in articles %}
51
- {% set article_key = article.title + '|' + article.link + '|' + article.published %}
52
- {% if article_key not in seen_articles %}
53
- <div class="article-tile" data-published="{{ article.published }}" data-id="{{ loop.index }}" data-key="{{ article_key }}">
54
  {% if article.image != "svg" %}
55
- <img src="{{ article.image }}" alt="Article Image">
56
  {% else %}
57
  <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
58
  <rect width="100%" height="100%" fill="#e0e0e0"/>
@@ -60,300 +83,177 @@
60
  </svg>
61
  {% endif %}
62
  <div class="title"><a href="{{ article.link }}" target="_blank">{{ article.title }}</a></div>
63
- <div class="description">{{ article.description }}</div>
64
  <div class="published">Published: {{ article.published }}</div>
65
  </div>
66
- {% set seen_articles = seen_articles + [article_key] %}
67
- {% endif %}
68
  {% endfor %}
69
  </div>
70
  <div class="tiles" id="all-{{ category }}" style="display: none;"></div>
71
  </div>
72
  {% endfor %}
73
- </div>
74
- {% else %}
75
- {% if not loading %}
76
- <div class="no-articles">No articles available yet. Loading new feeds...</div>
77
  {% endif %}
78
- {% endif %}
79
 
80
  <script>
81
  let lastUpdate = 0;
82
  let expandedCategories = new Set();
83
 
84
- // Restore expanded categories from session storage if available
85
  document.addEventListener('DOMContentLoaded', () => {
86
  const storedExpanded = sessionStorage.getItem('expandedCategories');
87
  if (storedExpanded) {
88
  expandedCategories = new Set(JSON.parse(storedExpanded));
89
  expandedCategories.forEach(category => {
90
- toggleCategory(category, false); // Expand without fetching
 
91
  });
92
  }
 
 
 
 
 
 
 
 
 
93
  });
94
 
95
  function getArticleKey(article) {
96
  return `${article.title}|${article.link}|${article.published}`;
97
  }
98
 
99
- function toggleCategory(category, fetchData = true) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  const spinner = document.getElementById(`spinner-${category}`);
101
  const tilesDiv = document.getElementById(`category-${category}`);
102
  const allTilesDiv = document.getElementById(`all-${category}`);
103
 
104
  if (expandedCategories.has(category)) {
105
- tilesDiv.style.display = 'grid';
106
  allTilesDiv.style.display = 'none';
107
  expandedCategories.delete(category);
108
  } else {
109
- if (fetchData) {
110
- spinner.style.display = 'inline-block';
111
- fetch(`/get_all_articles/${category}`)
112
- .then(response => response.json())
113
- .then(data => {
114
- spinner.style.display = 'none';
115
- if (data.articles.length > 0) {
116
- let html = '';
117
- const seenKeys = new Set();
118
- data.articles.sort((a, b) => new Date(b.published) - new Date(a.published));
119
- data.articles.forEach((article, index) => {
120
- const key = getArticleKey(article);
121
- if (!seenKeys.has(key)) {
122
- seenKeys.add(key);
123
- html += `
124
- <div class="article-tile" data-published="${article.published}" data-id="${index}" data-key="${key}">
125
- ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
126
- <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
127
- <rect width="100%" height="100%" fill="#e0e0e0"/>
128
- <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
129
- </svg>
130
- `}
131
- <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
132
- <div class="description">${article.description}</div>
133
- <div class="published">Published: ${article.published}</div>
134
- </div>
135
- `;
136
- } else {
137
- console.warn(`Duplicate article skipped in category ${category}: ${key}`);
138
- }
139
- });
140
- tilesDiv.style.display = 'none';
141
- allTilesDiv.innerHTML = html;
142
- allTilesDiv.style.display = 'grid';
143
- expandedCategories.add(category);
144
- } else {
145
- alert(`No additional articles found for ${category}.`);
146
- }
147
- })
148
- .catch(error => {
149
- spinner.style.display = 'none';
150
- console.error(`Error loading all articles for ${category}:`, error);
151
- alert(`Failed to load all articles for ${category}. Please try again.`);
152
- });
153
- } else {
154
- tilesDiv.style.display = 'none';
155
- allTilesDiv.style.display = 'grid';
156
- expandedCategories.add(category);
157
- }
158
  }
159
-
160
- // Save expanded categories to session storage
161
  sessionStorage.setItem('expandedCategories', JSON.stringify([...expandedCategories]));
162
  }
 
 
 
 
 
 
 
 
 
163
 
164
- function updateArticles() {
165
- fetch('/get_updates')
166
  .then(response => response.json())
167
  .then(data => {
168
- if (data.has_updates && data.last_update > lastUpdate) {
169
- lastUpdate = data.last_update;
170
- const newArticles = data.articles;
171
-
172
- // Store the current scroll position and expanded categories
173
- const scrollPosition = window.scrollY;
174
-
175
- for (const [category, articles] of Object.entries(newArticles)) {
176
- const tilesDiv = document.getElementById(`category-${category}`);
177
- const allTilesDiv = document.getElementById(`all-${category}`);
178
- if (tilesDiv) {
179
- const existingArticles = Array.from(tilesDiv.querySelectorAll('.article-tile'));
180
- let currentKeys = new Set(existingArticles.map(a => a.dataset.key));
181
- let newHtml = '';
182
- articles.sort((a, b) => new Date(b.published) - new Date(a.published));
183
- articles.slice(0, 10).forEach((article, index) => {
184
- const key = getArticleKey(article);
185
- if (!currentKeys.has(key)) {
186
- newHtml += `
187
- <div class="article-tile" data-published="${article.published}" data-id="${index}" data-key="${key}">
188
- ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
189
- <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
190
- <rect width="100%" height="100%" fill="#e0e0e0"/>
191
- <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
192
- </svg>
193
- `}
194
- <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
195
- <div class="description">${article.description}</div>
196
- <div class="published">Published: ${article.published}</div>
197
- </div>
198
- `;
199
- } else {
200
- console.warn(`Duplicate article skipped in update for ${category}: ${key}`);
201
- }
202
- });
203
-
204
- if (newHtml) {
205
- tilesDiv.innerHTML = (existingArticles.map(a => a.outerHTML).join('') + newHtml);
206
- const allLimited = Array.from(tilesDiv.querySelectorAll('.article-tile'));
207
- tilesDiv.innerHTML = allLimited
208
- .sort((a, b) => new Date(b.dataset.published) - new Date(a.dataset.published))
209
- .slice(0, 10)
210
- .map(a => a.outerHTML)
211
- .join('');
212
- }
213
-
214
- if (allTilesDiv.style.display === 'grid') {
215
- let allNewHtml = '';
216
- const allSeenKeys = new Set(Array.from(allTilesDiv.querySelectorAll('.article-tile')).map(a => a.dataset.key));
217
- articles.forEach((article, index) => {
218
- const key = getArticleKey(article);
219
- if (!allSeenKeys.has(key)) {
220
- allSeenKeys.add(key);
221
- allNewHtml += `
222
- <div class="article-tile" data-published="${article.published}" data-id="${index}" data-key="${key}">
223
- ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
224
- <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
225
- <rect width="100%" height="100%" fill="#e0e0e0"/>
226
- <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
227
- </svg>
228
- `}
229
- <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
230
- <div class="description">${article.description}</div>
231
- <div class="published">Published: ${article.published}</div>
232
- </div>
233
- `;
234
- } else {
235
- console.warn(`Duplicate article skipped in expanded view for ${category}: ${key}`);
236
- }
237
- });
238
- allTilesDiv.innerHTML = (Array.from(allTilesDiv.querySelectorAll('.article-tile')).map(a => a.outerHTML).join('') + allNewHtml);
239
- const allExpanded = Array.from(allTilesDiv.querySelectorAll('.article-tile'));
240
- allTilesDiv.innerHTML = allExpanded
241
- .sort((a, b) => new Date(b.dataset.published) - new Date(a.dataset.published))
242
- .map(a => a.outerHTML)
243
- .join('');
244
- }
245
- }
246
  }
247
-
248
- // Restore scroll position
249
- window.scrollTo(0, scrollPosition);
250
  }
251
-
252
- // Schedule the next update
253
- setTimeout(updateArticles, 30000); // Poll every 30 seconds
254
  })
255
  .catch(error => {
256
- console.error('Error updating articles:', error);
257
- setTimeout(updateArticles, 30000);
 
258
  });
259
  }
260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  function checkLoadingStatus() {
262
  fetch('/check_loading')
263
  .then(response => response.json())
264
  .then(data => {
265
  if (data.status === 'complete') {
266
  const loadingContainer = document.getElementById('loadingContainer');
267
- if (loadingContainer) {
268
- loadingContainer.style.display = 'none';
269
- }
270
- updateArticles(); // Start polling for updates
271
  } else {
272
  setTimeout(checkLoadingStatus, 2000);
273
  }
274
  })
275
  .catch(error => {
276
  console.error('Error checking loading status:', error);
277
- setTimeout(checkLoadingStatus, 2000);
278
  });
279
  }
280
-
281
- document.addEventListener('DOMContentLoaded', () => {
282
- const tiles = document.querySelectorAll('.tiles');
283
- tiles.forEach(tile => {
284
- lastUpdate = Math.max(lastUpdate, parseFloat(tile.dataset.lastUpdate) || 0);
285
- });
286
-
287
- // Start checking loading status if loading is active
288
- if (document.getElementById('loadingContainer')) {
289
- checkLoadingStatus();
290
- }
291
-
292
- const form = document.getElementById('searchForm');
293
- const backButton = document.getElementById('backButton');
294
- form.addEventListener('submit', (event) => {
295
- event.preventDefault();
296
- fetch('/search', {
297
- method: 'POST',
298
- body: new FormData(form)
299
- })
300
- .then(response => {
301
- if (!response.ok) throw new Error('Network response was not ok');
302
- return response.json();
303
- })
304
- .then(data => {
305
- if (data.has_articles) {
306
- backButton.style.display = 'block';
307
- const existingContent = document.querySelectorAll('.category-section');
308
- existingContent.forEach(section => section.remove());
309
- const categoriesHtml = Object.entries(data.categorized_articles).map(([cat, articles]) => {
310
- const seenKeys = new Set();
311
- const uniqueArticles = articles.filter(article => {
312
- const key = getArticleKey(article);
313
- if (seenKeys.has(key)) {
314
- console.warn(`Duplicate article skipped in search for ${cat}: ${key}`);
315
- return false;
316
- }
317
- seenKeys.add(key);
318
- return true;
319
- });
320
- return `
321
- <div class="category-section">
322
- <div class="category-title" onclick="toggleCategory('${cat}')">${cat} <span class="loading-spinner" id="spinner-${cat}" style="display: none;"></span></div>
323
- <div class="tiles" id="category-${cat}" data-last-update="0">
324
- ${uniqueArticles.map((article, index) => `
325
- <div class="article-tile" data-published="${article.published}" data-id="${index}" data-key="${getArticleKey(article)}">
326
- ${article.image !== "svg" ? `<img src="${article.image}" alt="Article Image">` : `
327
- <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
328
- <rect width="100%" height="100%" fill="#e0e0e0"/>
329
- <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
330
- </svg>
331
- `}
332
- <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
333
- <div class="description">${article.description}</div>
334
- <div class="published">Published: ${article.published}</div>
335
- </div>
336
- `).join('')}
337
- </div>
338
- <div class="tiles" id="all-${cat}" style="display: none;"></div>
339
- </div>
340
- `;
341
- }).join('');
342
- document.querySelector('.search-container').insertAdjacentHTML('afterend', categoriesHtml);
343
- } else {
344
- alert('No results found.');
345
- }
346
- })
347
- .catch(error => {
348
- console.error('Error performing search:', error);
349
- alert('Failed to perform search. Please try again.');
350
- });
351
- });
352
-
353
- backButton.addEventListener('click', () => {
354
- window.location.href = '/';
355
- });
356
- });
357
  </script>
358
  </body>
359
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>News Feed Hub</title>
7
  <style>
8
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, 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; margin-right: 10px; font-size: 1em; }
12
  .search-button { padding: 10px 20px; border: none; border-radius: 25px; background-color: #3498db; color: white; cursor: pointer; font-size: 1em; }
13
  .search-button:hover { background-color: #2980b9; }
14
  .category-section { margin: 20px 0; }
15
+ .category-title { background-color: #3498db; color: white; padding: 10px; border-radius: 5px; cursor: pointer; user-select: none; }
16
+ .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; margin-top: 15px; }
17
+ .article-tile {
18
+ background: white;
19
+ height: 380px;
20
+ padding: 15px;
21
+ border-radius: 8px;
22
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
23
+ display: flex;
24
+ flex-direction: column;
25
+ overflow: hidden;
26
+ }
27
+ .article-tile img, .article-tile svg {
28
+ width: 100%;
29
+ height: 150px;
30
+ object-fit: cover;
31
+ border-radius: 5px;
32
+ flex-shrink: 0;
33
+ }
34
+ .title { margin-top: 10px; }
35
+ .title a { font-size: 1.2em; color: #2c3e50; text-decoration: none; font-weight: 600; }
36
  .title a:hover { color: #3498db; }
37
+ .description {
38
+ color: #555;
39
+ font-size: 0.9em;
40
+ flex-grow: 1;
41
+ overflow-y: auto;
42
+ margin-top: 8px;
43
+ word-wrap: break-word;
44
+ }
45
+ .description p { margin: 0 0 0.5em 0; }
46
+ .description a { color: #3498db; }
47
+ .published { font-size: 0.8em; color: #95a5a6; margin-top: 8px; flex-shrink: 0; }
48
  .no-articles { text-align: center; color: #2c3e50; margin-top: 20px; }
49
  .loading-container { text-align: center; margin: 10px 0; }
50
+ .loading-spinner { display: inline-block; vertical-align: middle; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; }
51
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
52
  </style>
53
  </head>
 
55
  <h1>News Feed Hub</h1>
56
  <div class="search-container">
57
  <form method="POST" action="/search" id="searchForm">
58
+ <input type="text" name="search" class="search-bar" placeholder="Search semantically...">
59
  <button type="submit" class="search-button">Search</button>
60
  </form>
61
+ <button id="backButton" style="display: none; margin-top: 10px;" class="search-button">Back to Main</button>
62
  </div>
63
  {% if loading %}
64
  <div class="loading-container" id="loadingContainer">
65
+ <div class="loading-spinner"></div>
66
  </div>
67
  {% endif %}
68
+
69
+ <div id="contentContainer">
70
+ {% if has_articles %}
71
  {% for category, articles in categorized_articles.items() %}
72
  <div class="category-section">
73
  <div class="category-title" onclick="toggleCategory('{{ category }}')">{{ category }} <span class="loading-spinner" id="spinner-{{ category }}" style="display: none;"></span></div>
74
+ <div class="tiles" id="category-{{ category }}">
 
75
  {% for article in articles %}
76
+ <div class="article-tile" data-published="{{ article.published }}" data-key="{{ article.title + '|' + article.link + '|' + article.published }}">
 
 
77
  {% if article.image != "svg" %}
78
+ <img src="{{ article.image }}" alt="Article Image" onerror="this.style.display='none';">
79
  {% else %}
80
  <svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
81
  <rect width="100%" height="100%" fill="#e0e0e0"/>
 
83
  </svg>
84
  {% endif %}
85
  <div class="title"><a href="{{ article.link }}" target="_blank">{{ article.title }}</a></div>
86
+ <div class="description">{{ article.description | safe }}</div>
87
  <div class="published">Published: {{ article.published }}</div>
88
  </div>
 
 
89
  {% endfor %}
90
  </div>
91
  <div class="tiles" id="all-{{ category }}" style="display: none;"></div>
92
  </div>
93
  {% endfor %}
94
+ {% else %}
95
+ {% if not loading %}
96
+ <div class="no-articles">No articles available yet. Loading new feeds...</div>
97
+ {% endif %}
98
  {% endif %}
99
+ </div>
100
 
101
  <script>
102
  let lastUpdate = 0;
103
  let expandedCategories = new Set();
104
 
 
105
  document.addEventListener('DOMContentLoaded', () => {
106
  const storedExpanded = sessionStorage.getItem('expandedCategories');
107
  if (storedExpanded) {
108
  expandedCategories = new Set(JSON.parse(storedExpanded));
109
  expandedCategories.forEach(category => {
110
+ const allTilesDiv = document.getElementById(`all-${category}`);
111
+ if (allTilesDiv) allTilesDiv.style.display = 'grid';
112
  });
113
  }
114
+
115
+ if (document.getElementById('loadingContainer')) {
116
+ checkLoadingStatus();
117
+ }
118
+
119
+ document.getElementById('searchForm').addEventListener('submit', handleSearch);
120
+ document.getElementById('backButton').addEventListener('click', () => {
121
+ window.location.href = '/';
122
+ });
123
  });
124
 
125
  function getArticleKey(article) {
126
  return `${article.title}|${article.link}|${article.published}`;
127
  }
128
 
129
+ function createArticleTileHTML(article) {
130
+ const key = getArticleKey(article);
131
+ const imageHTML = article.image !== "svg"
132
+ ? `<img src="${article.image}" alt="Article Image" onerror="this.style.display='none';">`
133
+ : `<svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
134
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
135
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
136
+ </svg>`;
137
+
138
+ return `
139
+ <div class="article-tile" data-published="${article.published}" data-key="${key}">
140
+ ${imageHTML}
141
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
142
+ <div class="description">${article.description}</div>
143
+ <div class="published">Published: ${article.published}</div>
144
+ </div>`;
145
+ }
146
+
147
+ function renderArticles(container, articles) {
148
+ container.innerHTML = articles.map(createArticleTileHTML).join('');
149
+ }
150
+
151
+ function toggleCategory(category) {
152
  const spinner = document.getElementById(`spinner-${category}`);
153
  const tilesDiv = document.getElementById(`category-${category}`);
154
  const allTilesDiv = document.getElementById(`all-${category}`);
155
 
156
  if (expandedCategories.has(category)) {
 
157
  allTilesDiv.style.display = 'none';
158
  expandedCategories.delete(category);
159
  } else {
160
+ spinner.style.display = 'inline-block';
161
+ fetch(`/get_all_articles/${category}`)
162
+ .then(response => response.json())
163
+ .then(data => {
164
+ spinner.style.display = 'none';
165
+ if (data.articles && data.articles.length > 0) {
166
+ renderArticles(allTilesDiv, data.articles);
167
+ allTilesDiv.style.display = 'grid';
168
+ expandedCategories.add(category);
169
+ } else {
170
+ alert(`No additional articles found for ${category}.`);
171
+ }
172
+ })
173
+ .catch(error => {
174
+ spinner.style.display = 'none';
175
+ console.error(`Error loading all articles for ${category}:`, error);
176
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
 
 
178
  sessionStorage.setItem('expandedCategories', JSON.stringify([...expandedCategories]));
179
  }
180
+
181
+ function handleSearch(event) {
182
+ event.preventDefault();
183
+ const form = event.target;
184
+ const backButton = document.getElementById('backButton');
185
+ const contentContainer = document.getElementById('contentContainer');
186
+ const loadingContainer = document.getElementById('loadingContainer');
187
+ if(loadingContainer) loadingContainer.style.display = 'block';
188
+ contentContainer.innerHTML = '';
189
 
190
+
191
+ fetch('/search', { method: 'POST', body: new FormData(form) })
192
  .then(response => response.json())
193
  .then(data => {
194
+ if(loadingContainer) loadingContainer.style.display = 'none';
195
+ if (data.has_articles) {
196
+ backButton.style.display = 'block';
197
+ let categoriesHtml = '';
198
+ const sortedCategories = Object.keys(data.categorized_articles).sort();
199
+
200
+ for(const category of sortedCategories) {
201
+ const articles = data.categorized_articles[category];
202
+ categoriesHtml += `
203
+ <div class="category-section">
204
+ <div class="category-title">${category} (${articles.length})</div>
205
+ <div class="tiles">
206
+ ${articles.map(createArticleTileHTML).join('')}
207
+ </div>
208
+ </div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
210
+ contentContainer.innerHTML = categoriesHtml;
211
+ } else {
212
+ contentContainer.innerHTML = `<div class="no-articles">No results found.</div>`;
213
  }
 
 
 
214
  })
215
  .catch(error => {
216
+ if(loadingContainer) loadingContainer.style.display = 'none';
217
+ console.error('Error performing search:', error);
218
+ contentContainer.innerHTML = `<div class="no-articles">Failed to perform search. Please try again.</div>`;
219
  });
220
  }
221
 
222
+ function updateArticles() {
223
+ fetch('/get_updates')
224
+ .then(response => response.json())
225
+ .then(data => {
226
+ if (data.has_updates) {
227
+ // For simplicity, a full refresh is the most reliable way to update
228
+ // without complex DOM manipulation and state management.
229
+ if (!document.querySelector('.search-bar').value) { // Only refresh if not in search view
230
+ console.log("New content available. Refreshing page.");
231
+ window.location.reload();
232
+ }
233
+ }
234
+ })
235
+ .catch(error => console.error('Error checking for updates:', error))
236
+ .finally(() => setTimeout(updateArticles, 60000)); // Poll every 60 seconds
237
+ }
238
+
239
  function checkLoadingStatus() {
240
  fetch('/check_loading')
241
  .then(response => response.json())
242
  .then(data => {
243
  if (data.status === 'complete') {
244
  const loadingContainer = document.getElementById('loadingContainer');
245
+ if (loadingContainer) loadingContainer.style.display = 'none';
246
+ // Start polling for updates only after initial load is complete
247
+ setTimeout(updateArticles, 5000);
 
248
  } else {
249
  setTimeout(checkLoadingStatus, 2000);
250
  }
251
  })
252
  .catch(error => {
253
  console.error('Error checking loading status:', error);
254
+ setTimeout(checkLoadingStatus, 5000);
255
  });
256
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  </script>
258
  </body>
259
  </html>