Change clicked link's color

Change color of clicked links in body with color presets and custom hex input

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Change clicked link's color
// @license      MIT
// @namespace    http://tampermonkey.net/
// @author       [email protected]
// @homepageURL  https://greasyfork.org/vi/scripts/501244-change-clicked-link-s-color
// @version      1.2.6
// @description  Change color of clicked links in body with color presets and custom hex input
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @icon         https://cdn-icons-png.flaticon.com/512/4906/4906292.png
// ==/UserScript==

(function() {
    'use strict';

    // Config
    let p_color_clicked = GM_getValue('colorClicked', '#800080');
    let p_apply_all = GM_getValue('applyAll', true);
    let p_apply_domains = GM_getValue('applyDomains', '');

    // Variables
    const style_id = "clicked-link-color-style";
    const css_a_clicked = `
        a:visited:not(nav a):not(.nav a):not(.navbar a):not(.menu a):not(.navigation a),
        a:visited:not(nav a):not(.nav a):not(.navbar a):not(.menu a):not(.navigation a) *,
        a.custom-visited,
        a.custom-visited * {
            color: %COLOR% !important;
        }`;
    const colorPresets = {
        'Purple': '#800080',
        'Red': '#FF0000',
        'Blue': '#0000FF',
        'Green': '#008000',
        'Orange': '#FFA500',
        'Pink': '#FFC0CB',
        'Brown': '#A52A2A',
        'Gray': '#808080',
        'Cyan': '#00FFFF',
        'Magenta': '#FF00FF',
        'Lime': '#00FF00'
    };

    // Functions
    function isDomainApplied(domains, site) {
        if (p_apply_all) return true;
        if (domains.trim() === '') return false;
        let domainList = domains.split(",");
        return domainList.some(domain => site.includes(domain.trim()));
    }

    function addStyle(css) {
        let style = document.getElementById(style_id);
        if (style === null) {
            let head = document.getElementsByTagName("head")[0];
            style = document.createElement("style");
            style.setAttribute("id", style_id);
            style.setAttribute("type", "text/css");
            head.appendChild(style);
        }
        style.textContent = css;
    }

    function assignColor(css, color) {
        return css.replace(/%COLOR%/ig, color);
    }

    function main() {
        let url = document.documentURI;
        let css = assignColor(css_a_clicked, p_color_clicked);
        if (isDomainApplied(p_apply_domains, url)) {
            addStyle(css);
            addClickListener();
        }
    }

    function addClickListener() {
        document.body.addEventListener('click', (e) => {
            if (e.target.tagName === 'A') {
                e.target.classList.add('custom-visited');
            }
        });
    }

    function observeDOMChanges() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    main();
                }
            });
        });

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

    function createDialog(id, content) {
        const dialog = document.createElement('dialog');
        dialog.className = 'custom-dialog';
        dialog.id = id;
        dialog.innerHTML = `
            <form method="dialog">
                ${content}
            </form>
        `;
        document.body.appendChild(dialog);

        dialog.style.position = 'fixed';
        dialog.style.left = '50%';
        dialog.style.top = '50%';
        dialog.style.transform = 'translate(-50%, -50%)';
        dialog.style.maxWidth = '400px';
        dialog.style.width = '90%';
        dialog.style.border = '2px solid #007bff';
        dialog.style.borderRadius = '8px';
        dialog.style.padding = '16px';
        dialog.style.backgroundColor = '#fff';
        dialog.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        dialog.style.overflow = 'hidden';
        dialog.showModal();
        return dialog;
    }

    function showColorSelector() {
        const content = `
            <h3>Change Clicked Link's Color</h3>
            <div class="section">
                <label for="colorPreset">Choose a preset color:</label>
                <select id="colorPreset" style="width: 100%;">
                    <option value="">-- Choose a preset color --</option>
                    ${Object.entries(colorPresets).map(([name, value]) =>
                        `<option value="${value}" style="background-color: ${value}; color: white;">${name} (${value})</option>`
                    ).join('')}
                </select>
            </div>
            <div class="section">
                <label for="customColor">Or enter a custom hex color:</label>
                <div style="display: flex; align-items: center; gap: 10px;">
                    <input type="text" id="customColor" placeholder="#RRGGBB" pattern="^#[0-9A-Fa-f]{6}$" style="flex: 1; height: 2em; border: 2px solid #007bff; border-radius: 4px;">
                    <input type="color" id="colorPicker" value="${p_color_clicked}" style="height: 2em; border: 2px solid #007bff; border-radius: 4px;">
                </div>
            </div>
            <div class="button-container" style="margin-top: 20px;">
                <button type="submit" id="applyBtn" style="background-color: #007bff; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; margin-right: 10px;">Apply</button>
                <button type="button" id="cancelBtn" style="background-color: #ccc; color: #000; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">Cancel</button>
            </div>
        `;

        const dialog = createDialog('colorDialog', content);
        const colorPreset = dialog.querySelector('#colorPreset');
        const customColor = dialog.querySelector('#customColor');
        const colorPicker = dialog.querySelector('#colorPicker');
        const applyBtn = dialog.querySelector('#applyBtn');
        const cancelBtn = dialog.querySelector('#cancelBtn');

        colorPreset.value = p_color_clicked;
        customColor.value = p_color_clicked;
        colorPicker.value = p_color_clicked;

        colorPreset.addEventListener('change', function() {
            if (this.value) {
                customColor.value = this.value;
                colorPicker.value = this.value;
            }
        });

        colorPicker.addEventListener('input', function() {
            customColor.value = this.value;
        });

        cancelBtn.addEventListener('click', () => dialog.close());

        dialog.addEventListener('close', () => {
            if (dialog.returnValue !== 'cancel') {
                const newColor = customColor.value;
                if (/^#[0-9A-Fa-f]{6}$/i.test(newColor)) {
                    p_color_clicked = newColor;
                    GM_setValue('colorClicked', p_color_clicked);
                    main();
                } else {
                    alert("Invalid color code. Please use hex format (e.g., #800080).");
                }
            }
        });
    }

    function showDomainSettings() {
        const content = `
            <h3>Manage Enabled Domains</h3>
            <div class="section">
                <label for="enabledSites">Enable on these sites (one per line):</label>
                <textarea id="enabledSites" rows="10" style="width: 100%; border: 2px solid #007bff; border-radius: 4px; padding: 8px; box-sizing: border-box;">${p_apply_domains.replace(/,/g, '\n')}</textarea>
            </div>
            <div class="section" style="margin-top: 10px;">
                <label>
                    <input type="checkbox" id="enableAllSites" ${p_apply_all ? 'checked' : ''}> Apply to all websites
                </label>
            </div>
            <div class="button-container" style="display: flex; justify-content: space-between; margin-top: 20px;">
                <button type="button" id="addThisSite" style="background-color: #28a745; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">Add this site</button>
                <div class="button-group" style="display: flex; gap: 10px;">
                    <button type="submit" style="background-color: #007bff; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">Save</button>
                    <button type="button" id="cancelBtn" style="background-color: #ccc; color: #000; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">Cancel</button>
                </div>
            </div>
        `;

        const dialog = createDialog('domainDialog', content);
        const enabledSitesTextarea = dialog.querySelector('#enabledSites');
        const addThisSiteBtn = dialog.querySelector('#addThisSite');
        const saveBtn = dialog.querySelector('button[type="submit"]');
        const cancelBtn = dialog.querySelector('#cancelBtn');
        const enableAllSitesCheckbox = dialog.querySelector('#enableAllSites');

        addThisSiteBtn.addEventListener('click', () => {
            const currentDomain = window.location.hostname.replace(/^www\./, '');
            const domainList = enabledSitesTextarea.value.split('\n').map(site => site.trim()).filter(Boolean);

            if (!domainList.includes(currentDomain)) {
                domainList.push(currentDomain);
                enabledSitesTextarea.value = domainList.join('\n');
            }
        });

        cancelBtn.addEventListener('click', () => dialog.close('cancel'));

        dialog.addEventListener('close', () => {
            if (dialog.returnValue !== 'cancel') {
                const newEnabledSites = enabledSitesTextarea.value.split('\n').map(site => site.trim()).filter(Boolean);
                const newEnableAllSites = enableAllSitesCheckbox.checked;

                if (JSON.stringify(newEnabledSites) !== JSON.stringify(p_apply_domains.split(',')) ||
                    newEnableAllSites !== p_apply_all) {
                    p_apply_domains = newEnabledSites.join(',');
                    p_apply_all = newEnableAllSites;
                    GM_setValue('applyDomains', p_apply_domains);
                    GM_setValue('applyAll', p_apply_all);
                    main();
                }
            }
        });
    }

    // Menu commands
    GM_registerMenuCommand("🎨 Change clicked link's color", showColorSelector, "C");
    GM_registerMenuCommand("🌐 Domain settings", showDomainSettings, "D");

    // Run main function immediately
    main();

    // Observe DOM changes
    observeDOMChanges();
})();