20250309 [油猴脚本] [LINUX DO] 🚫 屏蔽含有指定:类别、标签和标题关键词 的话题|适配iOS移动端

前情提要

20240817.0 [油猴脚本] [LINUX DO] 🚫 屏蔽含有指定:类别、标签和标题关键词 的话题|适配iOS移动端


太长不看

本次更新,修复了:脚本影响话题持续加载的问题;


点击查看 完整脚本
// ==UserScript==
// @name         [LINUX DO] 🚫 屏蔽含有指定:类别、标签和标题关键词 的话题 [20250124] Fixing 0
// @namespace    0_V userscripts/[LINUX DO] 🚫 屏蔽含有指定:类别、标签和标题关键词 的话题
// @version      [20250124] Fixing 0
// @description  功能:1️⃣自定义配置;2️⃣适配搜索页;3️⃣无需手动刷新,配置更新后立即生效;4️⃣支持从账号的偏好设置中,导入类别、标签设置;5️⃣未登录时,屏蔽功能依然有效;6️⃣支持从侧边栏导入标题关键词;7️⃣支持通过文件 导入/导出脚本的全部设置;8️⃣支持正则表达式过滤;[修复]搜索结果页空白
// @update-log   通过对表格行使用visibility:collapse(或对Safari、非<tr>使用绝对定位)实现“软隐藏”,解决搜索结果空白问题。
// @author       0_V
// @match        https://linux.do/*
//
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
//
// @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDEyMCAxMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnM+CiAgICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGlnaHRTYWJlckdyYWRpZW50IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CiAgICAgICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjM2IwMDAwIiBzdG9wLW9wYWNpdHk9IjAuNyIvPgogICAgICAgICAgPHN0b3Agb2Zmc2V0PSI1MCUiIHN0b3AtY29sb3I9IiM4YjAwMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogICAgICAgICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjM2IwMDAwIiBzdG9wLW9wYWNpdHk9IjAuNyIvPgogICAgICA8L2xpbmVhckdyYWRpZW50PgoKICAgICAgPGxpbmVhckdyYWRpZW50IGlkPSJtZXRhbFRleHR1cmUiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj4KICAgICAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxYTFhMWEiLz4KICAgICAgICAgIDxzdG9wIG9mZnNldD0iNTAlIiBzdG9wLWNvbG9yPSIjMmMyYzJjIi8+CiAgICAgICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxYTFhMWEiLz4KICAgICAgPC9saW5lYXJHcmFkaWVudD4KCiAgICAgIDxmaWx0ZXIgaWQ9ImxpZ2h0c2FiZXJHbG93Ij4KICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjUiIC8+CiAgICAgICAgICA8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwLjYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCAxIDAgMCAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAgMCAxIDAgMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwIDAgMCAwLjcgMCIvPgogICAgICA8L2ZpbHRlcj4KICA8L2RlZnM+CgogIDxjbGlwUGF0aCBpZD0iYSI+PGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNDciLz48L2NsaXBQYXRoPgogIDxjaXJjbGUgZmlsbD0iI2YwZjBmMCIgY3g9IjYwIiBjeT0iNjAiIHI9IjUwIi8+CiAgPHJlY3QgZmlsbD0iIzFjMWMxZSIgY2xpcC1wYXRoPSJ1cmwoI2EpIiB4PSIxMCIgeT0iMTAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMzAiLz4KICA8cmVjdCBmaWxsPSIjZjBmMGYwIiBjbGlwLXBhdGg9InVybCgjYSkiIHg9IjEwIiB5PSI0MCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSI0MCIvPgogIDxyZWN0IGZpbGw9IiNmZmIwMDMiIGNsaXAtcGF0aD0idXJsKCNhKSIgeD0iMTAiIHk9IjgwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjMwIi8+CgogIDxnIHRyYW5zZm9ybT0icm90YXRlKDQ1IDYwIDYwKSI+CiAgICAgIDxnPgogICAgICAgICAgPHBhdGggCiAgICAgICAgICAgICAgZD0iTTU3LDE1IAogICAgICAgICAgICAgICBMNjMsMTUgCiAgICAgICAgICAgICAgIEw2MywzMCAKICAgICAgICAgICAgICAgUTYxLDMxIDU5LDMwIAogICAgICAgICAgICAgICBMNTcsMzAgWiIgCiAgICAgICAgICAgICAgZmlsbD0idXJsKCNtZXRhbFRleHR1cmUpIiAKICAgICAgICAgICAgICBzdHJva2U9IiMwMDAiIAogICAgICAgICAgICAgIHN0cm9rZS13aWR0aD0iMC41Ii8+CiAgICAgICAgICAKICAgICAgICAgIDxwYXRoIAogICAgICAgICAgICAgIGQ9Ik01Ni41LDE1LjUgTDU3LjUsMTUuNSBMNTcuNSwyOS41IEw1Ni41LDI5LjUgWiIgCiAgICAgICAgICAgICAgZmlsbD0iIzExMSIvPgogICAgICAgICAgPHBhdGggCiAgICAgICAgICAgICAgZD0iTTYzLjUsMTUuNSBMNjIuNSwxNS41IEw2Mi41LDI5LjUgTDYzLjUsMjkuNSBaIiAKICAgICAgICAgICAgICBmaWxsPSIjMTExIi8+CiAgICAgICAgICAKICAgICAgICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYwIDIyKSI+CiAgICAgICAgICAgICAgPGNpcmNsZSAKICAgICAgICAgICAgICAgICAgY3g9IjAiIAogICAgICAgICAgICAgICAgICBjeT0iLTIiIAogICAgICAgICAgICAgICAgICByPSIxLjUiIAogICAgICAgICAgICAgICAgICBmaWxsPSIjMjIyIiAKICAgICAgICAgICAgICAgICAgc3Ryb2tlPSIjMDAwIiAKICAgICAgICAgICAgICAgICAgc3Ryb2tlLXdpZHRoPSIwLjMiLz4KICAgICAgICAgICAgICAKICAgICAgICAgICAgICA8Y2lyY2xlIAogICAgICAgICAgICAgICAgICBjeD0iLTIiIAogICAgICAgICAgICAgICAgICBjeT0iMCIgCiAgICAgICAgICAgICAgICAgIHI9IjAuOCIgCiAgICAgICAgICAgICAgICAgIGZpbGw9IiMzMzMiLz4KICAgICAgICAgICAgICA8Y2lyY2xlIAogICAgICAgICAgICAgICAgICBjeD0iMiIgCiAgICAgICAgICAgICAgICAgIGN5PSIwIiAKICAgICAgICAgICAgICAgICAgcj0iMC44IiAKICAgICAgICAgICAgICAgICAgZmlsbD0iIzMzMyIvPgogICAgICAgICAgPC9nPgogICAgICAgICAgCiAgICAgICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MCAzMikiPgogICAgICAgICAgICAgIDxyZWN0IAogICAgICAgICAgICAgICAgICB4PSItMy41IiAKICAgICAgICAgICAgICAgICAgeT0iMCIgCiAgICAgICAgICAgICAgICAgIHdpZHRoPSI3IiAKICAgICAgICAgICAgICAgICAgaGVpZ2h0PSIyIiAKICAgICAgICAgICAgICAgICAgZmlsbD0iIzFjMWMxYyIgCiAgICAgICAgICAgICAgICAgIHJ4PSIwLjUiIAogICAgICAgICAgICAgICAgICByeT0iMC41Ii8+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPGNpcmNsZSAKICAgICAgICAgICAgICAgICAgY3g9Ii0yIiAKICAgICAgICAgICAgICAgICAgY3k9IjEiIAogICAgICAgICAgICAgICAgICByPSIwLjUiIAogICAgICAgICAgICAgICAgICBmaWxsPSIjNDQ0Ii8+CiAgICAgICAgICAgICAgPGNpcmNsZSAKICAgICAgICAgICAgICAgICAgY3g9IjIiIAogICAgICAgICAgICAgICAgICBjeT0iMSIgCiAgICAgICAgICAgICAgICAgIHI9IjAuNSIgCiAgICAgICAgICAgICAgICAgIGZpbGw9IiM0NDQiLz4KICAgICAgICAgIDwvZz4KCiAgICAgICAgICA8Zz4KICAgICAgICAgICAgICA8cmVjdCAKICAgICAgICAgICAgICAgICAgeD0iNTkiIAogICAgICAgICAgICAgICAgICB5PSIxMyIgCiAgICAgICAgICAgICAgICAgIHdpZHRoPSIyIiAKICAgICAgICAgICAgICAgICAgaGVpZ2h0PSIxIiAKICAgICAgICAgICAgICAgICAgZmlsbD0iIzExMSIgCiAgICAgICAgICAgICAgICAgIHJ4PSIwLjMiIAogICAgICAgICAgICAgICAgICByeT0iMC4zIi8+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgPHJlY3QgCiAgICAgICAgICAgICAgICAgIHg9IjU5IiAKICAgICAgICAgICAgICAgICAgeT0iMzQiIAogICAgICAgICAgICAgICAgICB3aWR0aD0iMiIgCiAgICAgICAgICAgICAgICAgIGhlaWdodD0iMSIgCiAgICAgICAgICAgICAgICAgIGZpbGw9IiMyMjIiIAogICAgICAgICAgICAgICAgICByeD0iMC4zIiAKICAgICAgICAgICAgICAgICAgcnk9IjAuMyIvPgogICAgICAgICAgPC9nPgogICAgICA8L2c+CgogICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDM1KSI+CiAgICAgICAgICA8cGF0aCAKICAgICAgICAgICAgICBkPSJNNTUsMCBMNjUsMCBMNjUsNjUgTDU1LDY1IFoiIAogICAgICAgICAgICAgIGZpbGw9InVybCgjbGlnaHRTYWJlckdyYWRpZW50KSIgCiAgICAgICAgICAgICAgZmlsdGVyPSJ1cmwoI2xpZ2h0c2FiZXJHbG93KSI+CiAgICAgICAgICAgICAgPGFuaW1hdGUgCiAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZU5hbWU9ImQiIAogICAgICAgICAgICAgICAgICB2YWx1ZXM9Ik01NSwwIEw2NSwwIEw2NSw2NSBMNTUsNjUgWjsKICAgICAgICAgICAgICAgICAgICAgICAgICBNNTMsMCBMNjcsMCBMNjcsNjUgTDUzLDY1IFo7CiAgICAgICAgICAgICAgICAgICAgICAgICAgTTU1LDAgTDY1LDAgTDY1LDY1IEw1NSw2NSBaIiAKICAgICAgICAgICAgICAgICAgZHVyPSIycyIgCiAgICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIi8+CiAgICAgICAgICA8L3BhdGg+CiAgICAgIDwvZz4KICA8L2c+CgogIDxjaXJjbGUgCiAgICAgIGN4PSI2MCIgCiAgICAgIGN5PSI2MCIgCiAgICAgIHI9IjU1IiAKICAgICAgZmlsbD0ibm9uZSIgCiAgICAgIHN0cm9rZT0iI2ZmMDAwMCIgCiAgICAgIHN0cm9rZS13aWR0aD0iNiIgCiAgICAgIHN0cm9rZS1kYXNoYXJyYXk9IjE1IDEwIj4KICAgICAgPGFuaW1hdGUgCiAgICAgICAgICBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgCiAgICAgICAgICB2YWx1ZXM9IjI1OzA7LTI1IiAKICAgICAgICAgIGR1cj0iMnMiIAogICAgICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICA8L2NpcmNsZT4KPC9zdmc+
// ==/UserScript==

(function() {
    'use strict';

    // ================================
    // ========== 默认数据区 ==========
    // ================================

    // 默认屏蔽的类别、标签和标题关键词列表
    let blockedTitles = [];
    let blockedCategories = [];
    let blockedTtags = [];

    // 正则表达式存储
    let titleRegexList = [];
    let categoryRegexList = [];
    let tagRegexList = [];

    // 常用正则表达式预设
    const regexPresets = {
        '1️⃣ 包含 [特定词]': '.*(word1|word2|word3).*',
        '2️⃣ 以 [特定词] 开头': '^(start1|start2|start3)',
        '3️⃣ 以 [特定词] 结尾': '(end1|end2|end3)'
    };

    // 新增 Safari 判断
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

    // ================================
    // ========== 软隐藏关键逻辑 ========
    // ================================

    function softHideElement(elem) {
        if (!elem) return;

        // 如果是表格行:
        if (elem.tagName === 'TR') {
            if (isSafari) {
                // Safari - 用绝对定位脱离文档流
                elem.style.visibility = 'hidden';
                elem.style.position = 'absolute';
                elem.style.left = '-9999px';
                elem.style.opacity = '0';
                elem.style.height = '0px';
                elem.style.overflow = 'hidden';
            } else {
                // 其他浏览器:使用 visibility: collapse
                elem.style.visibility = 'collapse';
                elem.style.height = '';
                elem.style.overflow = '';
                elem.style.opacity = '';
            }
        } else {
            // [MODIFIED] 非 <tr> 的软隐藏:统一采用绝对定位移除文档流,防止搜索结果页出现空白
            elem.style.visibility = 'hidden';
            elem.style.position = 'absolute';
            elem.style.left = '-9999px';
            elem.style.opacity = '0';
            elem.style.height = '0px';
            elem.style.overflow = 'hidden';
        }
    }

    function unhideElement(elem) {
        if (!elem) return;
        // 恢复样式
        if (elem.tagName === 'TR') {
            if (isSafari) {
                elem.style.visibility = '';
                elem.style.position = '';
                elem.style.left = '';
                elem.style.opacity = '';
                elem.style.height = '';
                elem.style.overflow = '';
            } else {
                elem.style.visibility = '';
            }
        } else {
            elem.style.visibility = '';
            elem.style.position = '';
            elem.style.left = '';
            elem.style.opacity = '';
            elem.style.height = '';
            elem.style.overflow = '';
        }
    }

    // ================================
    // ========== 辅助工具方法 =========
    // ================================

    function showNotification(message, type = 'success') {
        const notificationPanel = document.getElementById('notificationPanel');
        if (!notificationPanel) return;

        notificationPanel.textContent = message;
        notificationPanel.className = 'show';

        switch (type) {
            case 'success':
                notificationPanel.style.backgroundColor = '#28a745';
                break;
            case 'error':
                notificationPanel.style.backgroundColor = '#dc3545';
                break;
            case 'info':
                notificationPanel.style.backgroundColor = '#17a2b8';
                break;
        }

        setTimeout(() => {
            notificationPanel.className = '';
        }, 3000);
    }

    function getUsernameFromUrl() {
        const urlRegex = /https:\/\/linux\.do\/u\/([^\/]+)/;
        const match = window.location.href.match(urlRegex);
        return match ? match[1] : null;
    }

    // ================================
    // ========== 设置相关方法 =========
    // ================================

    function loadSettings() {
        console.log('Loading settings...');
        blockedTitles = GM_getValue('blockedTitles', []);
        blockedCategories = GM_getValue('blockedCategories', []);
        blockedTtags = GM_getValue('blockedTtags', []);

        blockedTitles = Array.isArray(blockedTitles) ? blockedTitles : [];
        blockedCategories = Array.isArray(blockedCategories) ? blockedCategories : [];
        blockedTtags = Array.isArray(blockedTtags) ? blockedTtags : [];

        try {
            titleRegexList = GM_getValue('titleRegexList', []).map(pattern => new RegExp(pattern));
            categoryRegexList = GM_getValue('categoryRegexList', []).map(pattern => new RegExp(pattern));
            tagRegexList = GM_getValue('tagRegexList', []).map(pattern => new RegExp(pattern));
        } catch (error) {
            console.error('Error loading regex patterns:', error);
            titleRegexList = [];
            categoryRegexList = [];
            tagRegexList = [];
        }

        console.log('Loaded blocked titles:', blockedTitles);
        console.log('Loaded blocked categories:', blockedCategories);
        console.log('Loaded blocked ttags:', blockedTtags);
        console.log('Loaded title regex:', titleRegexList);
        console.log('Loaded category regex:', categoryRegexList);
        console.log('Loaded tag regex:', tagRegexList);
    }

    function saveRegexSettings(type) {
        const container = document.getElementById(`${type}RegexContainer`);
        if (!container) return;

        const patterns = Array.from(container.querySelectorAll('.regex-text'))
            .map(input => input.value)
            .filter(Boolean);

        try {
            const compiledRegexList = patterns.map(pattern => new RegExp(pattern));

            switch(type) {
                case 'title':
                    titleRegexList = compiledRegexList;
                    GM_setValue('titleRegexList', patterns);
                    break;
                case 'category':
                    categoryRegexList = compiledRegexList;
                    GM_setValue('categoryRegexList', patterns);
                    break;
                case 'tag':
                    tagRegexList = compiledRegexList;
                    GM_setValue('tagRegexList', patterns);
                    break;
            }

            resetAndReapplyFilter();
            showNotification('正则表达式设置已保存!', 'success');
        } catch (error) {
            console.error('保存正则表达式时发生错误:', error);
            showNotification('保存正则表达式时发生错误!', 'error');
        }
    }

    function saveTitleKeywords() {
        let hasChanges = false;
        let errorOccurred = false;

        try {
            const titlesElement = document.getElementById('titles');
            if (titlesElement) {
                let newBlockedTitles = titlesElement.value
                    .split(',')
                    .map(title => title.trim())
                    .filter(Boolean);

                if (JSON.stringify(newBlockedTitles) !== JSON.stringify(blockedTitles)) {
                    blockedTitles = newBlockedTitles;
                    GM_setValue('blockedTitles', blockedTitles);
                    hasChanges = true;
                }
            }

            const container = document.getElementById('titleRegexContainer');
            if (container) {
                const patterns = Array.from(container.querySelectorAll('.regex-text'))
                    .map(input => input.value)
                    .filter(Boolean);

                const currentPatterns = titleRegexList.map(r => r.source);
                if (JSON.stringify(patterns) !== JSON.stringify(currentPatterns)) {
                    titleRegexList = patterns.map(pattern => new RegExp(pattern));
                    GM_setValue('titleRegexList', patterns);
                    hasChanges = true;
                }
            }
        } catch (error) {
            console.error("保存标题关键词时发生错误:", error);
            errorOccurred = true;
        }

        if (errorOccurred) {
            showNotification('保存标题关键词时发生错误,请重试!', 'error');
        } else if (hasChanges) {
            loadSettings();
            resetAndReapplyFilter();
            showNotification('标题关键词已成功更新!', 'success');
        } else {
            showNotification('标题关键词无变化!', 'info');
        }
    }

    function saveCategories() {
        let hasChanges = false;
        let errorOccurred = false;

        try {
            const categoriesElement = document.getElementById('categories');
            if (categoriesElement) {
                let newBlockedCategories = categoriesElement.value
                    .split(',')
                    .map(category => category.trim())
                    .filter(Boolean);

                if (JSON.stringify(newBlockedCategories) !== JSON.stringify(blockedCategories)) {
                    blockedCategories = newBlockedCategories;
                    GM_setValue('blockedCategories', blockedCategories);
                    hasChanges = true;
                }
            }

            const container = document.getElementById('categoryRegexContainer');
            if (container) {
                const patterns = Array.from(container.querySelectorAll('.regex-text'))
                    .map(input => input.value)
                    .filter(Boolean);

                const currentPatterns = categoryRegexList.map(r => r.source);
                if (JSON.stringify(patterns) !== JSON.stringify(currentPatterns)) {
                    categoryRegexList = patterns.map(pattern => new RegExp(pattern));
                    GM_setValue('categoryRegexList', patterns);
                    hasChanges = true;
                }
            }
        } catch (error) {
            console.error("保存类别时发生错误:", error);
            errorOccurred = true;
        }

        if (errorOccurred) {
            showNotification('保存类别时发生错误,请重试!', 'error');
        } else if (hasChanges) {
            loadSettings();
            resetAndReapplyFilter();
            showNotification('类别已成功更新!', 'success');
        } else {
            showNotification('类别无变化!', 'info');
        }
    }

    function saveTtags() {
        let hasChanges = false;
        let errorOccurred = false;

        try {
            const tagsElement = document.getElementById('ttags');
            if (tagsElement) {
                let newBlockedTtags = tagsElement.value
                    .split(',')
                    .map(tag => tag.trim())
                    .filter(Boolean);

                if (JSON.stringify(newBlockedTtags) !== JSON.stringify(blockedTtags)) {
                    blockedTtags = newBlockedTtags;
                    GM_setValue('blockedTtags', blockedTtags);
                    hasChanges = true;
                }
            }

            const container = document.getElementById('tagRegexContainer');
            if (container) {
                const patterns = Array.from(container.querySelectorAll('.regex-text'))
                    .map(input => input.value)
                    .filter(Boolean);

                const currentPatterns = tagRegexList.map(r => r.source);
                if (JSON.stringify(patterns) !== JSON.stringify(currentPatterns)) {
                    tagRegexList = patterns.map(pattern => new RegExp(pattern));
                    GM_setValue('tagRegexList', patterns);
                    hasChanges = true;
                }
            }
        } catch (error) {
            console.error("保存标签时发生错误:", error);
            errorOccurred = true;
        }

        if (errorOccurred) {
            showNotification('保存标签时发生错误,请重试!', 'error');
        } else if (hasChanges) {
            loadSettings();
            resetAndReapplyFilter();
            showNotification('标签已成功更新!', 'success');
        } else {
            showNotification('标签无变化!', 'info');
        }
    }

    // ================================
    // ========== 导入/导出相关 =========
    // ================================

    function exportSettings() {
        const settings = {
            blockedTitles: GM_getValue('blockedTitles', []),
            blockedCategories: GM_getValue('blockedCategories', []),
            blockedTtags: GM_getValue('blockedTtags', []),
            titleRegexList: GM_getValue('titleRegexList', []),
            categoryRegexList: GM_getValue('categoryRegexList', []),
            tagRegexList: GM_getValue('tagRegexList', [])
        };

        const blob = new Blob([JSON.stringify(settings, null, 2)], {type: 'application/json'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'linux_do_content_filter_settings.json';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        showNotification('设置已成功导出!', 'success');
    }

    function importSettings(event) {
        const file = event.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = function(e) {
            try {
                const settings = JSON.parse(e.target.result);
                GM_setValue('blockedTitles', settings.blockedTitles || []);
                GM_setValue('blockedCategories', settings.blockedCategories || []);
                GM_setValue('blockedTtags', settings.blockedTtags || []);
                GM_setValue('titleRegexList', settings.titleRegexList || []);
                GM_setValue('categoryRegexList', settings.categoryRegexList || []);
                GM_setValue('tagRegexList', settings.tagRegexList || []);

                loadSettings();
                resetAndReapplyFilter();
                showNotification('设置已成功导入!', 'success');

                const dialog = document.getElementById('settingsDialog');
                if (dialog) {
                    document.getElementById('titles').value = blockedTitles.join(', ');
                    document.getElementById('categories').value = blockedCategories.join(', ');
                    document.getElementById('ttags').value = blockedTtags.join(', ');
                    initRegexInputs('title');
                    initRegexInputs('category');
                    initRegexInputs('tag');
                }
            } catch (error) {
                console.error('导入设置时发生错误:', error);
                showNotification('导入设置失败,请检查文件格式!', 'error');
            }
        };
        reader.readAsText(file);
    }

    // ================================
    // ========== 导入功能相关 ==========
    // ================================

    function importTitleKeywords() {
        const sidebarSection = document.querySelector('li[data-list-item-name="标题关键词"]');
        if (!sidebarSection) {
            showNotification('未找到标题关键词侧边栏。请确保侧边栏已展开并包含"标题关键词"部分!', 'error');
            return;
        }

        const linkElement = sidebarSection.querySelector('a');
        if (!linkElement) {
            showNotification('在侧边栏中未找到标题关键词链接!', 'error');
            return;
        }

        const hrefValue = linkElement.getAttribute('href');
        if (!hrefValue) {
            showNotification('标题关键词链接为空!', 'error');
            return;
        }

        const newKeywords = hrefValue.split(',').map(keyword => keyword.trim()).filter(Boolean);
        const updatedKeywords = [...new Set([...blockedTitles, ...newKeywords])];

        if (JSON.stringify(updatedKeywords) !== JSON.stringify(blockedTitles)) {
            GM_setValue('blockedTitles', updatedKeywords);
            blockedTitles = updatedKeywords;
            resetAndReapplyFilter();
            showNotification(`成功导入 ${newKeywords.length} 个新的标题关键词!`, 'success');
            setTimeout(() => {
                closeDialog();
                showSettingsDialog();
                showNotification('设置弹窗已自动刷新!', 'success');
            }, 1000);
        } else {
            showNotification('标题关键词已是最新!', 'info');
        }
    }

    function importCategoriesFromPage() {
        if (!importCheck()) return;

        let hasChanges = false;
        const mutedCategories = document.querySelector('.tracking-controls__muted-categories .select-kit.multi-select.category-selector.has-selection');

        if (mutedCategories) {
            const newCategories = mutedCategories.querySelector('summary').dataset.name
                .split(',')
                .map(category => category.trim())
                .filter(Boolean);

            const updatedCategories = [...new Set([...blockedCategories, ...newCategories])];
            if (JSON.stringify(updatedCategories) !== JSON.stringify(blockedCategories)) {
                blockedCategories = updatedCategories;
                GM_setValue('blockedCategories', blockedCategories);
                hasChanges = true;
            }
        }

        if (hasChanges) {
            resetAndReapplyFilter();
            showNotification('成功导入类别!', 'success');
            setTimeout(() => {
                closeDialog();
                showSettingsDialog();
                showNotification('设置弹窗已自动刷新!', 'success');
            }, 1000);
        } else {
            showNotification('类别已是最新!', 'info');
        }
    }

    function importTagsFromPage() {
        if (!importCheck()) return;

        let hasChanges = false;
        const mutedTtags = document.querySelector('.tracking-controls__muted-tags .select-kit-header');

        if (mutedTtags) {
            const newTtags = mutedTtags.dataset.name
                .replace(/^"|"$/g, '')
                .split(',')
                .map(tag => tag.trim().replace(/^"|"$/g, ''))
                .filter(Boolean);

            const updatedTtags = [...new Set([...blockedTtags, ...newTtags])];
            if (JSON.stringify(updatedTtags) !== JSON.stringify(blockedTtags)) {
                blockedTtags = updatedTtags;
                GM_setValue('blockedTtags', blockedTtags);
                hasChanges = true;
            }
        }

        if (hasChanges) {
            resetAndReapplyFilter();
            showNotification('成功导入标签!', 'success');
            setTimeout(() => {
                closeDialog();
                showSettingsDialog();
                showNotification('设置弹窗已自动刷新!', 'success');
            }, 1000);
        } else {
            showNotification('标签已是最新!', 'info');
        }
    }

    function importCheck() {
        const currentUrl = window.location.href;
        const settingsUrlRegex = /https:\/\/linux\.do\/u\/([^\/]+)\/preferences\/tracking/;
        const match = currentUrl.match(settingsUrlRegex);

        if (match) {
            const settingsPage = document.querySelector('.user-preferences__tracking-categories-tags-wrapper');
            if (!settingsPage) {
                showNotification('无法找到设置内容,请确保您在正确的[⚙️偏好设置][⚙️跟踪]页!', 'error');
                return false;
            }
            return true;
        } else {
            const username = getUsernameFromUrl();
            if (username) {
                if (confirm('跳转至[⚙️偏好设置->跟踪]页?')) {
                    window.location.href = `https://linux.do/u/${username}/preferences/tracking`;
                }
            } else {
                showNotification('请先前往[⚙️偏好设置]页!', 'error');
            }
            return false;
        }
    }

    // ================================
    // ========== 过滤逻辑核心 =========
    // ================================

    function shouldBlockTitle(title) {
        if (!title) return false;
        const lowerTitle = title.toLowerCase();
        return blockedTitles.some(keyword => lowerTitle.includes(keyword.toLowerCase())) ||
               titleRegexList.some(regex => regex && regex.test(title));
    }

    function shouldBlockCategory(category) {
        if (!category) return false;
        return blockedCategories.includes(category) ||
               categoryRegexList.some(regex => regex && regex.test(category));
    }

    function shouldBlockTag(tag) {
        if (!tag) return false;
        return blockedTtags.includes(tag) ||
               tagRegexList.some(regex => regex && regex.test(tag));
    }

    function filterPosts() {
        const rows = document.querySelectorAll('tr.topic-list-item');
        rows.forEach(row => {
            unhideElement(row);

            const categoryElement = row.querySelector('div.link-bottom-line a.badge-category__wrapper span.badge-category__name');
            const tagElement = row.querySelector('div.link-bottom-line .discourse-tags');
            const titleElement = row.querySelector('a.title');

            if (shouldHideElement(categoryElement, tagElement, titleElement)) {
                softHideElement(row);
            }
        });
    }

    function filterSearchResults() {
        const results = document.querySelectorAll('.fps-result');
        results.forEach(result => {
            unhideElement(result);

            const categoryElement = result.querySelector('.search-category .badge-category__name');
            const tagElement = result.querySelector('.discourse-tags');
            const titleElement = result.querySelector('.topic-title');

            if (shouldHideElement(categoryElement, tagElement, titleElement)) {
                softHideElement(result);
            }
        });
    }

    function shouldHideElement(categoryElement, tagElement, titleElement) {
        if (categoryElement && shouldBlockCategory(categoryElement.textContent.trim())) {
            return true;
        }
        if (tagElement) {
            const tags = Array.from(tagElement.querySelectorAll('a'))
                .map(t => t.textContent.trim());
            if (tags.some(t => shouldBlockTag(t))) {
                return true;
            }
        }
        if (titleElement && shouldBlockTitle(titleElement.textContent)) {
            return true;
        }
        return false;
    }

    function filterContent() {
        if (window.location.pathname.includes('/search')) {
            filterSearchResults();
        } else {
            filterPosts();
        }
    }

    function resetAndReapplyFilter() {
        document.querySelectorAll('tr.topic-list-item').forEach(row => {
            unhideElement(row);
        });
        document.querySelectorAll('.fps-result').forEach(result => {
            unhideElement(result);
        });
        filterContent();
    }

    // ================================
    // ========== UI & 事件部分 ========
    // ================================

    function showSettingsDialog() {
        loadSettings();
        const overlay = document.createElement('div');
        overlay.id = 'settingsOverlay';
        document.body.appendChild(overlay);

        const dialog = document.createElement('div');
        dialog.id = 'settingsDialog';
        dialog.innerHTML = `
            <h2>⚙️ 屏蔽设置</h2>
            <button id="closeDialog">&times;</button>
            <div class="settings-tabs">
                <div class="settings-tab active" data-tab="titles">标题关键词</div>
                <div class="settings-tab" data-tab="categories">类别</div>
                <div class="settings-tab" data-tab="ttags">标签</div>
                <div class="settings-tab" data-tab="other">...</div>
                <div class="settings-tab" data-tab="importExport">同步</div>
            </div>
            <div class="settings-content active" data-content="titles">
                <div class="settings-subtabs">
                    <div class="settings-subtab active" data-subtab="view-titles">查看</div>
                    <div class="settings-subtab" data-subtab="import-titles">导入</div>
                    <div class="settings-subtab" data-subtab="regex-titles">高级自定义</div>
                </div>
                <div class="settings-subcontent active" data-subcontent="view-titles">
                    <label for="titles">🚫 屏蔽的标题关键词(逗号分隔):</label>
                    <textarea id="titles">${blockedTitles.join(', ')}</textarea>
                    <button id="saveTitleKeywords" class="actionButton saveButton">保存设置</button>
                </div>
                <div class="settings-subcontent" data-subcontent="import-titles">
                    <label>⤵️ 从侧边栏导入标题关键词:</label>
                    <p>此操作会将侧边栏中的<br>"标题关键词"对应的链接内容,<br>添加到现有列表中!</p>
                    <p>请先在侧边栏中,<br>自定义一个名为"标题关键词"的链接<br>链接的内容,就填你想屏蔽的标题关键词(逗号分隔)<br>例如:音乐, vpn</p>
                    <p>导入时,请确保:<br>侧边栏中的"标题关键词"部分已展开。</p>
                    <button id="importTitleKeywords" class="actionButton">导入关键词</button>
                </div>
                <div class="settings-subcontent" data-subcontent="regex-titles">
                    <label>🔍 标题关键词过滤规则:</label>
                    <div id="titleRegexContainer"></div>
                    <button id="addTitleRegex" class="actionButton">新增规则</button>
                    <div class="regex-help">
                        <p>正则表达式使用说明:</p>
                        <ul>
                            <li>使用 <code>^</code> 匹配行首,<code>$</code> 匹配行尾</li>
                            <li>使用 <code>.</code> 匹配任意字符,<code>*</code> 表示零次或多次匹配</li>
                            <li>使用 <code>[]</code> 匹配字符集,如 <code>[a-z]</code> 匹配小写字母</li>
                            <li>使用 <code>\\b</code> 匹配单词边界</li>
                        </ul>
                    </div>
                </div>
            </div>
            <div class="settings-content" data-content="categories">
                <div class="settings-subtabs">
                    <div class="settings-subtab active" data-subtab="view-categories">查看</div>
                    <div class="settings-subtab" data-subtab="import-categories">导入</div>
                    <div class="settings-subtab" data-subtab="regex-categories">高级自定义</div>
                </div>
                <div class="settings-subcontent active" data-subcontent="view-categories">
                    <label for="categories">🚫 屏蔽的类别(逗号分隔):</label>
                    <textarea id="categories">${blockedCategories.join(', ')}</textarea>
                    <button id="saveCategories" class="actionButton saveButton">保存设置</button>
                </div>
                <div class="settings-subcontent" data-subcontent="import-categories">
                    <label>⤵️ 通过论坛账号导入类别:</label>
                    <p>此操作会将导入的类别添加到现有列表中!</p>
                    <p>使用论坛账号来同步![偏好设置->跟踪]</p>
                    <button id="importCategories" class="actionButton">导入类别</button>
                </div>
                <div class="settings-subcontent" data-subcontent="regex-categories">
                    <label>🔍 类别过滤规则:</label>
                    <div id="categoryRegexContainer"></div>
                    <button id="addCategoryRegex" class="actionButton">新增规则</button>
                    <div class="regex-help">
                        <p>正则表达式使用说明:</p>
                        <ul>
                            <li>使用 <code>^</code> 匹配行首,<code>$</code> 匹配行尾</li>
                            <li>使用 <code>.</code> 匹配任意字符,<code>*</code> 表示零次或多次匹配</li>
                            <li>使用 <code>[]</code> 匹配字符集,如 <code>[a-z]</code> 匹配小写字母</li>
                            <li>使用 <code>\\b</code> 匹配单词边界</li>
                        </ul>
                    </div>
                </div>
            </div>
            <div class="settings-content" data-content="ttags">
                <div class="settings-subtabs">
                    <div class="settings-subtab active" data-subtab="view-ttags">查看</div>
                    <div class="settings-subtab" data-subtab="import-ttags">导入</div>
                    <div class="settings-subtab" data-subtab="regex-ttags">高级自定义</div>
                </div>
                <div class="settings-subcontent active" data-subcontent="view-ttags">
                    <label for="ttags">🚫 屏蔽的标签(逗号分隔):</label>
                    <textarea id="ttags">${blockedTtags.join(', ')}</textarea>
                    <button id="saveTtags" class="actionButton saveButton">保存设置</button>
                </div>
                <div class="settings-subcontent" data-subcontent="import-ttags">
                    <label>⤵️ 通过论坛账号导入标签:</label>
                    <p>此操作会将导入的标签添加到现有列表中!</p>
                    <p>使用论坛账号来同步![偏好设置->跟踪]</p>
                    <button id="importTtags" class="actionButton">导入标签</button>
                </div>
                <div class="settings-subcontent" data-subcontent="regex-ttags">
                    <label>🔍 标签过滤规则:</label>
                    <div id="tagRegexContainer"></div>
                    <button id="addTagRegex" class="actionButton">新增规则</button>
                    <div class="regex-help">
                        <p>正则表达式使用说明:</p>
                        <ul>
                            <li>使用 <code>^</code> 匹配行首,<code>$</code> 匹配行尾</li>
                            <li>使用 <code>.</code> 匹配任意字符,<code>*</code> 表示零次或多次匹配</li>
                            <li>使用 <code>[]</code> 匹配字符集,如 <code>[a-z]</code> 匹配小写字母</li>
                            <li>使用 <code>\\b</code> 匹配单词边界</li>
                        </ul>
                    </div>
                </div>
            </div>
            <div class="settings-content" data-content="other">
                <div class="settings-subtabs">
                    <div class="settings-subtab active" data-subtab="view-other">查看</div>
                    <div class="settings-subtab" data-subtab="import-other">导入</div>
                </div>
                <div class="settings-subcontent active" data-subcontent="view-other">
                    <label>🚫 屏蔽的...</label>
                    <textarea>还想屏蔽啥?👀</textarea>
                    <button id="saveOther" class="actionButton saveButton">假的保存设置</button>
                </div>
                <div class="settings-subcontent" data-subcontent="import-other">
                    <label>⤵️ 从...导入...:</label>
                    <p>此操作会将...的...添加到现有列表中!</p>
                    <p>请确保...</p>
                    <button id="importOther" class="actionButton">假的导入标签</button>
                </div>
            </div>
            <div class="settings-content" data-content="importExport">
                <div class="settings-subtabs">
                    <div class="settings-subtab active" data-subtab="export">导出</div>
                    <div class="settings-subtab" data-subtab="import">导入</div>
                </div>
                <div class="settings-subcontent active" data-subcontent="export">
                    <label>📤 导出脚本配置:</label>
                    <p>选择一个文件夹,存放当前脚本的配置。</p>
                    <button id="exportSettings" class="actionButton saveButton">导出脚本配置</button>
                </div>
                <div class="settings-subcontent" data-subcontent="import">
                    <label>📥 导入脚本配置:</label>
                    <p>选择一个(之前导出的)脚本配置文件,进行导入。</p>
                    <input type="file" id="importSettingsFile" accept=".json" style="display: none;">
                    <button id="importSettings" class="actionButton">导入脚本配置</button>
                </div>
            </div>
        `;
        document.body.appendChild(dialog);
        overlay.style.display = 'block';

        console.log('Displaying blocked categories:', blockedCategories);
        console.log('Displaying blocked tags:', blockedTtags);

        const notificationPanel = document.createElement('div');
        notificationPanel.id = 'notificationPanel';
        document.body.appendChild(notificationPanel);

        document.getElementById('saveTitleKeywords').addEventListener('click', saveTitleKeywords);
        document.getElementById('saveCategories').addEventListener('click', saveCategories);
        document.getElementById('saveTtags').addEventListener('click', saveTtags);

        document.getElementById('importTitleKeywords').addEventListener('click', importTitleKeywords);
        document.getElementById('importCategories').addEventListener('click', importCategoriesFromPage);
        document.getElementById('importTtags').addEventListener('click', importTagsFromPage);

        document.getElementById('closeDialog').addEventListener('click', closeDialog);

        document.getElementById('exportSettings').addEventListener('click', exportSettings);
        document.getElementById('importSettings').addEventListener('click', () => document.getElementById('importSettingsFile').click());
        document.getElementById('importSettingsFile').addEventListener('change', importSettings);

        initTabSwitching();
        initSubtabSwitching();
        initRegexInputs('title');
        initRegexInputs('category');
        initRegexInputs('tag');

        document.getElementById('addTitleRegex').addEventListener('click', () => addRegexInput('title'));
        document.getElementById('addCategoryRegex').addEventListener('click', () => addRegexInput('category'));
        document.getElementById('addTagRegex').addEventListener('click', () => addRegexInput('tag'));
    }

    function closeDialog() {
        const dialog = document.getElementById('settingsDialog');
        const overlay = document.getElementById('settingsOverlay');
        const notificationPanel = document.getElementById('notificationPanel');

        if (dialog) dialog.remove();
        if (overlay) overlay.remove();
        if (notificationPanel) notificationPanel.remove();
    }

    function initTabSwitching() {
        const tabs = document.querySelectorAll('.settings-tab');
        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                tabs.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                const contents = document.querySelectorAll('.settings-content');
                contents.forEach(content => {
                    content.classList.remove('active');
                    if (content.dataset.content === tab.dataset.tab) {
                        content.classList.add('active');
                    }
                });
            });
        });
    }

    function initSubtabSwitching() {
        const subtabs = document.querySelectorAll('.settings-subtab');
        subtabs.forEach(subtab => {
            subtab.addEventListener('click', () => {
                const parentContent = subtab.closest('.settings-content');
                const siblingSubtabs = parentContent.querySelectorAll('.settings-subtab');
                siblingSubtabs.forEach(t => t.classList.remove('active'));
                subtab.classList.add('active');
                const subcontents = parentContent.querySelectorAll('.settings-subcontent');
                subcontents.forEach(sc => {
                    sc.classList.remove('active');
                    if (sc.dataset.subcontent === subtab.dataset.subtab) {
                        sc.classList.add('active');
                    }
                });
            });
        });
    }

    function initRegexInputs(type) {
        const container = document.getElementById(`${type}RegexContainer`);
        const regexList = type === 'title' ? titleRegexList :
                          (type === 'category' ? categoryRegexList : tagRegexList);

        if (!container) return;
        container.innerHTML = '';
        regexList.forEach(regex => {
            if (regex) {
                addRegexInput(type, regex.source);
            }
        });
    }

    function addRegexInput(type, value = '') {
        const container = document.getElementById(`${type}RegexContainer`);
        if (!container) return;
        const inputGroup = document.createElement('div');
        inputGroup.className = 'regex-input';
        inputGroup.innerHTML = `
            <input type="text"
                   class="regex-text"
                   value="${value}"
                   placeholder="输入正则表达式"
                   aria-label="输入正则表达式">
            <select class="regex-preset" aria-label="过滤规则模版">
                <option value="">🔧 过滤规则模版:</option>
                ${Object.entries(regexPresets)
                    .map(([name, pattern]) =>
                        `<option value="${pattern}">${name}</option>`)
                    .join('')}
            </select>
            <button class="delete-btn" aria-label="删除">删除</button>
        `;
        setupRegexInputEvents(inputGroup, type);
        container.appendChild(inputGroup);

        setTimeout(() => {
            inputGroup.style.opacity = '1';
            inputGroup.style.transform = 'translateX(0)';
        }, 10);
    }

    function setupRegexInputEvents(inputGroup, type) {
        const textInput = inputGroup.querySelector('.regex-text');
        const presetSelect = inputGroup.querySelector('.regex-preset');
        const deleteBtn = inputGroup.querySelector('.delete-btn');

        presetSelect.addEventListener('change', () => {
            textInput.value = presetSelect.value;
            validateAndSaveRegex(textInput, type);
        });

        textInput.addEventListener('input', () => {
            validateAndSaveRegex(textInput, type);
        });

        deleteBtn.addEventListener('click', () => {
            inputGroup.style.opacity = '0';
            inputGroup.style.transform = 'translateX(20px)';
            setTimeout(() => {
                inputGroup.remove();
                saveRegexSettings(type);
            }, 300);
        });
    }

    function validateAndSaveRegex(input, type) {
        removeRegexError(input);
        try {
            if (input.value) {
                new RegExp(input.value);
                input.style.borderColor = '#28a745';
                saveRegexSettings(type);
            } else {
                input.style.borderColor = '#ddd';
            }
        } catch (e) {
            showRegexError(input, '无效的正则表达式');
            input.style.borderColor = '#dc3545';
        }
    }

    function showRegexError(input, message) {
        removeRegexError(input);
        const error = document.createElement('div');
        error.className = 'regex-error';
        error.textContent = message;
        input.parentElement.appendChild(error);
    }

    function removeRegexError(input) {
        const errorElement = input.parentElement.querySelector('.regex-error');
        if (errorElement) {
            errorElement.remove();
        }
    }

    // ================================
    // ========== 优化:统一观察 =========
    // ================================
    let debounceTimer = null;
    function debounceFilter() {
        if (debounceTimer) {
            clearTimeout(debounceTimer);
        }
        debounceTimer = setTimeout(() => {
            filterContent();
        }, 100);
    }

    function observeDomChanges() {
        const mainContainer = document.querySelector('#main-outlet') || document.body;
        if (!mainContainer) return;

        const observer = new MutationObserver((mutations) => {
            let foundSignificantChange = false;
            for (const mutation of mutations) {
                if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
                    foundSignificantChange = true;
                    break;
                }
            }
            if (foundSignificantChange) {
                debounceFilter();
            }
        });

        observer.observe(mainContainer, {
            childList: true,
            subtree: true
        });
    }

    // ================================
    // ========== 核心初始化 ===========
    // ================================
    function init() {
        loadSettings();
        // 立即尝试过滤一次(可能当前已有页面内容)
        filterContent();
        // 监听 DOM 变动,以后若有新内容出现,马上过滤
        observeDomChanges();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 注:以下 popstate/pageshow 在 SPA 下往往不稳定,仅作fallback
    window.addEventListener('popstate', () => {
        console.log('popstate event triggered, re-initializing...');
        init();
    });
    window.addEventListener('pageshow', () => {
        console.log('pageshow event triggered, re-initializing...');
        init();
    });

    // 注册菜单命令,打开设置弹窗
    GM_registerMenuCommand('⚙️ 屏蔽设置', showSettingsDialog);

    // 注入样式
    GM_addStyle(`
        #settingsOverlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9998;
            display: none;
        }
        #settingsDialog {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 2px solid rgba(137, 207, 240, 0.6);
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
            z-index: 9999;
            min-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            padding: 20px;
            color: #333;
        }
        #settingsDialog h2 {
            margin: 0 0 20px 0;
            color: #007bff;
            font-size: 24px;
            text-align: center;
        }
        #settingsDialog textarea {
            width: 100%;
            height: 200px;
            overflow-y: auto;
            margin-bottom: 15px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 8px;
            font-size: 14px;
            resize: vertical;
            background: white;
            color: #333;
        }
        #settingsDialog button {
            padding: 8px 16px;
            border: none;
            border-radius: 20px;
            color: white;
            cursor: pointer;
            transition: all 0.3s ease;
            font-size: 14px;
            font-weight: bold;
            margin-right: 10px;
            margin-bottom: 10px;
        }
        #settingsDialog button:hover {
            opacity: 0.9;
            transform: translateY(-2px);
        }
        .actionButton {
            background-color: #007bff;
            position: fixed;
            bottom: 10px;
            right: 40px;
            box-shadow: 0 2px 10px rgba(0, 123, 255, 0.3);
        }
        .saveButton {
            background-color: #28a745;
        }
        #closeDialog {
            position: absolute;
            top: 10px;
            right: 10px;
            background: red;
            border: none;
            font-size: 24px;
            color: white;
            cursor: pointer;
            padding: 0;
            margin: 0;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            transition: background-color 0.3s ease;
        }
        .settings-tabs {
            display: flex;
            justify-content: space-around;
            margin-bottom: 10px;
            border-bottom: 2px solid #e9ecef;
        }
        .settings-tab {
            padding: 10px;
            cursor: pointer;
            border-bottom: 3px solid transparent;
            transition: all 0.3s ease;
            font-size: 16px;
            font-weight: bold;
            color: #495057;
        }
        .settings-tab:hover {
            color: #007bff;
        }
        .settings-tab.active {
            color: #007bff;
            border-bottom-color: #007bff;
        }
        .settings-subtabs {
            display: flex;
            justify-content: flex-start;
        }
        .settings-subtab {
            padding: 6px 12px;
            cursor: pointer;
            border: 1px solid #dee2e6;
            border-radius: 15px;
            transition: all 0.3s ease;
            font-size: 14px;
            margin-right: 10px;
            background-color: #f8f9fa;
        }
        .settings-subtab:hover {
            background-color: #e9ecef;
        }
        .settings-subtab.active {
            background-color: #007bff;
            color: white;
            border-color: #007bff;
        }
        .settings-content,
        .settings-subcontent {
            display: none;
            padding: 15px;
            background-color: #f8f9fa;
            border-radius: 8px;
        }
        .settings-content.active,
        .settings-subcontent.active {
            display: block;
        }
        .settings-content label,
        .settings-subcontent label {
            display: block;
            margin-bottom: 10px;
            font-weight: bold;
            color: #495057;
        }
        .regex-input {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 15px;
            padding: 12px;
            border-radius: 8px;
            background: rgba(0, 0, 0, 0.03);
            transition: all 0.3s ease;
        }
        .regex-input:hover {
            background: rgba(0, 0, 0, 0.05);
        }
        .regex-input input[type="text"] {
            flex: 1;
            height: 36px;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
            transition: all 0.3s ease;
        }
        .regex-input input[type="text"]:focus {
            border-color: #007bff;
            box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
            outline: none;
        }
        .regex-input select {
            width: auto;
            min-width: 130px;
            height: 36px;
            padding: 0 8px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
            background-color: white;
            cursor: pointer;
        }
        .regex-input .delete-btn {
            padding: 8px 12px;
            border: none;
            border-radius: 6px;
            background-color: #dc3545;
            color: white;
            font-size: 14px;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        .regex-input .delete-btn:hover {
            background-color: #c82333;
            transform: translateY(-1px);
        }
        .regex-error {
            color: #dc3545;
            font-size: 12px;
            margin-top: 4px;
        }
        .regex-help {
            font-size: 12px;
            color: #666;
            margin-top: 5px;
        }
        #notificationPanel {
            position: fixed;
            bottom: 15%;
            left: 45%;
            padding: 12px 24px;
            border-radius: 8px;
            color: white;
            font-weight: 500;
            z-index: 10000;
            opacity: 0;
            transform: translateY(20px);
            transition: all 0.3s ease;
        }
        #notificationPanel.show {
            opacity: 1;
            transform: translateY(0);
        }
        @media (prefers-color-scheme: dark) {
            #settingsDialog {
                background: #2c2c2c;
                color: #e0e0e0;
                border-color: rgba(77, 166, 255, 0.6);
            }
            #settingsDialog h2 {
                color: #4da6ff;
            }
            #settingsDialog textarea,
            #settingsDialog input[type="text"] {
                background: #3a3a3a;
                color: #e0e0e0;
                border-color: #555;
            }
            #closeDialog {
                color: black;
            }
            .settings-tab {
                color: #bbb;
            }
            .settings-tab:hover {
                color: #4da6ff;
            }
            .settings-tab.active {
                color: #4da6ff;
                border-bottom-color: #4da6ff;
            }
            .settings-subtab {
                background-color: #3a3a3a;
                border-color: #555;
                color: #e0e0e0;
            }
            .settings-subtab:hover {
                background-color: #4a4a4a;
            }
            .settings-subtab.active {
                background-color: #4da6ff;
                color: #2c2c2c;
            }
            .settings-content,
            .settings-subcontent {
                background-color: #3a3a3a;
            }
            .settings-content label,
            .settings-subcontent label {
                color: #bbb;
            }
            .regex-help {
                color: #aaa;
            }
            .regex-input {
                background: rgba(255, 255, 255, 0.05);
            }
            .regex-input:hover {
                background: rgba(255, 255, 255, 0.08);
            }
            .regex-input input[type="text"],
            .regex-input select {
                background-color: #3a3a3a;
                border-color: #555;
                color: #e0e0e0;
            }
            .regex-input select option {
                background-color: #2c2c2c;
                color: #e0e0e0;
            }
        }
    `);

})();

20250309

8 个赞

感谢大佬,一会尝试一下。每天好多抽奖帖子,实在是不想看到:face_blowing_a_kiss::face_blowing_a_kiss::face_blowing_a_kiss:

1 个赞

感谢大佬

多谢大佬分享

感谢佬友,

此话题已在最后回复的 30 天后被自动关闭。不再允许新回复。