broadfield-dev commited on
Commit
adfeb4f
·
verified ·
1 Parent(s): 9aa5d32

Create index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +308 -0
templates/index.html ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: -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
+ .container { display: flex; }
11
+ .sidebar { width: 200px; background-color: #2c3e50; color: white; padding: 15px; border-radius: 5px; min-height: calc(100vh - 100px); }
12
+ .sidebar h3 { margin: 0 0 15px; font-size: 1.2em; }
13
+ .sidebar ul { list-style: none; padding: 0; }
14
+ .sidebar li { padding: 10px; cursor: pointer; border-radius: 5px; margin-bottom: 5px; }
15
+ .sidebar li:hover { background-color: #34495e; }
16
+ .sidebar li.active { background-color: #3498db; }
17
+ .content { flex-grow: 1; padding-left: 20px; }
18
+ .search-container { text-align: center; margin: 20px 0; }
19
+ .search-bar { width: 50%; padding: 10px; border: 2px solid #3498db; border-radius: 25px; margin-right: 10px; font-size: 1em; }
20
+ .search-button { padding: 10px 20px; border: none; border-radius: 25px; background-color: #3498db; color: white; cursor: pointer; font-size: 1em; }
21
+ .search-button:hover { background-color: #2980b9; }
22
+ .category-section { margin: 20px 0; }
23
+ .category-title { background-color: #3498db; color: white; padding: 10px; border-radius: 5px; cursor: pointer; user-select: none; }
24
+ .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; margin-top: 15px; }
25
+ .article-tile {
26
+ background: white;
27
+ height: 380px;
28
+ padding: 15px;
29
+ border-radius: 8px;
30
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
31
+ display: flex;
32
+ flex-direction: column;
33
+ overflow: hidden;
34
+ }
35
+ .article-tile img, .article-tile svg {
36
+ width: 100%;
37
+ height: 150px;
38
+ object-fit: cover;
39
+ border-radius: 5px;
40
+ flex-shrink: 0;
41
+ }
42
+ .title { margin-top: 10px; }
43
+ .title a { font-size: 1.2em; color: #2c3e50; text-decoration: none; font-weight: 600; }
44
+ .title a:hover { color: #3498db; }
45
+ .description {
46
+ color: #555;
47
+ font-size: 0.9em;
48
+ flex-grow: 1;
49
+ overflow-y: auto;
50
+ margin-top: 8px;
51
+ word-wrap: break-word;
52
+ }
53
+ .description p { margin: 0 0 0.5em 0; }
54
+ .description a { color: #3498db; }
55
+ .published { font-size: 0.8em; color: #95a5a6; margin-top: 8px; flex-shrink: 0; }
56
+ .no-articles { text-align: center; color: #2c3e50; margin-top: 20px; }
57
+ .loading-container { text-align: center; margin: 10px 0; }
58
+ .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; }
59
+ .pagination { text-align: center; margin: 20px 0; }
60
+ .pagination button { padding: 8px 12px; margin: 0 5px; border: none; border-radius: 5px; background-color: #3498db; color: white; cursor: pointer; }
61
+ .pagination button:disabled { background-color: #95a5a6; cursor: not-allowed; }
62
+ .pagination span { margin: 0 10px; }
63
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <h1>News Feed Hub</h1>
68
+ <div class="search-container">
69
+ <form method="POST" action="/search" id="searchForm">
70
+ <input type="text" name="search" class="search-bar" placeholder="Search semantically...">
71
+ <button type="submit" class="search-button">Search</button>
72
+ </form>
73
+ <button id="backButton" style="display: none; margin-top: 10px;" class="search-button">Back to Main</button>
74
+ </div>
75
+ {% if loading %}
76
+ <div class="loading-container" id="loadingContainer">
77
+ <div class="loading-spinner"></div>
78
+ </div>
79
+ {% endif %}
80
+ <div class="container">
81
+ <div class="sidebar">
82
+ <h3>Categories</h3>
83
+ <ul>
84
+ {% for category in categories %}
85
+ <li onclick="showCategory('{{ category }}')" id="sidebar-{{ category }}">{{ category }}</li>
86
+ {% endfor %}
87
+ </ul>
88
+ </div>
89
+ <div class="content">
90
+ <div id="contentContainer">
91
+ {% if has_articles %}
92
+ {% for category, articles in categorized_articles.items() %}
93
+ <div class="category-section" id="section-{{ category }}" style="display: none;">
94
+ <div class="category-title">{{ category }} <span class="loading-spinner" id="spinner-{{ category }}" style="display: none;"></span></div>
95
+ <div class="tiles" id="category-{{ category }}"></div>
96
+ <div class="pagination" id="pagination-{{ category }}">
97
+ <button onclick="changePage('{{ category }}', -1)">Previous</button>
98
+ <span id="page-info-{{ category }}">Page 1</span>
99
+ <button onclick="changePage('{{ category }}', 1)">Next</button>
100
+ </div>
101
+ </div>
102
+ {% endfor %}
103
+ {% else %}
104
+ {% if not loading %}
105
+ <div class="no-articles">No articles available yet. Loading new feeds...</div>
106
+ {% endif %}
107
+ {% endif %}
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <script>
113
+ let lastUpdate = 0;
114
+ let currentCategory = null;
115
+ let currentPages = {};
116
+ let articlesPerPage = 9;
117
+ let allArticles = {};
118
+
119
+ document.addEventListener('DOMContentLoaded', () => {
120
+ const storedCategory = sessionStorage.getItem('currentCategory');
121
+ if (storedCategory) {
122
+ showCategory(storedCategory);
123
+ } else if (document.querySelector('.sidebar li')) {
124
+ showCategory(document.querySelector('.sidebar li').textContent);
125
+ }
126
+
127
+ if (document.getElementById('loadingContainer')) {
128
+ checkLoadingStatus();
129
+ }
130
+
131
+ document.getElementById('searchForm').addEventListener('submit', handleSearch);
132
+ document.getElementById('backButton').addEventListener('click', () => {
133
+ window.location.href = '/';
134
+ });
135
+ });
136
+
137
+ function getArticleKey(article) {
138
+ return `${article.title}|${article.link}|${article.published}`;
139
+ }
140
+
141
+ function createArticleTileHTML(article) {
142
+ const key = getArticleKey(article);
143
+ const imageHTML = article.image !== "svg"
144
+ ? `<img src="${article.image}" alt="Article Image" onerror="this.style.display='none';">`
145
+ : `<svg width="100%" height="150" xmlns="http://www.w3.org/2000/svg">
146
+ <rect width="100%" height="100%" fill="#e0e0e0"/>
147
+ <text x="50%" y="50%" text-anchor="middle" dy=".3em" fill="#666">No Image</text>
148
+ </svg>`;
149
+
150
+ return `
151
+ <div class="article-tile" data-published="${article.published}" data-key="${key}">
152
+ ${imageHTML}
153
+ <div class="title"><a href="${article.link}" target="_blank">${article.title}</a></div>
154
+ <div class="description">${article.description}</div>
155
+ <div class="published">Published: ${article.published}</div>
156
+ </div>`;
157
+ }
158
+
159
+ function renderArticles(container, articles, category, page) {
160
+ const start = (page - 1) * articlesPerPage;
161
+ const end = start + articlesPerPage;
162
+ const paginatedArticles = articles.slice(start, end);
163
+ container.innerHTML = paginatedArticles.map(createArticleTileHTML).join('');
164
+ updatePagination(category, page, articles.length);
165
+ }
166
+
167
+ function updatePagination(category, page, totalArticles) {
168
+ const totalPages = Math.ceil(totalArticles / articlesPerPage);
169
+ const pageInfo = document.getElementById(`page-info-${category}`);
170
+ const prevButton = document.getElementById(`pagination-${category}`).querySelector('button:first-child');
171
+ const nextButton = document.getElementById(`pagination-${category}`).querySelector('button:last-child');
172
+ pageInfo.textContent = `Page ${page} of ${totalPages}`;
173
+ prevButton.disabled = page === 1;
174
+ nextButton.disabled = page === totalPages;
175
+ }
176
+
177
+ function showCategory(category) {
178
+ if (currentCategory === category) return;
179
+ currentCategory = category;
180
+ sessionStorage.setItem('currentCategory', category);
181
+
182
+ // Update sidebar active state
183
+ document.querySelectorAll('.sidebar li').forEach(li => {
184
+ li.classList.remove('active');
185
+ if (li.textContent === category) li.classList.add('active');
186
+ });
187
+
188
+ // Hide all category sections
189
+ document.querySelectorAll('.category-section').forEach(section => {
190
+ section.style.display = 'none';
191
+ });
192
+
193
+ // Show selected category section
194
+ const section = document.getElementById(`section-${category}`);
195
+ section.style.display = 'block';
196
+ const spinner = document.getElementById(`spinner-${category}`);
197
+ const tilesDiv = document.getElementById(`category-${category}`);
198
+
199
+ // Initialize page if not set
200
+ if (!currentPages[category]) {
201
+ currentPages[category] = 1;
202
+ }
203
+
204
+ // Fetch articles if not already cached
205
+ if (!allArticles[category]) {
206
+ spinner.style.display = 'inline-block';
207
+ fetch(`/get_all_articles/${category}`)
208
+ .then(response => response.json())
209
+ .then(data => {
210
+ spinner.style.display = 'none';
211
+ if (data.articles && data.articles.length > 0) {
212
+ allArticles[category] = data.articles;
213
+ renderArticles(tilesDiv, data.articles, category, currentPages[category]);
214
+ } else {
215
+ tilesDiv.innerHTML = `<div class="no-articles">No articles found for ${category}.</div>`;
216
+ }
217
+ })
218
+ .catch(error => {
219
+ spinner.style.display = 'none';
220
+ console.error(`Error loading articles for ${category}:`, error);
221
+ tilesDiv.innerHTML = `<div class="no-articles">Failed to load articles for ${category}.</div>`;
222
+ });
223
+ } else {
224
+ renderArticles(tilesDiv, allArticles[category], category, currentPages[category]);
225
+ }
226
+ }
227
+
228
+ function changePage(category, delta) {
229
+ const totalPages = Math.ceil((allArticles[category] || []).length / articlesPerPage);
230
+ currentPages[category] = Math.max(1, Math.min(currentPages[category] + delta, totalPages));
231
+ renderArticles(document.getElementById(`category-${category}`), allArticles[category], category, currentPages[category]);
232
+ }
233
+
234
+ function handleSearch(event) {
235
+ event.preventDefault();
236
+ const form = event.target;
237
+ const backButton = document.getElementById('backButton');
238
+ const contentContainer = document.getElementById('contentContainer');
239
+ const loadingContainer = document.getElementById('loadingContainer');
240
+ if (loadingContainer) loadingContainer.style.display = 'block';
241
+ contentContainer.innerHTML = '';
242
+
243
+ fetch('/search', { method: 'POST', body: new FormData(form) })
244
+ .then(response => response.json())
245
+ .then(data => {
246
+ if (loadingContainer) loadingContainer.style.display = 'none';
247
+ if (data.has_articles) {
248
+ backButton.style.display = 'block';
249
+ let categoriesHtml = '';
250
+ const sortedCategories = Object.keys(data.categorized_articles).sort();
251
+ for (const category of sortedCategories) {
252
+ const articles = data.categorized_articles[category];
253
+ categoriesHtml += `
254
+ <div class="category-section">
255
+ <div class="category-title">${category} (${articles.length})</div>
256
+ <div class="tiles">
257
+ ${articles.map(createArticleTileHTML).join('')}
258
+ </div>
259
+ </div>`;
260
+ }
261
+ contentContainer.innerHTML = categoriesHtml;
262
+ } else {
263
+ contentContainer.innerHTML = `<div class="no-articles">No results found.</div>`;
264
+ }
265
+ })
266
+ .catch(error => {
267
+ if (loadingContainer) loadingContainer.style.display = 'none';
268
+ console.error('Error performing search:', error);
269
+ contentContainer.innerHTML = `<div class="no-articles">Failed to perform search. Please try again.</div>`;
270
+ });
271
+ }
272
+
273
+ function updateArticles() {
274
+ fetch('/get_updates')
275
+ .then(response => response.json())
276
+ .then(data => {
277
+ if (data.has_updates) {
278
+ if (!document.querySelector('.search-bar').value && currentCategory) {
279
+ console.log("New content available. Refreshing current category.");
280
+ allArticles[currentCategory] = null; // Clear cache
281
+ showCategory(currentCategory); // Reload current category
282
+ }
283
+ }
284
+ })
285
+ .catch(error => console.error('Error checking for updates:', error))
286
+ .finally(() => setTimeout(updateArticles, 60000));
287
+ }
288
+
289
+ function checkLoadingStatus() {
290
+ fetch('/check_loading')
291
+ .then(response => response.json())
292
+ .then(data => {
293
+ if (data.status === 'complete') {
294
+ const loadingContainer = document.getElementById('loadingContainer');
295
+ if (loadingContainer) loadingContainer.style.display = 'none';
296
+ setTimeout(updateArticles, 5000);
297
+ } else {
298
+ setTimeout(checkLoadingStatus, 2000);
299
+ }
300
+ })
301
+ .catch(error => {
302
+ console.error('Error checking loading status:', error);
303
+ setTimeout(checkLoadingStatus, 5000);
304
+ });
305
+ }
306
+ </script>
307
+ </body>
308
+ </html>