/* app.js - fetch only when Refresh is pressed */ (function () { function $(id) { return document.getElementById(id); } function parseQuery() { var out = {}; var qs = window.location.search || ""; if (qs.indexOf("?") === 0) qs = qs.substring(1); if (!qs) return out; var parts = qs.split("&"); for (var i = 0; i < parts.length; i++) { var kv = parts[i].split("="); var k = decodeURIComponent(kv[0] || "").trim(); var v = decodeURIComponent(kv.slice(1).join("=") || "").trim(); if (k) out[k] = v; } return out; } function setQuery(params) { var keys = []; for (var k in params) { if (params.hasOwnProperty(k) && params[k] !== "" && params[k] != null) { keys.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k])); } } var qs = keys.length ? ("?" + keys.join("&")) : ""; if (window.history && window.history.replaceState) { window.history.replaceState(null, "", window.location.pathname + qs); } } function findById(arr, id, fallback) { for (var i = 0; i < arr.length; i++) if (arr[i].id === id) return arr[i]; return fallback || null; } function buildFeedUrl(feed, region) { var base; if (feed.type === "top") { base = "https://news.google.com/rss"; } else if (feed.type === "topic" && feed.topic) { base = "https://news.google.com/rss/headlines/section/topic/" + encodeURIComponent(feed.topic); } else { base = "https://news.google.com/rss"; } var q = []; q.push("hl=" + encodeURIComponent(region.hl)); q.push("gl=" + encodeURIComponent(region.gl)); q.push("ceid=" + encodeURIComponent(region.ceid)); return base + "?" + q.join("&"); } function fetchText(url, cb) { var xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.timeout = 15000; xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (xhr.status >= 200 && xhr.status < 300) return cb(null, xhr.responseText); cb(new Error("HTTP " + xhr.status + " while fetching feed")); }; xhr.ontimeout = function () { cb(new Error("Timed out while fetching feed")); }; xhr.onerror = function () { cb(new Error("Network error while fetching feed")); }; xhr.send(null); } function safeText(s) { if (s == null) return ""; return String(s); } function escapeHtml(s) { s = safeText(s); return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function escapeAttr(s) { return escapeHtml(s); } function stripHtml(s) { return safeText(s).replace(/<[^>]*>/g, ""); } function parseRss(xmlText) { var items = []; var doc = null; if (window.DOMParser) { try { doc = new DOMParser().parseFromString(xmlText, "text/xml"); } catch (e) { doc = null; } } if (!doc || !doc.getElementsByTagName) return items; var nodes = doc.getElementsByTagName("item"); for (var i = 0; i < nodes.length; i++) { var it = nodes[i]; function get(tag) { var n = it.getElementsByTagName(tag); if (!n || !n[0] || !n[0].textContent) return ""; return n[0].textContent; } var title = get("title"); var link = get("link"); var pubDate = get("pubDate"); var desc = get("description"); var source = ""; if (title.indexOf(" - ") !== -1) { var parts = title.split(" - "); source = parts[parts.length - 1]; } items.push({ title: title, link: link, pubDate: pubDate, source: source, description: desc }); } return items; } function showError(msg, show) { var el = $("error"); if (!el) return; if (show) { el.style.display = "block"; el.textContent = msg; } else { el.style.display = "none"; el.textContent = ""; } } function renderItems(items) { var host = $("items"); host.innerHTML = ""; if (!items || !items.length) { host.innerHTML = "
No items." + "
Press Refresh to load.
"; return; } var html = []; for (var i = 0; i < items.length; i++) { var it = items[i]; html.push("
"); html.push( "" + escapeHtml(it.title) + "" ); var meta = []; if (it.source) meta.push(escapeHtml(it.source)); if (it.pubDate) meta.push(escapeHtml(it.pubDate)); if (meta.length) html.push("
" + meta.join(" ยท ") + "
"); var snippet = stripHtml(safeText(it.description)).replace(/\s+/g, " ").trim(); if (snippet.length > 220) snippet = snippet.substring(0, 220) + "..."; if (snippet) html.push("
" + escapeHtml(snippet) + "
"); html.push("
"); } host.innerHTML = html.join(""); } function buildSelectOptions(selectEl, arr, selectedId) { selectEl.innerHTML = ""; for (var i = 0; i < arr.length; i++) { var o = document.createElement("option"); o.value = arr[i].id; o.textContent = arr[i].label; if (arr[i].id === selectedId) o.selected = true; selectEl.appendChild(o); } } function getCurrentSelection() { var feeds = window.NEWS_FEEDS || []; var regions = window.NEWS_REGIONS || []; var feedId = $("feedSelect").value; var regionId = $("regionSelect").value; var feed = findById(feeds, feedId, feeds[0] || null); var region = findById(regions, regionId, regions[0] || null); return { feed: feed, region: region }; } function syncUrlToSelection() { setQuery({ feed: $("feedSelect").value, region: $("regionSelect").value }); } function requestNews() { var sel = getCurrentSelection(); if (!sel.feed || !sel.region) { showError("feeds.js is missing NEWS_FEEDS or NEWS_REGIONS.", true); return; } syncUrlToSelection(); showError("", false); var feedUrl = buildFeedUrl(sel.feed, sel.region); var proxyBase = window.NEWS_PROXY_BASE || ""; var fetchUrl = proxyBase ? (proxyBase + encodeURIComponent(feedUrl)) : feedUrl; fetchText(fetchUrl, function (err, txt) { if (err) { showError( "Could not load feed. If this is CORS, set NEWS_PROXY_BASE in feeds.js. Details: " + err.message, true ); renderItems([]); return; } renderItems(parseRss(txt)); }); } window.addEventListener("DOMContentLoaded", function () { var feeds = window.NEWS_FEEDS || []; var regions = window.NEWS_REGIONS || []; if (!feeds.length || !regions.length) { showError("No feeds/regions configured. Check feeds.js.", true); renderItems([]); return; } var q = parseQuery(); var feedId = q.feed || window.NEWS_DEFAULT_FEED_ID || feeds[0].id; var regionId = q.region || window.NEWS_DEFAULT_REGION_ID || regions[0].id; buildSelectOptions($("feedSelect"), feeds, feedId); buildSelectOptions($("regionSelect"), regions, regionId); // Keep URL updated when selecting, but DO NOT request. $("feedSelect").addEventListener("change", syncUrlToSelection); $("regionSelect").addEventListener("change", syncUrlToSelection); $("refreshBtn").addEventListener("click", requestNews); // No auto-request on load. renderItems([]); syncUrlToSelection(); }); })();