Upload files to "website/news"
This commit is contained in:
280
website/news/app.js
Normal file
280
website/news/app.js
Normal file
@@ -0,0 +1,280 @@
|
||||
/* 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, """)
|
||||
.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 =
|
||||
"<div class='item'><span class='title'>No items.</span>" +
|
||||
"<div class='meta'>Press Refresh to load.</div></div>";
|
||||
return;
|
||||
}
|
||||
|
||||
var html = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var it = items[i];
|
||||
|
||||
html.push("<div class='item'>");
|
||||
html.push(
|
||||
"<a class='title' href='" + escapeAttr(it.link) + "' target='_blank' rel='noopener noreferrer'>" +
|
||||
escapeHtml(it.title) +
|
||||
"</a>"
|
||||
);
|
||||
|
||||
var meta = [];
|
||||
if (it.source) meta.push(escapeHtml(it.source));
|
||||
if (it.pubDate) meta.push(escapeHtml(it.pubDate));
|
||||
if (meta.length) html.push("<div class='meta'>" + meta.join(" · ") + "</div>");
|
||||
|
||||
var snippet = stripHtml(safeText(it.description)).replace(/\s+/g, " ").trim();
|
||||
if (snippet.length > 220) snippet = snippet.substring(0, 220) + "...";
|
||||
if (snippet) html.push("<div class='desc'>" + escapeHtml(snippet) + "</div>");
|
||||
|
||||
html.push("</div>");
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user