Fab 隐藏已保存项目

优化性能,隐藏“已保存在我的库中/Saved in My Library”项目并添加控制按钮与计数器,UI跟随网页语言

As of 2025-07-01. See the latest version.

// ==UserScript==
// @name          Fab 隐藏已保存项目
// @name:en       Fab Hide Saved Items
// @namespace     https://fab.com/
// @version       1.9
// @description   优化性能,隐藏“已保存在我的库中/Saved in My Library”项目并添加控制按钮与计数器,UI跟随网页语言
// @description:en Optimizes performance, hides "Saved in My Library" items, adds a control button with counter.
// @author        chatgpt
// @match         https://www.fab.com/channels/*
// @match         https://www.fab.com/zh-cn/channels/*
// @match         https://www.fab.com/discover*
// @match         https://www.fab.com/zh-cn/discover*
// @match         https://www.fab.com/search*
// @match         https://www.fab.com/zh-cn/search*
// @grant         none
// ==/UserScript==

(function () {
    'use strict';

    let hiddenCount = 0;
    let isHidden = true;
    let debounceTimer = null;
    let currentLang = 'en'; // 默认语言为英文

    // 精确匹配“已保存在我的库中”和“Saved in My Library”的文本
    // 修复了中文文本中的错别字
    const savedTextSet = new Set(['已保存在我的库中', 'Saved in My Library']);

    // 根据语言设置按钮文本
    const buttonTexts = {
        en: {
            hide: '🙈 Hide Saved Items',
            show: '👀 Show Saved Items',
            initial: 'Hide Saved Items'
        },
        zh: {
            hide: '🙈 隐藏已保存项目',
            show: '👀 显示已保存项目',
            initial: '隐藏已保存项目'
        }
    };

    /**
     * 检测当前网页语言
     * 优先判断 URL 是否包含中文路径,其次判断 HTML lang 属性
     */
    function detectLanguage() {
        const url = window.location.href;
        const langAttr = document.documentElement.lang;

        // 如果 URL 包含中文路径,则认为是中文
        if (url.includes('/zh-cn/')) {
            currentLang = 'zh';
        }
        // 否则,如果 HTML 的 lang 属性以 'zh' 开头,也认为是中文
        else if (langAttr && langAttr.startsWith('zh')) {
            currentLang = 'zh';
        }
        // 如果以上都不是,则默认是英文
        else {
            currentLang = 'en';
        }
    }

    function hideOrShowSavedCards() {
        hiddenCount = 0;
        const cards = document.querySelectorAll('div.fabkit-Stack-root.nTa5u2sc');
        cards.forEach(card => {
            const text = card.textContent.trim();
            const hasSavedText = [...savedTextSet].some(str => text.includes(str));
            if (hasSavedText) {
                card.style.display = isHidden ? 'none' : '';
                if (isHidden) hiddenCount++;
            }
        });
        updateButtonText();
    }

    function updateButtonText() {
        const btn = document.getElementById('fabToggleBtn');
        if (!btn) return;
        const textOption = buttonTexts[currentLang];
        btn.innerHTML = `
            ${isHidden ? textOption.hide : textOption.show}
            (${hiddenCount})
        `;
    }

    function createToggleButton() {
        const btn = document.createElement('button');
        btn.id = 'fabToggleBtn';
        Object.assign(btn.style, {
            position: 'fixed',
            top: '20px',
            right: '20px',
            zIndex: 9999,
            padding: '10px 14px',
            background: '#0a84ff',
            color: '#fff',
            border: 'none',
            borderRadius: '8px',
            cursor: 'pointer',
            boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
            fontSize: '14px',
            fontFamily: 'system-ui, sans-serif',
            opacity: '0.95',
        });
        const textOption = buttonTexts[currentLang]; // 根据检测到的语言设置初始文本
        btn.innerText = textOption.initial;
        btn.onclick = () => {
            isHidden = !isHidden;
            hideOrShowSavedCards();
        };
        document.body.appendChild(btn);
    }

    function debouncedRun() {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(hideOrShowSavedCards, 300);
    }

    // 页面加载时执行
    window.addEventListener('load', () => {
        detectLanguage(); // 在创建按钮前检测语言
        createToggleButton();
        hideOrShowSavedCards();
        // 设置 MutationObserver 监听动态内容变化
        const observer = new MutationObserver(debouncedRun);
        observer.observe(document.body, { childList: true, subtree: true });
    });
})();