obsolete.computer

feedfetcher/page.js

File Type: text/plain


const filter = document.getElementById("filter");
const ageButtons = [...document.querySelectorAll(".filter-tools button")];
const count = document.getElementById("filter-count");
const empty = document.getElementById("no-matches");
const categoryRow = document.getElementById("categories");
const categoryButtons = [...document.querySelectorAll("#categories button")];
const channels = [...document.querySelectorAll(".channel")];
const groups = [...document.querySelectorAll(".group")];

function selectedAge() {
    const pressed = ageButtons.find((button) => button.getAttribute("aria-pressed") === "true");
    return pressed ? pressed.dataset.age : "";
}

function selectedCategory() {
    const pressed = categoryButtons.find((button) => button.getAttribute("aria-pressed") === "true");
    return pressed ? pressed.dataset.category : "";
}

function applyFilter() {
    const query = filter.value.trim().toLocaleLowerCase();
    const age = selectedAge();
    const category = selectedCategory();
    let shown = 0;
    let feeds = 0;
    for (const channel of channels) {
        const inCategory = !category || channel.dataset.category === category;
        const channelHit = !query || channel.dataset.channel.includes(query);
        let matches = 0;
        for (const item of channel.querySelectorAll(".item")) {
            const textHit = channelHit || item.dataset.text.includes(query);
            const ageHit = !age || item.classList.contains(age);
            const hit = inCategory && textHit && ageHit;
            item.classList.toggle("is-hidden", !hit);
            if (hit) matches += 1;
        }
        channel.classList.toggle("is-hidden", matches === 0);
        shown += matches;
        if (matches) feeds += 1;
    }
    for (const group of groups) {
        const visible = group.querySelector(".channel:not(.is-hidden)");
        group.classList.toggle("is-hidden", !visible);
    }
    const filtering = query.length > 0 || age.length > 0 || category.length > 0;
    count.textContent = shown + " entries · " + feeds + " feeds";
    empty.classList.toggle("is-hidden", !filtering || shown > 0);
}

filter.addEventListener("input", applyFilter);
for (const button of ageButtons) {
    button.addEventListener("click", () => {
        const turnOn = button.getAttribute("aria-pressed") !== "true";
        for (const other of ageButtons) other.setAttribute("aria-pressed", "false");
        if (turnOn) button.setAttribute("aria-pressed", "true");
        applyFilter();
    });
}
for (const button of categoryButtons) {
    button.addEventListener("click", () => {
        for (const other of categoryButtons) other.setAttribute("aria-pressed", "false");
        button.setAttribute("aria-pressed", "true");
        applyFilter();
        window.scrollTo({top: 0, behavior: "smooth"});
    });
}
const toTop = document.getElementById("to-top");
window.addEventListener("scroll", () => {
    toTop.classList.toggle("is-visible", window.scrollY > 320);
}, {passive: true});
toTop.addEventListener("click", () => window.scrollTo({top: 0, behavior: "smooth"}));
if (location.hash.length > 1) {
    filter.value = decodeURIComponent(location.hash.slice(1));
    applyFilter();
}
applyFilter();
filter.focus();
const scroller = categoryRow && categoryRow.parentElement;
function updateCategoryScroll() {
    if (!scroller || !scroller.classList.contains("category-scroller")) return;
    const max = categoryRow.scrollWidth - categoryRow.clientWidth;
    scroller.classList.toggle("can-left", categoryRow.scrollLeft > 4);
    scroller.classList.toggle("can-right", max - categoryRow.scrollLeft > 4);
}
const topbar = document.querySelector(".topbar");
const searchForm = document.querySelector(".search");
let compact = false;

function reserveTopbar() {
    // Hold the expanded height so collapsing the bar does not pull the
    // list up and jump the content under the cursor. The empty part of
    // the bar ignores pointer events so items behind it stay clickable.
    // A narrow layout is already compact, so reserve that shorter height.
    if (window.matchMedia("(max-width: 680px)").matches) {
        const saved = topbar.style.minHeight;
        topbar.style.minHeight = "0";
        const height = topbar.offsetHeight;
        topbar.style.minHeight = saved;
        topbar.style.setProperty("--topbar-height", height + "px");
        return;
    }
    if (!compact) topbar.style.setProperty("--topbar-height", topbar.offsetHeight + "px");
}

function updateCompact() {
    if (!topbar) return;
    const next = window.scrollY > 40 && !window.matchMedia("(max-width: 680px)").matches;
    if (next === compact) return;
    compact = next;
    topbar.classList.toggle("is-compact", compact);
    if (searchForm) searchForm.classList.toggle("is-compact", compact);
    updateCategoryScroll();
}
if (categoryRow) {
    for (const caret of scroller.querySelectorAll(".caret")) {
        let timer = 0;
        const step = () => { categoryRow.scrollLeft += Number(caret.dataset.dir) * 8; };
        const stop = () => { clearInterval(timer); timer = 0; };
        caret.addEventListener("pointerdown", (event) => {
            event.preventDefault();
            step();
            stop();
            timer = setInterval(step, 16);
        });
        caret.addEventListener("pointerup", stop);
        caret.addEventListener("pointerleave", stop);
        caret.addEventListener("pointercancel", stop);
    }
    categoryRow.addEventListener("scroll", updateCategoryScroll, {passive: true});
    window.addEventListener("resize", () => {
        reserveTopbar();
        updateCategoryScroll();
    });
    updateCategoryScroll();
}
window.addEventListener("scroll", updateCompact, {passive: true});
reserveTopbar();
updateCompact();

Meta