1/* 2 Data structure example (you can keep adding objects like this) 3*/ 4 5const researchItems = [ 6 { 7 "title": "Beyond Remuneration for Patient and Public Involvement Group Members", 8 "type": "Presentation", 9 "categories": ["presentations"], 10 "image": "images/resources/house_with_heart.png", 11 "date": "2026-07-16", 12 "description": "Tracy will be displaying her poster at International Population Data Linkage Network 2026 in Rotterdam.", 13 "url": "https://ipdln.org/2026-ipdln-conference/" 14 }, 15 { 16 "title": "7 Days, 100 Scientists - Dynamic Earth", 17 "type": "Engagement", 18 "categories": ["engagements"], 19 "image": "images/resources/chilly_people.png", 20 "date": "2026-04-10", 21 "description": "The team were at Dynamic Earth for 3 days to engage with children about the impact of housing on health.", 22 "url": "https://www.edinburghscience.co.uk/wp-content/uploads/2026/02/ESF-Programme-2026.pdf" 23 }, 24 { 25 "title": "Four Days Inside Government", 26 "type": "Engagement", 27 "categories": ["engagements"], 28 "image": "images/resources/arctic_blast.png", 29 "date": "2026-04-20", 30 "description": "Livvy Swann reflects on her Royal Society Policy Pairing Scheme experience, exploring policymaking and science communication", 31 "url": "https://usher.ed.ac.uk/news-events/features/four-days-inside-government-reflections-from-the-royal-society-policy-pairing-scheme" 32 }, 33 { 34 "title": "Health and Housing Linkage Across TREs", 35 "type": "Presentation", 36 "categories": ["presentations"], 37 "image": "images/resources/house_group.png", 38 "date": "2026-05-27", 39 "description": "Dr Jess Butler (Lead Data Scientist, NHS Grampian) presented on her work linking Health and Housing Data across Trusted Research Environments. The project used a novel linkage of housing and health data to analyse the health of vulnerable populations, meet local needs and track impact", 40 "url": "https://teams.microsoft.com/l/meetingrecap?driveId=b%21fmrZLNnF0EqtA53iQU_xMYUAfovptFxAocbWexc5ke9hIHLFeqw3QphVCCvBHfeh&driveItemId=01QWYW7NJQY6F673P7RZD2SWB7JEFZMDBK&sitePath=https%3A%2F%2Fuoe-my.sharepoint.com%2F%3Av%3A%2Fg%2Fpersonal%2Foswann_ed_ac_uk%2FIQAwx4vv7f-OR6lYP0kLlgwqAaHY7KYc99WfwKj0wLxddXM&fileUrl=https%3A%2F%2Fuoe-my.sharepoint.com%2F%3Av%3A%2Fg%2Fpersonal%2Foswann_ed_ac_uk%2FIQAwx4vv7f-OR6lYP0kLlgwqAaHY7KYc99WfwKj0wLxddXM&iCalUid=040000008200E00074C5B7101A82E00800000000346BE8A23AE8DC010000000000000000100000006FAE66E514897F45893934224A962080&threadId=19%3Ameeting_OTUzNDI4MDEtMzM4Yy00NjQ5LWIzNzMtZTM4ZWE4ZWY4YTQw%40thread.v2&organizerId=6b008deb-e397-4785-a709-e89e9372c23a&tenantId=2e9f06b0-1669-4589-8789-10a06934dc61&callId=534168b4-8680-46f5-8dda-c940ab5c6831&threadType=Meeting&meetingType=Scheduled&subType=RecapSharingLink_RecapChiclet" 41 }, 42 { 43 44 "title": "Healthy Urban Places Seminar - Healthy Homes Special", 45 "type": "Presentation", 46 "categories": ["presentations"], 47 "image": "images/resources/boilers.png", 48 "date": "2026-03--25", 49 "description": "Livvy talks to Born In Bradford about understanding the connections between underheated homes and preschool children's respiratory health in Scotland.", 50 "url": "https://borninbradford.nhs.uk/events/healthy-urban-places-seminar-healthy-homes-special/" 51 }, 52 { 53 54 "title": "Early Childhood Respiratory Infections - Plan", 55 "type": "Presentation", 56 "categories": ["presentations"], 57 "image": "images/resources/radiator.png", 58 "date": "2026-03-10", 59 "description": "This talk will describe some of the planned analysis of a linked Lothians-region dataset containing housing data, primary and secondary healthcare.",
60 "url": "https://www.strath.ac.uk/engineering/architecture/" 61 }, 62 { 63 "title": "Glasgow Science Festival - Staying Warm and Well", 64 "type": "Engagement", 65 "categories": ["engagements"], 66 "image": "https://cfmnsugzia.cloudimg.io/www.gla.ac.uk/media/Media_762130_smxx.jpg", 67 "date": "2026-06-13", 68 "description": "The team will be at Glasgow University to engage with children about the impact of housing on health, and to explore potential interventions to improve health outcomes.", 69 "url": "https://www.gla.ac.uk/events/sciencefestival/" 70 }, 71 { 72 "title": "Castlebrae Community Science Festival", 73 "type": "Engagement", 74 "categories": ["engagements"], 75 "image": "images/resources/parent_with_child.png", 76 "date": "2026-03-27", 77 "description": "The Community Science Festival is happening at Castlebrae Community Campus! Join us for an afternoon filled with exciting experiments, hands-on activities, and interactive demonstrations.", 78 "url": "https://www.eventbrite.co.uk/e/community-science-festival-castlebrae-2026-tickets-1983762665534?aff=oddtdtcreator" 79 }, 80 { 81 "title": "The Impact of Home Energy Efficiency Measures on Childrenâs Respiratory Health", 82 "type": "Paper", 83 "categories": ["papers"], 84 "image": "images/resources/jumper_with_concerns.jpg", 85 "date": "2025-11-22", 86 "description": "Scoping review examining the association between home energy efficiency measures and children's respiratory health, and exploring potential downsides.", 87 "url": "https://doi.org/10.12688/wellcomeopenres.24692.1" 88 }, 89 { 90 "title": "Homes, Heat and Healthy Kids - HDR UK Conference", 91 "type": "Presentation", 92 "date": "2025-10-15", 93 "categories": ["presentations"], 94 "image": "images/resources/house_group.png", 95 "description": "Livvy Swann presents on the Homes, Heat and Healthy Kids study and the role of underheated homes in preschool respiratory health at the Health Data Research UK Conference.", 96 "url": "https://www.hdruk.ac.uk/about/hdr-uk-conference/health-data-research-uk-conference-2025-agenda/" 97 }, 98 { 99 "title": "Homes, Heat and Healthy Kids - UKIEG Conference", 100 "type": "Presentation", 101 "date": "2026-06-25", 102 "categories": ["presentations"], 103 "image": "images/resources/house_with_germs.png", 104 "description": "Tim Wilding presented on the SHCS. Caroline presented on the Warmer Kiwi's study. And Eleanor Harrison won best poster presentation.", 105 "url": "https://hestia-network.org/ukieg-conference-agm/" 106 }, 107 { 108 "title": "Orkney Science Festival", 109 "type": "Engagement", 110 "categories": ["engagements"], 111 "image": "images/resources/parent_with_child.png", 112 "date": "2026-09-05", 113 "description": "The Orkney International Science Festival will return this year. Join the team for an exciting time in the beautiful Orkney Islands.", 114 "url": "https://www.orkney.com/events/orkney-international-science-festival" 115 }, 116 { 117 "title": "Bang Goes The Borders - 2026", 118 "type": "Engagement", 119 "categories": ["engagements"], 120 "image": "images/resources/parent_with_child.png", 121 "date": "2026-09-19", 122 "description": "Bang Goes the Borders is an annual science and learning festival and typically comprises around 30 presentations and interactive workshops running throughout the day. The event is free to attend and takes place in the grounds of Abbey Park Campus in Melrose.", 123 "url": "https://stmarystd6.greenhousecms.co.uk/Co-Curricular/BGTB/" 124 }, 125 { 126 "title": "Housing Matters: How did we get here and where are we going?", 127 "type": "Presentation", 128 "date": "2026-09-02", 129 "categories": ["presentations"], 130 "image": "images/resources/house_with_germs.png", 131 "description": "Tim Wilding will present on the effects of Housing Precarity.", 132 "url": "https://housingevidence.ac.uk/event/cache-conference/" 133 }, 134 { 135 "title": "Doors Open Day",
136 "type": "Engagement", 137 "date": "2026-09-26", 138 "categories": ["engagements"], 139 "image": "images/resources/house_group.png", 140 "description": "Meet the H3K team at the Usher Institute on Doors Open Day", 141 "url": "https://www.doorsopendays.org.uk/regions/edinburgh/university-of-edinburgh-usher-building-and-institute-for-regeneration-and-repair" 142 }, 143 { 144 "title": "House Doctor - An Interactive Game for Families", 145 "type": "Blog", 146 "date": "2026-09-17", 147 "categories": ["blogs"], 148 "image": "images/resources/house_group.png", 149 "description": "A new Healthy Homes Hub blog looks at House Doctor, an interactive game by Drs Swann and McGill. The blog explains how the game helps families visualize trade-offs between home warmth and air quality, fostering understanding of ventilation needs through engaging, blame-free play.", 150 "url": "https://healthyhomeshub.uk/articles/house-doctor-game-warmth-indoor-air-quality" 151 } 152 ]; 153 154 // Ideally this would be loaded from a separate Javascript or API, but for simplicity we're hardcoding it here. 155 async function loadMediumJSON(options = {}) { 156 const { 157 limit = null, 158 cacheMinutes = 30 159 } = options; 160 161 const cacheKey = "mediumFeedCache"; 162 const cacheTTL = cacheMinutes * 60 * 1000; 163 164 const cached = getCachedFeed(cacheKey, cacheTTL); 165 166 // ð¥ If cached exists, return immediately (fast UI) 167 if (cached) { 168 // Trigger background refresh (donât await) 169 refreshInBackground(cacheKey); 170 171 return applyLimit(cached, limit); 172 } 173 174 // No cache â fetch normally 175 const fresh = await fetchMediumFeed(); 176 setCachedFeed(cacheKey, fresh); 177 178 return applyLimit(fresh, limit); 179} 180 181async function refreshInBackground(cacheKey) { 182 try { 183 const freshData = await fetchMediumFeed(); 184 const current = getCachedFeed(cacheKey, Infinity); 185 186 // Compare JSON to detect change 187 if (JSON.stringify(freshData) !== JSON.stringify(current)) { 188 setCachedFeed(cacheKey, freshData); 189 190 // ð Notify the app that new data is available 191 window.dispatchEvent( 192 new CustomEvent("mediumFeedUpdated", { 193 detail: freshData 194 }) 195 ); 196 } 197 } catch (err) { 198 console.log("Background refresh failed (ignored):", err); 199 } 200} 201function getCachedFeed(key, ttl) { 202 const stored = localStorage.getItem(key); 203 204 if (!stored) return null; 205 206 const parsed = JSON.parse(stored); 207 208 if (Date.now() - parsed.timestamp > ttl) { 209 localStorage.removeItem(key); 210 return null; 211 } 212 213 return parsed.data; 214} 215 216function setCachedFeed(key, data) { 217 const payload = { 218 timestamp: Date.now(), 219 data: data 220 }; 221 222 localStorage.setItem(key, JSON.stringify(payload)); 223} 224function applyLimit(array, limit) { 225 if (!limit) return array; 226 return array.slice(0, limit); 227} 228 229async function fetchMediumFeed() { 230 const mediumNames = ["@hhhk_83035"]; 231 const endpoint = "https://api.rss2json.com/v1/api.json"; 232 233 const responses = await Promise.all( 234 mediumNames.map(name => 235 fetch(`${endpoint}?rss_url=${encodeURIComponent( 236 `https://medium.com/feed/${name}` 237 )}`) 238 ) 239 ); 240 241 const jsonData = await Promise.all(responses.map(r => r.json())); 242 243 const articles = []; 244 245 jsonData.forEach(feed => { 246 if (feed.status === "ok") { 247 feed.items.forEach(item => { 248 articles.push(transformItem(item)); 249 }); 250 } 251 }); 252 253 articles.sort((a, b) => new Date(b.rawDate) - new Date(a.rawDate)); 254 255 return articles; 256} 257 258function truncateText(text, maxLength) { 259 if (text.length <= maxLength) return text; 260 261 const trimmed = text.substring(0, maxLength); 262 return trimmed.substring(0, trimmed.lastIndexOf(" ")) + "..."; 263} 264 265function transformItem(item) { 266 const cleanDescription = stripHtml(item.description); 267 268 return { 269 title: item.title, 270 type: "Blog", 271 date: item.pubDate, 272 categories: ["blogs"], 273 image: extractImage(item.description), 274 description: cleanDescription, 275 url: item.link 276 }; 277} 278function stripHtml(html) { 279 const div = document.createElement("div"); 280 div.innerHTML = html; 281 return div.textContent || div.innerText || ""; 282} 283 284function extractImage(description) { 285 const match = description.match(/<img[^>]+src="([^">]+)"/); 286 return match ? match[1] : "images/resources/radiator.png"; 287} 288 289function formatDate(dateString) { 290 const date = new Date(dateString); 291 return date.toLocaleDateString("en-GB", { 292 day: "numeric", 293 month: "long", 294 year: "numeric" 295 }); 296} 297 298 299function mergeFeeds(blogArray, otherArray) { 300 const combined = [...blogArray, ...otherArray]; 301 302 combined.sort((a, b) => new Date(b.date) - new Date(a.date)); 303 304 return combined; 305} 306 307async function loadResearch() { 308 309 const mediumItems = await loadMediumJSON(); 310 const grid = document.getElementById('researchGrid'); 311 grid.innerHTML = ''; // Clear existing content
312 const allItems = mergeFeeds(researchItems, mediumItems); 313 314 315 allItems.forEach(item => { 316 const card = document.createElement('article'); 317 card.className = `research-card ${item.categories.join(' ')}`; 318 319 card.innerHTML = ` 320 <div class="card-image"> 321 <span class="card-badge">${item.type}</span> 322 <img src="${item.image}" alt="${item.title}"> 323 </div> 324 <div class="card-content"> 325 <h4>${item.title}</h4> 326 <p>${item.date ? `<span class="icon fa-regular fa-calendar accent1"></span> ${formatDate(item.date)}` : ''}</p> 327 <p>${truncateText(item.description, 150)}</p> 328 <a href="${item.url}" target="_blank" rel="noopener">Read more â</a> 329 </div> 330 `; 331 332 grid.appendChild(card); 333 }); 334} 335 336 337document.addEventListener("DOMContentLoaded", async () => { 338 loadResearch(); 339 340 // ð Listen for background updates 341 window.addEventListener("mediumFeedUpdated", (event) => { 342 console.log("Feed updated in background"); 343 loadResearch(); // Re-render with new data 344 }); 345}); 346 347function setupFilters() { 348 const buttons = document.querySelectorAll('.filters button'); 349 350 buttons.forEach(button => { 351 button.addEventListener('click', () => { 352 buttons.forEach(b => b.classList.remove('active')); 353 button.classList.add('active'); 354 355 const filter = button.dataset.filter; 356 document.querySelectorAll('.research-card').forEach(card => { 357 card.classList.toggle('hidden', filter !== 'all' && !card.classList.contains(filter)); 358 }); 359 }); 360 }); 361} 362 363document.addEventListener('DOMContentLoaded', async () => { 364 setupFilters(); 365}); 366 367document.addEventListener("DOMContentLoaded", () => { 368 const buttons = document.querySelectorAll(".filters button"); 369 const cards = document.querySelectorAll(".research-card"); 370 const comingUpButton = document.querySelector(".news button:last-child"); 371 372 comingUpButton.addEventListener("click", () => { 373 cards.forEach(card => { 374 filter = ""; 375 buttons.forEach( b => 376 { 377 if (b.classList.contains("active")) {filter = b.dataset.filter;} 378 }); 379 380 const date = new Date(card.querySelector(".card-content p").textContent.trim()); 381 if (date > new Date() && (card.classList.contains(filter) || filter === "all")) { 382 card.classList.remove("hidden"); 383 } else { 384 card.classList.add("hidden"); 385 } 386 387 }); 388 }); 389 390 buttons.forEach(button => { 391 button.addEventListener("click", () => { 392 buttons.forEach(b => b.classList.remove("active")); 393 button.classList.add("active"); 394 395 const filter = button.dataset.filter; 396 397 cards.forEach(card => { 398 if (filter === "all" || card.classList.contains(filter)) { 399 card.classList.remove("hidden"); 400 } else { 401 card.classList.add("hidden"); 402 } 403 }); 404 }); 405 }); 406});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.