Change clicked link's color

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

当前为 2024-08-31 提交的版本,查看 最新版本

// ==UserScript==
// @name         Change clicked link's color
// @license      MIT
// @namespace    http://tampermonkey.net/
// @author       [email protected]
// @homepageURL  https://gf.qytechs.cn/vi/scripts/501244-change-clicked-link-s-color
// @version      1.2.1
// @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';

    const ClickedLinkColor = {
        // Config
        p_color_clicked: GM_getValue('colorClicked', '#800080'),
        p_apply_all: GM_getValue('applyAll', true),
        p_apply_domains: GM_getValue('applyDomains', ''),

        // Constants
        style_id: "clicked-link-color-style",
        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;
            }`,
        colorPresets: {
            'Purple': '#800080',
            'Red': '#FF0000',
            'Blue': '#0000FF',
            'Green': '#008000',
            'Orange': '#FFA500',
            'Pink': '#FFC0CB',
            'Brown': '#A52A2A',
            'Gray': '#808080',
            'Cyan': '#00FFFF',
            'Magenta': '#FF00FF',
            'Lime': '#00FF00'
        },

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

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

        main: function() {
            try {
                let url = document.documentURI;
                let css = this.assignColor(this.css_a_clicked, this.p_color_clicked);
                if (this.isDomainApplied(this.p_apply_domains, url)) {
                    GM_addStyle(css);
                    this.addClickListener();
                }
            } catch (error) {
                console.error("ClickedLinkColor main error:", error);
            }
        },

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

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

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

        createDialog: function(id, content) {
            const wrapper = document.createElement('div');
            wrapper.attachShadow({ mode: 'closed' });
            
            const dialog = document.createElement('dialog');
            dialog.id = id;
            
            const style = document.createElement('style');
            style.textContent = `
                :host {
                    all: initial;
                    display: block;
                }
                dialog {
                    all: initial;
                    font-family: Arial, sans-serif;
                    position: fixed;
                    left: 50%;
                    top: 50%;
                    transform: translate(-50%, -50%);
                    max-width: 400px;
                    width: 90%;
                    border: 2px solid #007bff;
                    border-radius: 8px;
                    padding: 16px;
                    background-color: #fff;
                    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
                    z-index: 2147483647;
                }
                dialog::backdrop {
                    background-color: rgba(0, 0, 0, 0.5);
                }
                form {
                    margin: 0;
                    padding: 0;
                }
                h3 {
                    margin-top: 0;
                    color: #333;
                }
                input, select, textarea, button {
                    font-family: inherit;
                    font-size: 14px;
                    margin: 5px 0;
                    padding: 5px;
                    box-sizing: border-box;
                }
                button {
                    cursor: pointer;
                    padding: 8px 16px;
                    border-radius: 4px;
                    border: none;
                }
                #applyBtn {
                    background-color: #007bff;
                    color: #fff;
                }
                #cancelBtn {
                    background-color: #ccc;
                    color: #000;
                }
            `;
            
            dialog.innerHTML = `
                <form method="dialog">
                    ${content}
                </form>
            `;
            
            wrapper.shadowRoot.appendChild(style);
            wrapper.shadowRoot.appendChild(dialog);
            document.body.appendChild(wrapper);
            
            dialog.showModal();
            return dialog;
        },

        showColorSelector: function() {
            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(this.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;">
                        <input type="color" id="colorPicker" value="${this.p_color_clicked}">
                    </div>
                </div>
                <div class="button-container" style="margin-top: 20px;">
                    <button type="submit" id="applyBtn">Apply</button>
                    <button type="button" id="cancelBtn">Cancel</button>
                </div>
            `;

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

            colorPreset.value = this.p_color_clicked;
            customColor.value = this.p_color_clicked;
            colorPicker.value = this.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('cancel'));

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

        showDomainSettings: function() {
            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%;">${this.p_apply_domains.replace(/,/g, '\n')}</textarea>
                </div>
                <div class="section" style="margin-top: 10px;">
                    <label>
                        <input type="checkbox" id="enableAllSites" ${this.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">Add this site</button>
                    <div class="button-group" style="display: flex; gap: 10px;">
                        <button type="submit" id="applyBtn">Save</button>
                        <button type="button" id="cancelBtn">Cancel</button>
                    </div>
                </div>
            `;

            const dialog = this.createDialog('domainDialog', content);
            const enabledSitesTextarea = dialog.querySelector('#enabledSites');
            const addThisSiteBtn = dialog.querySelector('#addThisSite');
            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(this.p_apply_domains.split(',')) ||
                        newEnableAllSites !== this.p_apply_all) {
                        this.p_apply_domains = newEnabledSites.join(',');
                        this.p_apply_all = newEnableAllSites;
                        GM_setValue('applyDomains', this.p_apply_domains);
                        GM_setValue('applyAll', this.p_apply_all);
                        this.main();
                    }
                }
                document.body.removeChild(dialog.parentNode);
            });
        },

        init: function() {
            // Menu commands
            GM_registerMenuCommand("🎨 Change clicked link's color", () => this.showColorSelector(), "C");
            GM_registerMenuCommand("🌐 Domain settings", () => this.showDomainSettings(), "D");

            // Run main function immediately
            this.main();

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

    // Initialize the script
    ClickedLinkColor.init();
})();

QingJ © 2025

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