TF.TV Thread Title Hider

Hides thread titles you don't want to see immediately

// ==UserScript==
// @name        TF.TV Thread Title Hider
// @namespace   sheybey
// @description Hides thread titles you don't want to see immediately
// @include     https://www.teamfortress.tv/*
// @version     1.3
// @grant       none
// ==/UserScript==

// I don't agree with some of jslint's whitespace rules
/*jslint browser, for, multivar, this, white*/
/*global NodeList*/
/*property
    add, addEventListener, appendChild, backgroundColor, border, call,
    classList, clear, color, contains, createElement, currentTarget, dataset,
    display, float, fontSize, fontStyle, forEach, getAttribute, getElementById,
    getItem, height, insertBefore, join, keys, length, lineHeight, marginTop,
    oldHref, oldTitle, overflow, padding, parentNode, parse, position,
    preventDefault, prototype, push, querySelector, querySelectorAll, remove,
    removeChild, removeEventListener, replace, setAttribute, setItem, slice,
    split, stopPropagation, stringify, style, test, textContent, toLowerCase,
    top, trim, value, width, zIndex
*/

(function () {
    "use strict";
    var hidden = localStorage.getItem("user-hidden"),
        def = [
            "esea",
            "etf2l",
            "ozf",
            "lan"
        ],
        // these elements are explained where they are first used
        navbar = document.querySelector("nav"),
        spoiler = document.createElement("a"),
        spoilerdiv = document.createElement("div"),
        spoilericon = document.createElement("i"),
        container = document.createElement("div"),
        add = document.createElement("span"),
        savetext = document.createElement("span"),
        firstrow = document.createElement("div"),
        shown = false,
        before, threads, articles, news;

    if (hidden === null) {
        hidden = def;
    } else {
        hidden = JSON.parse(hidden);
    }
    hidden = (function () {
        var o = {};
        hidden.forEach(function (keyword) {
            o[keyword] = new RegExp("\\b" +
                // this replace call escapes regex characters
                keyword.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") +
                "\\b", "i");
        });
        return o;
    }());

    // remove the title of a thread from a link, while preserving the effective target
    function sanitize(link, title) {
        link.classList.add("user-hidden");
        link.dataset.oldTitle = title; // <a title="..."> is not always set
        link.dataset.oldHref = link.getAttribute("href");
        // this slice and join removes the last component of the url, which is the slug, from href
        link.setAttribute("href", link.dataset.oldHref.split("/").slice(0, -1).join("/"));
        link.setAttribute("title", "Click to reveal");
    }
    // restore the information removed by sanitize()
    function restore(link) {
        link.classList.remove("user-hidden");
        link.setAttribute("title", link.dataset.oldTitle);
        link.setAttribute("href", link.dataset.oldHref);
    }

    // these functions find link and title elements given a click event and restore information as necessary
    function revealsidebar(event) {
        var link = event.currentTarget,
            span = link.querySelector(".module-item-title");

        event.preventDefault();
        event.stopPropagation();

        restore(link);
        link.removeEventListener("click", revealsidebar);

        span.textContent = link.dataset.oldTitle;
        span.style.fontStyle = "";
    }
    function revealthread(event) {
        var link = event.currentTarget;

        event.preventDefault();
        event.stopPropagation();

        restore(link);
        link.removeEventListener("click", revealthread);
        link.textContent = link.dataset.oldTitle;
        link.style.fontStyle = "";
    }
    function revealarticle(event) {
        var link = event.currentTarget,
            span = link.querySelector(".news-item-title");

        event.preventDefault();
        event.stopPropagation();

        restore(link);
        link.removeEventListener("click", revealarticle);

        span.textContent = link.dataset.oldTitle;
        span.style.fontStyle = "";
    }

    // basically Array.prototype.forEach, but safer than using it directly
    if (typeof NodeList.prototype.forEach !== "function") {
        NodeList.prototype.forEach = function (f) {
            var i;
            for (i = 0; i < this.length; i += 1) {
                f.call(this[i], this[i], i, this);
            }
        };
    }

    // look for links to sanitize
    // the sidebar exists on all pages
    document.getElementById("sidebar-left").querySelectorAll(".module").forEach(function (module) {
        module.querySelectorAll(".module-item").forEach(function (link) {
            var span = link.querySelector(".module-item-title"),
                title;
            if (span.classList.contains("mod-event")) {
                return; // event posts generally do not have spoilers
            }
            title = span.textContent.trim();
            // Object.keys is usually faster than querySelector[All], so it is on the inside of the loop
            Object.keys(hidden).forEach(function (keyword) {
                var newtitle = "[" + keyword + " thread]";
                if (link.classList.contains("user-hidden")) {
                    return;
                }
                if (hidden[keyword].test(title)) {
                    sanitize(link, title);
                    link.addEventListener("click", revealsidebar);

                    span.textContent = newtitle;
                    span.style.fontStyle = "italic";
                }
            });
        });
    });
    // this element only exists on /forum... and /threads
    threads = document.getElementById("thread-list");
    if (threads !== null) {
        threads.querySelectorAll(".thread-inner").forEach(function (thread) {
            // this element only exists on /threads
            var category = thread.querySelector(".description a"),
                link, title;
            if (category !== null && category.textContent.trim() === "Events") {
                return;
            }
            link = thread.querySelector("a.title");
            title = link.textContent.trim();
            Object.keys(hidden).forEach(function (keyword) {
                var newtitle = "[" + keyword + " thread]";
                if (link.classList.contains("user-hidden")) {
                    return;
                }
                if (hidden[keyword].test(title)) {
                    sanitize(link, title);
                    link.addEventListener("click", revealthread);
                    link.textContent = newtitle;
                    link.style.fontStyle = "italic";
                }
            });
        });
    }
    // this element only exists on the front page
    articles = document.getElementById("article-list");
    if (articles !== null) {
        articles.querySelectorAll("a").forEach(function (link) {
            var span = link.querySelector(".news-item-title"),
                title = span.textContent.trim();

            Object.keys(hidden).forEach(function (keyword) {
                var newtitle = "[" + keyword + " article]";
                if (link.classList.contains("user-hidden")) {
                    return;
                }
                if (hidden[keyword].test(title)) {
                    sanitize(link, title);
                    link.addEventListener("click", revealarticle);

                    span.textContent = newtitle;
                    span.style.fontStyle = "italic";
                }
            });
        });
    }
    // this element only exists on /news
    news = document.querySelector("#content > .list-table:not(#schedule-table)");
    if (news !== null) {
        news.querySelectorAll("a").forEach(function (link) {
            var title = link.textContent.trim();

            Object.keys(hidden).forEach(function (keyword) {
                var newtitle = "[" + keyword + " article]";
                if (link.classList.contains("user-hidden")) {
                    return;
                }
                if (hidden[keyword].test(title)) {
                    sanitize(link, title);
                    link.addEventListener("click", revealthread);
                    link.textContent = newtitle;
                    link.style.fontStyle = "italic";
                }
            });
        });
    }

    // this function creates an <input> and remove button for the config div
    function createrow(word) {
        var row = document.createElement("div"),
            input = document.createElement("input"),
            remove = document.createElement("span");

        if (word === undefined) {
            word = "";
        }

        // the <input>
        input.style.height = "30px";
        input.style.marginTop = "5px";
        input.style.fontSize = "11px";
        input.style.lineHeight = "30px";
        input.style.width = "100px";
        input.value = word;

        // remove button
        remove.style.float = "right";
        remove.classList.add("btn");
        remove.textContent = "Remove";
        remove.addEventListener("click", function () {
            row.parentNode.removeChild(row);
        });

        // containing row
        row.style.height = "40px";
        row.style.padding = "5px 0";
        row.style.clear = "both";
        row.appendChild(input);
        row.appendChild(remove);
        return row;
    }

    // the spoiler <a> in the navbar
    spoiler.classList.add("header-nav-item-wrapper");
    spoiler.classList.add("mod-last");
    spoiler.setAttribute("href", "#");
    spoiler.style.overflow = "visible";
    spoiler.addEventListener("click", function (event) {
        event.preventDefault(); // # causes "scroll jump" to top otherwise
        var newhidden = [];
        if (shown) {
            container.style.display = "none";
            container.querySelectorAll("input").forEach(function (input) {
                newhidden.push(input.value.toLowerCase());
            });

            localStorage.setItem("user-hidden", JSON.stringify(newhidden));

            spoilericon.classList.remove("fa-caret-up");
            spoilericon.classList.add("fa-caret-down");

            shown = false;
        } else {
            container.style.display = "";

            spoilericon.classList.remove("fa-caret-down");
            spoilericon.classList.add("fa-caret-up");

            shown = true;
        }
    });

    // each navbar button has a <div> child that contains its label
    spoilerdiv.classList.add("header-nav-item");
    spoilerdiv.classList.add("mod-solo");
    spoilerdiv.textContent = "Spoilers ";

    // font awesome icon that reflects whether the config <div> is shown or not
    spoilericon.classList.add("fa");
    spoilericon.classList.add("fa-caret-down");

    // this <div> is shown and hidden when the spoiler button is clicked
    container.style.backgroundColor = "#f5f5f5";
    container.style.position = "absolute";
    container.style.top = "40px";
    container.style.display = "none";
    container.style.zIndex = "99";
    container.style.width = "200px";
    container.style.padding = "5px";
    container.style.border = "1px solid #b4b4b4";
    // this is necessary to prevent clicks on the config <div> from propagating to the spoiler button
    container.addEventListener("click", function (event) {
        event.preventDefault();
        event.stopPropagation();
    });

    // this row contains the help text and add button
    firstrow.style.height = "40px";
    firstrow.style.padding = "5px 0";
    firstrow.style.clear = "both";

    // help text for config <div>
    savetext.style.fontSize = "11px";
    savetext.style.color = "#555555";
    savetext.style.lineHeight = "40px";
    savetext.textContent = "Close to save";

    // the add button
    add.classList.add("btn");
    add.textContent = "Add";
    add.style.float = "right";
    add.addEventListener("click", function () {
        container.appendChild(createrow());
    });

    firstrow.appendChild(savetext);
    firstrow.appendChild(add);

    container.appendChild(firstrow);
    Object.keys(hidden).forEach(function (keyword) {
        container.appendChild(createrow(keyword));
    });

    spoilerdiv.appendChild(spoilericon);

    spoiler.appendChild(spoilerdiv);
    spoiler.appendChild(container);

    // spoiler button is inserted before this element
    before = navbar.querySelector("#user-actions"); // logged in
    if (before === null) {
        before = navbar.querySelector("a:last-child"); // not logged in
    }
    navbar.insertBefore(spoiler, before);
}());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址