已访问链接样式修改(带预设和自定义)

让点击过的链接变色并显示下划线,支持预设样式和自定义(含中文输入),支持鼠标中键点击和按钮内链接

// ==UserScript==
// @name         已访问链接样式修改(带预设和自定义)
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  让点击过的链接变色并显示下划线,支持预设样式和自定义(含中文输入),支持鼠标中键点击和按钮内链接
// @license      MIT
// @author       zzzwq&AI
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @icon         https://i0.hdslb.com/bfs/face/42998afc31c623fae26bd04ee5d9e11d7b778de7.jpg
// ==/UserScript==

(function() {
    'use strict';

    // 预设选项(保留字体粗度不变)
    const presets = {
        '红色实线': { color: '#FF0000', underline: 'solid' },
        '蓝色虚线': { color: '#0000FF', underline: 'dashed' },
        '绿色波浪线': { color: '#00FF00', underline: 'wavy' },
        '紫色双下划线': { color: '#800080', underline: 'double' },
        '橙色点状线': { color: '#FFA500', underline: 'dotted' }
    };

    // 中文映射表(保留字体粗度不变)
    const colorMap = {
        '红色': '#FF0000',
        '蓝色': '#0000FF',
        '绿色': '#00FF00',
        '黄色': '#FFFF00',
        '黑色': '#000000',
        '白色': '#FFFFFF',
        '灰色': '#808080',
        '橙色': '#FFA500',
        '紫色': '#800080',
        '青色': '#00FFFF',
        '品红': '#FF00FF',
        '透明': 'transparent',
        'red': '#FF0000',
        'blue': '#0000FF',
        'green': '#00FF00',
        'yellow': '#FFFF00',
        'black': '#000000',
        'white': '#FFFFFF',
        'gray': '#808080',
        'orange': '#FFA500',
        'purple': '#800080',
        'cyan': '#00FFFF',
        'magenta': '#FF00FF',
        'transparent': 'transparent'
    };

    const underlineMap = {
        '实线': 'solid',
        '虚线': 'dashed',
        '波浪线': 'wavy',
        '双线': 'double',
        '点状线': 'dotted',
        '无下划线': 'none',
        'solid': 'solid',
        'dashed': 'dashed',
        'wavy': 'wavy',
        'double': 'double',
        'dotted': 'dotted',
        'none': 'none'
    };

    // 独立的字体粗度设置(新增独立存储键)
    const defaultSettings = {
        color: GM_getValue('linkColor', '#FF0000'),
        underline: GM_getValue('underlineType', 'solid'),
        weight: GM_getValue('linkWeight', '400')  // 独立存储
    };

    // 获取当前设置(保持独立)
    let settings = {
        color: defaultSettings.color,
        underline: defaultSettings.underline,
        weight: defaultSettings.weight
    };

    // 生成提示信息的辅助函数 - 优化显示
    function generatePromptMessage(map, type) {
        // 优化显示:分成两列
        const keys = Object.keys(map);
        const half = Math.ceil(keys.length / 2);
        let message = '支持输入以下值(中文/英文均可):\n';
        
        for (let i = 0; i < half; i++) {
            const key1 = keys[i];
            const key2 = keys[i + half];
            const value1 = map[key1];
            const value2 = key2 ? map[key2] : '';
            
            // 格式化显示为两列
            const col1 = `${key1} (${value1})`;
            const col2 = key2 ? `${key2} (${value2})` : '';
            message += `${col1.padEnd(20)}${col2}\n`;
        }
        
        message += `\n其他格式:${type === 'color' ? '十六进制/RGB' : 'CSS标准值'}`;
        return message;
    }

    // 更新样式(保持独立)
    function updateStyles() {
        GM_addStyle(`
            .visited-link {
                color: ${settings.color} !important;
                text-decoration: ${settings.underline} underline !important;
                font-weight: ${settings.weight} !important;
            }
        `);
    }

    // 注册设置菜单
    function registerMenuCommands() {
        // 添加预设选项(不修改字体粗度)
        Object.entries(presets).forEach(([name, style]) => {
            GM_registerMenuCommand(`预设:${name}`, () => {
                settings = {
                    ...settings,
                    color: style.color,
                    underline: style.underline
                };
                GM_setValue('linkColor', style.color);
                GM_setValue('underlineType', style.underline);
                updateStyles();
            });
        });

        // 自定义颜色(支持中文)
        GM_registerMenuCommand('自定义颜色', () => {
            const promptMessage = generatePromptMessage(colorMap, 'color');
            const input = prompt(promptMessage, settings.color);
            if (input) {
                const color = colorMap[input] || input;
                if (isValidColor(color)) {
                    settings.color = color;
                    GM_setValue('linkColor', color);
                    updateStyles();
                } else {
                    alert('无效的颜色值!');
                }
            }
        });

        // 自定义下划线(支持中文)
        GM_registerMenuCommand('自定义下划线', () => {
            const promptMessage = generatePromptMessage(underlineMap, 'underline');
            const input = prompt(promptMessage, settings.underline);
            if (input) {
                const underline = underlineMap[input] || input;
                if (isValidUnderline(underline)) {
                    settings.underline = underline;
                    GM_setValue('underlineType', underline);
                    updateStyles();
                } else {
                    alert('无效的下划线样式!');
                }
            }
        });

        // 自定义字体粗度(独立设置)
        GM_registerMenuCommand('自定义字体粗度', () => {
            const input = prompt('请输入字体粗度(100-900之间的整数):', settings.weight);
            if (input) {
                const weight = parseInt(input);
                if (weight >= 100 && weight <= 900 && !isNaN(weight)) {
                    settings.weight = weight.toString();
                    GM_setValue('linkWeight', weight);
                    updateStyles();
                } else {
                    alert('请输入100到900之间的整数!');
                }
            }
        });

        // 清除已访问链接记录
        GM_registerMenuCommand('清除已访问链接记录', () => {
            if (confirm('确定要清除所有已访问链接的记录吗?')) {
                localStorage.removeItem('visitedLinks');
                document.querySelectorAll('.visited-link').forEach(link => {
                    link.classList.remove('visited-link');
                });
            }
        });
    }

    // 验证函数
    function isValidColor(value) {
        return typeof value === 'string' && 
               (colorMap[value] || CSS.supports('color', value));
    }

    function isValidUnderline(value) {
        const validValues = new Set(['solid', 'dashed', 'wavy', 'double', 'dotted', 'none']);
        return validValues.has(value) || CSS.supports('text-decoration-line', value);
    }

    // 初始化
    function init() {
        updateStyles();
        registerMenuCommands();

        // 使用Set存储已访问链接
        let visitedLinks = new Set(JSON.parse(localStorage.getItem('visitedLinks')) || []);

        // 初始化页面样式
        function applyVisitedStyles() {
            document.querySelectorAll('a').forEach(link => {
                const href = link.href;
                if (visitedLinks.has(href)) {
                    link.classList.add('visited-link');
                }
            });
        }

        // 保存到本地存储
        function saveToStorage() {
            localStorage.setItem('visitedLinks', JSON.stringify([...visitedLinks]));
        }

        // 处理链接标记的通用函数
        function markLinkAsVisited(link) {
            const href = link.href;
            if (!visitedLinks.has(href)) {
                visitedLinks.add(href);
                saveToStorage();
            }
            link.classList.add('visited-link');
        }

        // 点击事件处理 - 支持左键和鼠标中键
        document.addEventListener('click', function(e) {
            // 处理左键点击
            handleLinkInteraction(e.target);
        });

        // 中键点击事件处理
        document.addEventListener('auxclick', function(e) {
            if (e.button === 1) { // 鼠标中键
                handleLinkInteraction(e.target);
            }
        });

        // 按钮点击事件处理
        document.addEventListener('click', function(e) {
            const button = e.target.closest('button');
            if (button) {
                // 查找按钮内的链接
                const linkInButton = button.querySelector('a[href]');
                if (linkInButton) {
                    markLinkAsVisited(linkInButton);
                }
            }
        });

        // 处理链接交互的通用函数
        function handleLinkInteraction(target) {
            // 向上查找最近的链接
            let link = target.closest('a');
            if (link && link.href) {
                markLinkAsVisited(link);
            }
        }

        // 初始应用样式
        applyVisitedStyles();

        // 监听DOM变化
        const observer = new MutationObserver(applyVisitedStyles);
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    init();
})();