Change clicked link's color

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();