btoa() throws InvalidCharacterError on non-Latin1 input, so feed
names with em-dashes ("The Guardian — World") killed render()
before the dialog's hidden class was removed and nothing visibly
happened on click. Use the loop index for the row id instead.
145 lines
5.2 KiB
JavaScript
145 lines
5.2 KiB
JavaScript
// Per-feed visibility, stored in localStorage. Server still ships every story;
|
|
// we just hide cards whose data-source is in the disabled set.
|
|
(function () {
|
|
var KEY = "pete.disabledSources.v1";
|
|
|
|
function load() {
|
|
try {
|
|
var raw = localStorage.getItem(KEY);
|
|
if (!raw) return {};
|
|
var obj = JSON.parse(raw);
|
|
return obj && typeof obj === "object" ? obj : {};
|
|
} catch (e) { return {}; }
|
|
}
|
|
function save(set) {
|
|
try { localStorage.setItem(KEY, JSON.stringify(set)); } catch (e) {}
|
|
}
|
|
|
|
var disabled = load();
|
|
|
|
function apply() {
|
|
var cards = document.querySelectorAll("[data-story-card]");
|
|
cards.forEach(function (c) {
|
|
var src = c.getAttribute("data-source") || "";
|
|
c.style.display = disabled[src] ? "none" : "";
|
|
});
|
|
}
|
|
|
|
function knownSources() {
|
|
// Prefer the server-provided full list (grouped by channel below).
|
|
var server = Array.isArray(window.PETE_SOURCES) ? window.PETE_SOURCES : [];
|
|
if (server.length > 0) {
|
|
var copy = server.slice();
|
|
// Surface any persisted-disabled sources that aren't in the config anymore.
|
|
var present = Object.create(null);
|
|
copy.forEach(function (s) { present[s.name] = true; });
|
|
Object.keys(disabled).forEach(function (n) {
|
|
if (!present[n]) copy.push({ name: n, channel: "" });
|
|
});
|
|
copy.sort(function (a, b) {
|
|
var ca = (a.channel || "~").toLowerCase();
|
|
var cb = (b.channel || "~").toLowerCase();
|
|
if (ca !== cb) return ca.localeCompare(cb);
|
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
});
|
|
return copy;
|
|
}
|
|
// Fallback: derive from DOM if the server didn't ship a list.
|
|
var seen = Object.create(null);
|
|
document.querySelectorAll("[data-story-card]").forEach(function (c) {
|
|
var src = c.getAttribute("data-source");
|
|
var ch = c.getAttribute("data-channel") || "";
|
|
if (src && !seen[src]) seen[src] = { name: src, channel: ch };
|
|
});
|
|
Object.keys(disabled).forEach(function (s) {
|
|
if (!seen[s]) seen[s] = { name: s, channel: "" };
|
|
});
|
|
return Object.keys(seen).map(function (k) { return seen[k]; }).sort(function (a, b) {
|
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
});
|
|
}
|
|
|
|
function render() {
|
|
var list = document.querySelector("[data-settings-list]");
|
|
var meta = document.querySelector("[data-settings-meta]");
|
|
if (!list) return;
|
|
var sources = knownSources();
|
|
list.innerHTML = "";
|
|
if (sources.length === 0) {
|
|
list.innerHTML = '<p class="px-2 py-4 text-sm text-center text-[color:var(--ink)]/50">No feeds configured.</p>';
|
|
} else {
|
|
var lastChannel = null;
|
|
sources.forEach(function (src, i) {
|
|
if (src.channel !== lastChannel) {
|
|
lastChannel = src.channel;
|
|
var hdr = document.createElement("div");
|
|
hdr.className = "px-3 pt-3 pb-1 text-[11px] uppercase tracking-wider text-[color:var(--ink)]/40 font-bold";
|
|
hdr.textContent = src.channel || "other";
|
|
list.appendChild(hdr);
|
|
}
|
|
var id = "pete-feed-" + i;
|
|
var row = document.createElement("label");
|
|
row.setAttribute("for", id);
|
|
row.className = "flex items-center justify-between gap-3 rounded-2xl px-3 py-2 hover:bg-[color:var(--ink)]/5 cursor-pointer";
|
|
var nameEl = document.createElement("span");
|
|
nameEl.className = "text-sm font-semibold truncate";
|
|
nameEl.textContent = src.name;
|
|
var box = document.createElement("input");
|
|
box.type = "checkbox";
|
|
box.id = id;
|
|
box.className = "h-4 w-4 shrink-0 accent-[color:var(--accent)]";
|
|
box.checked = !disabled[src.name];
|
|
box.addEventListener("change", function () {
|
|
if (box.checked) delete disabled[src.name];
|
|
else disabled[src.name] = true;
|
|
save(disabled);
|
|
apply();
|
|
updateMeta();
|
|
});
|
|
row.appendChild(nameEl);
|
|
row.appendChild(box);
|
|
list.appendChild(row);
|
|
});
|
|
}
|
|
updateMeta();
|
|
|
|
function updateMeta() {
|
|
if (!meta) return;
|
|
var off = Object.keys(disabled).length;
|
|
meta.textContent = off === 0 ? "All feeds enabled" : off + " hidden";
|
|
}
|
|
}
|
|
|
|
function open() {
|
|
render();
|
|
var dlg = document.getElementById("pete-settings");
|
|
if (dlg) dlg.classList.remove("hidden");
|
|
}
|
|
function close() {
|
|
var dlg = document.getElementById("pete-settings");
|
|
if (dlg) dlg.classList.add("hidden");
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
apply();
|
|
var trigger = document.querySelector("[data-settings-trigger]");
|
|
if (trigger) trigger.addEventListener("click", open);
|
|
var closeBtn = document.querySelector("[data-settings-close]");
|
|
if (closeBtn) closeBtn.addEventListener("click", close);
|
|
var dlg = document.getElementById("pete-settings");
|
|
if (dlg) dlg.addEventListener("click", function (e) {
|
|
if (e.target === dlg) close();
|
|
});
|
|
var reset = document.querySelector("[data-settings-reset]");
|
|
if (reset) reset.addEventListener("click", function () {
|
|
disabled = {};
|
|
save(disabled);
|
|
apply();
|
|
render();
|
|
});
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape") close();
|
|
});
|
|
});
|
|
})();
|