PageSourceSearch

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

js homesheatandhealthykids.github.io collected 2026-10-03 09:18:11 UTC 4,919 bytes, 182 lines download raw bytes

1// mediumService.js
2
3 async function loadMediumJSON(options = {}) {
4    const {
5        limit = null,
6        cacheMinutes = 30
7    } = options;
8
9    const cacheKey = "mediumFeedCache";
10    const cacheTTL = cacheMinutes * 60 * 1000;
11
12    const cached = getCachedFeed(cacheKey, cacheTTL);
13
14    // 🔥 If cached exists, return immediately (fast UI)
15    if (cached) {
16        // Trigger background refresh (don’t await)
17        refreshInBackground(cacheKey);
18
19        return applyLimit(cached, limit);
20    }
21
22    // No cache → fetch normally
23    const fresh = await fetchMediumFeed();
24    setCachedFeed(cacheKey, fresh);
25
26    return applyLimit(fresh, limit);
27}
28
29async function refreshInBackground(cacheKey) {
30    try {
31        const freshData = await fetchMediumFeed();
32        const current = getCachedFeed(cacheKey, Infinity);
33
34        // Compare JSON to detect change
35        if (JSON.stringify(freshData) !== JSON.stringify(current)) {
36            setCachedFeed(cacheKey, freshData);
37
38            // 🔔 Notify the app that new data is available
39            window.dispatchEvent(
40                new CustomEvent("mediumFeedUpdated", {
41                    detail: freshData
42                })
43            );
44        }
45    } catch (err) {
46        console.log("Background refresh failed (ignored):", err);
47    }
48}
49function getCachedFeed(key, ttl) {
50    const stored = localStorage.getItem(key);
51
52    if (!stored) return null;
53
54    const parsed = JSON.parse(stored);
55
56    if (Date.now() - parsed.timestamp > ttl) {
57        localStorage.removeItem(key);
58        return null;
59    }
60
61    return parsed.data;
62}
63
64function setCachedFeed(key, data) {
65    const payload = {
66        timestamp: Date.now(),
67        data: data
68    };
69
70    localStorage.setItem(key, JSON.stringify(payload));
71}
72function applyLimit(array, limit) {
73    if (!limit) return array;
74    return array.slice(0, limit);
75}
76
77async function fetchMediumFeed() {
78    const mediumNames = ["@hhhk_83035"];
79    const endpoint = "https://api.rss2json.com/v1/api.json";
80
81    const responses = await Promise.all(
82        mediumNames.map(name =>
83            fetch(`${endpoint}?rss_url=${encodeURIComponent(
84                `https://medium.com/feed/${name}`
85            )}`)
86        )
87    );
88
89    const jsonData = await Promise.all(responses.map(r => r.json()));
90
91    const articles = [];
92
93    jsonData.forEach(feed => {
94        if (feed.status === "ok") {
95            feed.items.forEach(item => {
96                articles.push(transformItem(item));
97            });
98        }
99    });
100
101    articles.sort((a, b) => new Date(b.rawDate) - new Date(a.rawDate));
102
103    return articles;
104}
105
106function truncateText(text, maxLength) {
107    if (text.length <= maxLength) return text;
108
109    const trimmed = text.substring(0, maxLength);
110    return trimmed.substring(0, trimmed.lastIndexOf(" ")) + "...";
111}
112
113function transformItem(item) {
114    const cleanDescription = stripHtml(item.description);
115
116    return {
117        title: item.title,
118        type: "Blog",
119        date: item.pubDate,
120        categories: ["blogs"],
121        image: extractImage(item.description),
122        description: cleanDescription,
123        url: item.link
124    };
125}
126function stripHtml(html) {
127    const div = document.createElement("div");
128    div.innerHTML = html;
129    return div.textContent || div.innerText || "";
130}
131
132function extractImage(description) {
133    const match = description.match(/<img[^>]+src="([^">]+)"/);
134    return match ? match[1] : "images/resources/radiator.png";
135}
136
137function formatDate(dateString) {
138    const date = new Date(dateString);
139    return date.toLocaleDateString("en-GB", {
140        day: "numeric",
141        month: "long",
142        year: "numeric"
143    });
144}
145async function loadMedium() {
146
147  const mediumItems = await loadMediumJSON({
148    limit: 2,          // Show only the latest 5 items
149  });
150  const grid = document.getElementById('researchGrid');
151  grid.innerHTML = ''; // Clear existing content
152 
153  mediumItems.forEach(item => {
154    const card = document.createElement('article');
155    card.className = `frontpage-card ${item.categories.join(' ')}`;
156
157    card.innerHTML = `
158      <div class="frontcard-image">
159        <span class="card-badge">${item.type}</span>
160        <img src="${item.image}" alt="${item.title}">
161      </div>
162      <div class="card-content">
163        <h4>${item.title}</h4>
164        <p>${item.date ? `<span class="icon fa-regular fa-calendar accent1"></span> ${item.date}` : ''}</p>
165        <p>${truncateText(item.description, 250)}</p>
166        <a href="${item.url}" target="_blank" rel="noopener">Read more →</a>
167      </div>
168    `;
169
170    grid.appendChild(card);
171  });
172}
173
174
175document.addEventListener('DOMContentLoaded', () => {
176  loadMedium();
177      // 🔔 Listen for background updates
178  window.addEventListener("mediumFeedUpdated", (event) => {
179        console.log("Feed updated in background");
180        loadMedium(); // Re-render with new data
181    });
182});

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.