Floating Link Menu

Customizable link menu.

当前为 2025-08-02 提交的版本,查看 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Floating Link Menu
// @namespace    http://tampermonkey.net/
// @version      2.2 universal
// @description  Customizable link menu.
// @author       echoZ
// @license      MIT
// @match        *://*/*
// @exclude      *://*routerlogin.net/*
// @exclude      *://*192.168.1.1/*
// @exclude      *://*192.168.0.1/*
// @exclude      *://*my.bankofamerica.com/*
// @exclude      *://*wellsfargo.com/*
// @exclude      *://*chase.com/*
// @exclude      *://*citibank.com/*
// @exclude      *://*online.citi.com/*
// @exclude      *://*capitalone.com/*
// @exclude      *://*usbank.com/*
// @exclude      *://*paypal.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // --- SCRIPT EXCLUSION LOGIC ---
    const excludedDomainsStorageKey = 'excludedUniversalDomains';
    const isBubbleHiddenStorageKey = 'isBubbleHidden';

    function getExcludedDomains() {
        const storedDomains = localStorage.getItem(excludedDomainsStorageKey);
        return storedDomains ? JSON.parse(storedDomains) : [];
    }

    const excludedDomains = getExcludedDomains();
    const currentUrl = window.location.href;
    const isExcluded = excludedDomains.some(domain => currentUrl.includes(domain));
    if (isExcluded) {
        return;
    }
    // --- END EXCLUSION LOGIC ---

    // --- UNIFIED LINKS & STATE ---
    const storageKey = 'universalLinkManagerLinks';
    let isDeleteMode = false;
    let isExcludeDeleteMode = false;
    let isExportMode = false;
    let isImportMode = false;
    let clickCount = 0;
    let clickTimer = null;

    function getBubbleHiddenState() {
        return localStorage.getItem(isBubbleHiddenStorageKey) === 'true';
    }

    function saveBubbleHiddenState(isHidden) {
        localStorage.setItem(isBubbleHiddenStorageKey, isHidden);
    }

    function saveExcludedDomains() {
        localStorage.setItem(excludedDomainsStorageKey, JSON.stringify(excludedDomains));
    }

    function getLinks() {
        const storedLinks = localStorage.getItem(storageKey);
        if (storedLinks) {
            return JSON.parse(storedLinks);
        }
        return [
            { label: 'Google', url: 'https://www.google.com/' },
            { label: 'Gemini AI', url: 'https://gemini.google.com/' },
            { label: 'OpenAI', url: 'https://www.openai.com/' }
        ];
    }

    let userLinks = getLinks();

    function saveLinks() {
        localStorage.setItem(storageKey, JSON.stringify(userLinks));
    }

    function populateLinkList(linkListElement) {
        linkListElement.innerHTML = '';
        userLinks.forEach((linkData, index) => {
            const linkWrapper = document.createElement('div');
            linkWrapper.className = 'link-wrapper';
            
            const link = document.createElement('a');
            link.href = linkData.url;
            link.textContent = linkData.label;
            link.target = '_blank';
            
            linkWrapper.appendChild(link);
            
            if (isDeleteMode) {
                const deleteButton = document.createElement('button');
                deleteButton.className = 'delete-link-button';
                deleteButton.textContent = 'x';
                deleteButton.addEventListener('click', (event) => {
                    event.preventDefault();
                    userLinks.splice(index, 1);
                    saveLinks();
                    populateLinkList(linkListElement);
                });
                linkWrapper.appendChild(deleteButton);
            }
            linkListElement.appendChild(linkWrapper);
        });
    }

    function populateExcludeList(excludeListElement) {
        excludeListElement.innerHTML = '';
        excludedDomains.forEach((domain, index) => {
            const domainWrapper = document.createElement('div');
            domainWrapper.className = 'exclude-wrapper';

            const domainLabel = document.createElement('span');
            domainLabel.textContent = domain;
            domainWrapper.appendChild(domainLabel);

            if (isExcludeDeleteMode) {
                const deleteButton = document.createElement('button');
                deleteButton.className = 'delete-exclude-button';
                deleteButton.textContent = 'x';
                deleteButton.addEventListener('click', (event) => {
                    event.preventDefault();
                    excludedDomains.splice(index, 1);
                    saveExcludedDomains();
                    populateExcludeList(excludeListElement);
                });
                domainWrapper.appendChild(deleteButton);
            }
            excludeListElement.appendChild(domainWrapper);
        });
    }

    function initializeScript() {
        if (document.getElementById('customFloatingBubble')) {
            return;
        }

        const bubble = document.createElement('div');
        bubble.id = 'customFloatingBubble';
        bubble.textContent = 'λ';
        
        const menu = document.createElement('div');
        menu.id = 'floatingMenu';

        const linkList = document.createElement('div');
        linkList.id = 'linkList';

        const linkForm = document.createElement('div');
        linkForm.id = 'linkForm';
        linkForm.innerHTML = `
            <h3>Add New Link</h3>
            <input type="text" id="linkLabel" placeholder="Label (e.g. My Site)">
            <input type="text" id="linkUrl" placeholder="URL (e.g. https://example.com)">
            <button id="saveLinkButton">Save</button>
        `;
        const saveLinkButton = linkForm.querySelector('#saveLinkButton');
        const linkLabelInput = linkForm.querySelector('#linkLabel');
        const linkUrlInput = linkForm.querySelector('#linkUrl');

        const excludeSection = document.createElement('div');
        excludeSection.id = 'excludeSection';
        excludeSection.innerHTML = `
            <h3>Excluded Websites</h3>
            <div id="excludeList"></div>
            <input type="text" id="excludeUrl" placeholder="Domain (e.g. example.com)">
            <button id="saveExcludeButton">Add Exclude</button>
            <button id="deleteExcludeButton">Delete Excludes</button>
        `;
        const excludeListElement = excludeSection.querySelector('#excludeList');
        const deleteExcludeButton = excludeSection.querySelector('#deleteExcludeButton');
        const saveExcludeButton = excludeSection.querySelector('#saveExcludeButton');
        const excludeUrlInput = excludeSection.querySelector('#excludeUrl');

        const backupSection = document.createElement('div');
        backupSection.id = 'backupSection';
        backupSection.innerHTML = `
            <h3>Backup & Restore</h3>
            <div id="exportWrapper">
                <button id="exportButton">Export</button>
            </div>
            <div id="importWrapper">
                <button id="importButton">Import</button>
            </div>
        `;
        const exportWrapper = backupSection.querySelector('#exportWrapper');
        const importWrapper = backupSection.querySelector('#importWrapper');
        
        const controls = document.createElement('div');
        controls.id = 'menuControls';
        controls.innerHTML = `
            <button id="deleteLinksButton">Delete Links</button>
            <button id="hideButton">Hide Button</button>
            <button id="closeMenuButton">Close Menu</button>
        `;
        const deleteLinksButton = controls.querySelector('#deleteLinksButton');
        const hideButton = controls.querySelector('#hideButton');
        const closeMenuButton = controls.querySelector('#closeMenuButton');
        
        // Triple-click functionality
        const showBubbleButton = document.createElement('div');
        showBubbleButton.id = 'showBubbleButton';
        
        const style = document.createElement('style');
        style.innerHTML = `
            #customFloatingBubble {
                position: fixed;
                bottom: 30px;
                right: 30px;
                width: 60px;
                height: 60px;
                background-color: #0ff;
                border-radius: 50%;
                box-shadow: 0 0 15px 3px #0ff, 0 0 30px 10px #0ff;
                cursor: pointer;
                z-index: 9999999;
                display: flex;
                justify-content: center;
                align-items: center;
                font-size: 36px;
                font-weight: 900;
                color: #001f3f;
                user-select: none;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                transition: transform 0.2s ease, box-shadow 0.2s ease;
            }
            #customFloatingBubble:hover {
                transform: scale(1.15);
                box-shadow: 0 0 20px 5px #0ff, 0 0 40px 15px #0ff;
            }
            #floatingMenu {
                position: fixed;
                bottom: 100px;
                right: 30px;
                width: 300px;
                background-color: #222;
                border: 2px solid #0ff;
                border-radius: 8px;
                box-shadow: 0 0 10px rgba(0,255,255,0.7);
                padding: 10px;
                z-index: 9999998;
                display: none;
                flex-direction: column;
                gap: 10px;
                max-height: 80vh;
                overflow-y: auto;
            }
            #linkList, #excludeList {
                display: flex;
                flex-direction: column;
                gap: 5px;
            }
            .link-wrapper, .exclude-wrapper {
                display: flex;
                align-items: center;
                gap: 5px;
            }
            #linkList a, .exclude-wrapper span {
                flex-grow: 1;
                padding: 8px;
                color: #fff;
                background-color: #333;
                border: 1px solid #0ff;
                text-align: center;
                text-decoration: none;
                border-radius: 5px;
                transition: background-color 0.2s ease, color 0.2s ease;
            }
            #linkList a:hover {
                background-color: #0ff;
                color: #000;
            }
            .delete-link-button, .delete-exclude-button {
                width: 30px;
                height: 30px;
                background-color: #a00;
                color: #fff;
                border: 1px solid #f00;
                border-radius: 50%;
                cursor: pointer;
                font-weight: bold;
                transition: background-color 0.2s ease;
                display: flex;
                justify-content: center;
                align-items: center;
                padding: 0;
            }
            .delete-link-button:hover, .delete-exclude-button:hover {
                background-color: #f00;
            }
            #menuControls {
                display: flex;
                flex-wrap: wrap;
                justify-content: space-between;
                gap: 5px;
            }
            #menuControls button {
                padding: 8px 12px;
                background-color: #444;
                color: #0ff;
                border: 1px solid #0ff;
                border-radius: 5px;
                cursor: pointer;
                flex: 1 1 45%;
                font-size: 12px;
                text-align: center;
            }
            #menuControls button:hover {
                background-color: #0ff;
                color: #000;
            }
            #linkForm, #excludeSection, #backupSection {
                display: flex;
                flex-direction: column;
                gap: 5px;
                padding-top: 10px;
                border-top: 1px solid #444;
            }
            #linkForm h3, #excludeSection h3, #backupSection h3 {
                color: #fff;
                margin: 0;
                text-align: center;
            }
            #linkForm input, #excludeSection input, #backupSection textarea {
                padding: 8px;
                border: 1px solid #0ff;
                background-color: #333;
                color: #fff;
                border-radius: 5px;
            }
            #backupSection button {
                padding: 8px 12px;
                background-color: #444;
                color: #0ff;
                border: 1px solid #0ff;
                border-radius: 5px;
                cursor: pointer;
                flex: 1;
                font-size: 14px;
            }
            #backupSection button:hover {
                background-color: #0ff;
                color: #000;
            }
            #showBubbleButton {
                position: fixed;
                bottom: 30px;
                right: 30px;
                width: 60px;
                height: 60px;
                cursor: pointer;
                z-index: 9999997;
            }
        `;

        document.head.appendChild(style);
        document.body.appendChild(bubble);
        document.body.appendChild(menu);
        menu.appendChild(linkList);
        menu.appendChild(linkForm);
        menu.appendChild(excludeSection);
        menu.appendChild(backupSection);
        menu.appendChild(controls);

        populateLinkList(linkList);
        populateExcludeList(excludeListElement);
        
        const isHidden = getBubbleHiddenState();
        bubble.style.display = isHidden ? 'none' : 'flex';
        
        if (isHidden) {
            document.body.appendChild(showBubbleButton);
        }

        // --- Event Listeners for the Triple-Click functionality ---
        function handleBubbleClick(e) {
            clickCount++;
            if (clickCount === 1) {
                clickTimer = setTimeout(() => {
                    if (clickCount === 1) {
                        const isMenuVisible = menu.style.display === 'flex';
                        menu.style.display = isMenuVisible ? 'none' : 'flex';
                    }
                    clickCount = 0;
                }, 300); // 300ms window for triple-click
            } else if (clickCount === 3) {
                clearTimeout(clickTimer);
                bubble.style.display = 'none';
                menu.style.display = 'none';
                saveBubbleHiddenState(true);
                document.body.appendChild(showBubbleButton);
                clickCount = 0;
            }
        }
        
        function handleShowBubbleClick(e) {
            clickCount++;
            if (clickCount === 3) {
                bubble.style.display = 'flex';
                saveBubbleHiddenState(false);
                menu.style.display = 'flex';
                showBubbleButton.remove();
                clickCount = 0;
            }
        }

        bubble.addEventListener('click', handleBubbleClick);
        showBubbleButton.addEventListener('click', handleShowBubbleClick);
        
        // --- UI BUTTON LISTENERS ---
        const exportButton = backupSection.querySelector('#exportButton');
        const importButton = backupSection.querySelector('#importWrapper #importButton');

        hideButton.addEventListener('click', () => {
            bubble.style.display = 'none';
            menu.style.display = 'none';
            saveBubbleHiddenState(true);
            document.body.appendChild(showBubbleButton);
        });
        
        closeMenuButton.addEventListener('click', () => {
            menu.style.display = 'none';
        });

        saveLinkButton.addEventListener('click', () => {
            const label = linkLabelInput.value;
            const url = linkUrlInput.value;
            if (label && url) {
                userLinks.push({ label, url });
                saveLinks();
                populateLinkList(linkList);
                linkLabelInput.value = '';
                linkUrlInput.value = '';
            } else {
                alert('Please enter both a label and a URL.');
            }
        });
        
        deleteLinksButton.addEventListener('click', () => {
            isDeleteMode = !isDeleteMode;
            deleteLinksButton.textContent = isDeleteMode ? 'Exit Delete' : 'Delete Links';
            populateLinkList(linkList);
        });

        saveExcludeButton.addEventListener('click', () => {
            const domain = excludeUrlInput.value;
            if (domain && !excludedDomains.includes(domain)) {
                excludedDomains.push(domain);
                saveExcludedDomains();
                populateExcludeList(excludeListElement);
                excludeUrlInput.value = '';
            } else if (excludedDomains.includes(domain)) {
                alert('This domain is already on the exclusion list.');
            } else {
                alert('Please enter a domain to exclude.');
            }
        });

        deleteExcludeButton.addEventListener('click', () => {
            isExcludeDeleteMode = !isExcludeDeleteMode;
            deleteExcludeButton.textContent = isExcludeDeleteMode ? 'Exit Exclude Delete' : 'Delete Excludes';
            populateExcludeList(excludeListElement);
        });
        
        // --- BACKUP & RESTORE EVENT LISTENERS ---
        exportButton.addEventListener('click', () => {
            isExportMode = !isExportMode;
            if (isExportMode) {
                exportWrapper.innerHTML = `
                    <textarea readonly>${JSON.stringify(userLinks)}</textarea>
                    <button id="exportButton">Close</button>
                `;
                exportWrapper.querySelector('#exportButton').addEventListener('click', () => {
                    isExportMode = false;
                    exportWrapper.innerHTML = `<button id="exportButton">Export</button>`;
                    exportWrapper.querySelector('#exportButton').addEventListener('click', exportButton.click);
                });
            } else {
                exportWrapper.innerHTML = `<button id="exportButton">Export</button>`;
            }
        });

        importButton.addEventListener('click', () => {
            isImportMode = !isImportMode;
            if (isImportMode) {
                importWrapper.innerHTML = `
                    <textarea placeholder="Paste your link data here..."></textarea>
                    <button id="loadButton">Load</button>
                    <button id="importButton">Close</button>
                `;
                importWrapper.querySelector('#loadButton').addEventListener('click', () => {
                    const data = importWrapper.querySelector('textarea').value;
                    try {
                        const importedLinks = JSON.parse(data);
                        if (Array.isArray(importedLinks)) {
                            userLinks = importedLinks;
                            saveLinks();
                            populateLinkList(linkList);
                            alert('Links imported successfully!');
                            isImportMode = false;
                            importWrapper.innerHTML = `<button id="importButton">Import</button>`;
                            importWrapper.querySelector('#importButton').addEventListener('click', importButton.click);
                        } else {
                            alert('Invalid data format. Please paste the correct JSON data.');
                        }
                    } catch (e) {
                        alert('Invalid data format. Please paste the correct JSON data.');
                    }
                });
                importWrapper.querySelector('#importButton').addEventListener('click', () => {
                    isImportMode = false;
                    importWrapper.innerHTML = `<button id="importButton">Import</button>`;
                    importWrapper.querySelector('#importButton').addEventListener('click', importButton.click);
                });
            } else {
                importWrapper.innerHTML = `<button id="importButton">Import</button>`;
            }
        });
    }

    function waitForBody() {
        if (document.body) {
            initializeScript();
        } else {
            setTimeout(waitForBody, 100);
        }
    }

    waitForBody();
})();