PageSourceSearch

https://homesheatandhealthykids.github.io/assets/js/research.js

js homesheatandhealthykids.github.io collected 2026-10-03 09:18:19 UTC 16,168 bytes, 406 lines download raw bytes

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.