您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Customizable link menu.
// ==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(); })();