您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Customizable link menu.
// ==UserScript== // @name Floating Link Menu // @namespace http://tampermonkey.net/ // @version 2.7 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 GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-start // ==/UserScript== (async function() { 'use strict'; // --- SCRIPT EXCLUSION LOGIC --- const excludedDomainsStorageKey = 'excludedUniversalDomains'; const currentUrl = window.location.href; const excludedDomains = await GM_getValue(excludedDomainsStorageKey, []); const isExcluded = excludedDomains.some(domain => currentUrl.includes(domain)); if (isExcluded) { return; } // --- END EXCLUSION LOGIC --- // --- Data Management using Tampermonkey's GM_ API --- const storageKey = 'universalLinkManagerLinks'; const isBubbleHiddenStorageKey = 'isBubbleHidden'; const buttonPositionStorageKey = 'bubblePosition'; const themeStorageKey = 'universalLinkManagerTheme'; const defaultLinks = [ { label: 'Google', url: 'https://www.google.com/' }, { label: 'Gemini AI', url: 'https://gemini.google.com/' }, { label: 'OpenAI', url: 'https://www.openai.com/' } ]; let isDeleteMode = false; let isExcludeDeleteMode = false; async function getLinks() { return await GM_getValue(storageKey, defaultLinks); } async function saveLinks(links) { await GM_setValue(storageKey, links); } async function getExcludedDomains() { return await GM_getValue(excludedDomainsStorageKey, []); } async function saveExcludedDomains(domains) { await GM_setValue(excludedDomainsStorageKey, domains); } async function getBubbleHiddenState() { return await GM_getValue(isBubbleHiddenStorageKey, false); } async function saveBubbleHiddenState(isHidden) { await GM_setValue(isBubbleHiddenStorageKey, isHidden); } async function getButtonPosition() { return await GM_getValue(buttonPositionStorageKey, { vertical: 'bottom', horizontal: 'right' }); } async function saveButtonPosition(position) { await GM_setValue(buttonPositionStorageKey, position); } async function getTheme() { return await GM_getValue(themeStorageKey, 'default'); } async function saveTheme(theme) { await GM_setValue(themeStorageKey, theme); } // --- Style and Themes --- const themes = { default: ` #floatingMenu, #universalLinkManagerUI, #bubbleMenu { background-color: #222; border: 2px solid #0ff; box-shadow: 0 0 10px rgba(0,255,255,0.7); } #linkList a, .exclude-wrapper span { color: #fff; background-color: #333; border: 1px solid #0ff; } #linkList a:hover { background-color: #0ff; color: #000; } #menuControls button, #backupSection button, #positionControls button, .modal-button, #bubbleMenu button { color: #0ff; border: 1px solid #0ff; background-color: #444; } #menuControls button:hover, #backupSection button:hover, #positionControls button:hover, .modal-button:hover, #bubbleMenu button:hover { background-color: #0ff; color: #000; } #linkForm input, #excludeSection input { border: 1px solid #0ff; background-color: #333; color: #fff; } #linkForm h3, #excludeSection h3, #backupSection h3, #positionControls h3 { color: #fff; } .delete-link-button, .delete-exclude-button { background-color: #a00; border: 1px solid #f00; color: #fff; } .delete-link-button:hover, .delete-exclude-button:hover { background-color: #f00; } .active { background-color: #0ff; color: #000 !important; } `, highContrast: ` #floatingMenu, #universalLinkManagerUI, #bubbleMenu { background-color: #000; border: 2px solid #ffff00; box-shadow: 0 0 15px rgba(255,255,0,0.9); } #linkList a, .exclude-wrapper span { color: #ffff00; background-color: #000; border: 1px solid #ffff00; text-shadow: 0 0 5px #ffff00; font-weight: bold; } #linkList a:hover { background-color: #ffff00; color: #000; text-shadow: none; } #menuControls button, #backupSection button, #positionControls button, .modal-button, #bubbleMenu button { color: #ffff00; border: 1px solid #ffff00; background-color: #333; } #menuControls button:hover, #backupSection button:hover, #positionControls button:hover, .modal-button:hover, #bubbleMenu button:hover { background-color: #ffff00; color: #000; } #linkForm input, #excludeSection input { border: 1px solid #ffff00; background-color: #000; color: #ffff00; } #linkForm h3, #excludeSection h3, #backupSection h3, #positionControls h3 { color: #ffff00; } .delete-link-button, .delete-exclude-button { background-color: #ff0000; border: 1px solid #ff0000; color: #ffff00; } .delete-link-button:hover, .delete-exclude-button:hover { background-color: #ff5555; } .active { background-color: #ffff00; color: #000 !important; } ` }; const baseStyle = ` @keyframes pulse { 0% { transform: scale(1); box-shadow: 0 0 15px 3px #0ff, 0 0 30px 10px #0ff; } 50% { transform: scale(1.05); box-shadow: 0 0 20px 5px #0ff, 0 0 40px 15px #0ff; } 100% { transform: scale(1); box-shadow: 0 0 15px 3px #0ff, 0 0 30px 10px #0ff; } } @keyframes neonGlow { 0% { box-shadow: 0 0 10px rgba(0,255,255,0.7); } 50% { box-shadow: 0 0 15px rgba(0,255,255,0.9), 0 0 25px rgba(0,255,255,0.6); } 100% { box-shadow: 0 0 10px rgba(0,255,255,0.7); } } #customFloatingBubble { position: fixed; 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, top 0.2s ease, bottom 0.2s ease, left 0.2s ease, right 0.2s ease; animation: pulse 3s infinite ease-in-out; } #customFloatingBubble:hover { transform: scale(1.15); box-shadow: 0 0 20px 5px #0ff, 0 0 40px 15px #0ff; } #universalLinkManagerUI { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 350px; border-radius: 8px; padding: 15px; z-index: 9999998; display: none; flex-direction: column; gap: 15px; max-height: 90vh; overflow-y: auto; animation: neonGlow 4s infinite ease-in-out; } #bubbleMenu { position: fixed; z-index: 9999999; padding: 10px; border-radius: 8px; display: none; flex-direction: column; gap: 8px; animation: neonGlow 4s infinite ease-in-out; } #bubbleMenu button { transition: background-color 0.2s, color 0.2s; } #ui-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #555; padding-bottom: 10px; } #ui-header h2 { margin: 0; color: #0ff; text-shadow: 0 0 5px #0ff; } #closeUI { background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 0; width: 30px; height: 30px; border-radius: 50%; transition: all 0.2s ease; } #closeUI:hover { background-color: #f00; transform: scale(1.1); } #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; text-align: center; text-decoration: none; border-radius: 5px; transition: background-color 0.2s ease, color 0.2s ease; text-shadow: 0 0 3px currentColor; font-size: 14px; } .delete-link-button, .delete-exclude-button { width: 30px; height: 30px; border-radius: 50%; cursor: pointer; font-weight: bold; transition: background-color 0.2s ease; display: flex; justify-content: center; align-items: center; padding: 0; } #menuControls { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px; } #menuControls button, .modal-button { padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1 1 45%; font-size: 12px; text-align: center; } #linkForm, #excludeSection, #backupSection, #positionControls { display: flex; flex-direction: column; gap: 5px; padding-top: 10px; border-top: 1px solid #444; } #linkForm h3, #excludeSection h3, #backupSection h3, #positionControls h3 { margin: 0; text-align: center; } #linkForm input, #excludeSection input { padding: 8px; border-radius: 5px; } #backupSection button { flex: 1; font-size: 14px; } #showBubbleButton { position: fixed; width: 60px; height: 60px; cursor: pointer; z-index: 9999997; background-color: transparent; border: none; user-select: none; } #positionControls .position-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; } .import-buttons { display: flex; gap: 5px; } .import-buttons button { flex: 1; } `; GM_addStyle(baseStyle); let bubble = null; let mainUI = null; let showBubbleButton = null; let bubbleMenu = null; function populateLinkList(links, linkListElement) { linkListElement.innerHTML = ''; links.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', async (event) => { event.preventDefault(); links.splice(index, 1); await saveLinks(links); populateLinkList(links, linkListElement); }); linkWrapper.appendChild(deleteButton); } linkListElement.appendChild(linkWrapper); }); } function populateExcludeList(domains, excludeListElement) { excludeListElement.innerHTML = ''; domains.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', async (event) => { event.preventDefault(); domains.splice(index, 1); await saveExcludedDomains(domains); populateExcludeList(domains, excludeListElement); }); domainWrapper.appendChild(deleteButton); } excludeListElement.appendChild(domainWrapper); }); } function applyButtonPosition(position) { if (bubble) { bubble.style.top = ''; bubble.style.left = ''; bubble.style.bottom = ''; bubble.style.right = ''; bubble.style[position.vertical] = '30px'; bubble.style[position.horizontal] = '30px'; } if (showBubbleButton) { showBubbleButton.style.top = ''; showBubbleButton.style.left = ''; showBubbleButton.style.bottom = ''; showBubbleButton.style.right = ''; showBubbleButton.style[position.vertical] = '30px'; showBubbleButton.style[position.horizontal] = '30px'; } if (bubbleMenu) { bubbleMenu.style.top = ''; bubbleMenu.style.left = ''; bubbleMenu.style.bottom = ''; bubbleMenu.style.right = ''; if (position.vertical === 'top') { bubbleMenu.style.top = '100px'; } else { bubbleMenu.style.bottom = '100px'; } if (position.horizontal === 'left') { bubbleMenu.style.left = '30px'; } else { bubbleMenu.style.right = '30px'; } } const positionButtons = document.querySelectorAll('#positionControls button'); positionButtons.forEach(btn => { btn.classList.remove('active'); }); const activeBtnId = `position-${position.vertical}-${position.horizontal}`; const activeBtn = document.getElementById(activeBtnId); if (activeBtn) { activeBtn.classList.add('active'); } } function applyTheme(themeName) { const styleElement = document.getElementById('universalLinkManagerStyle'); if (styleElement) { styleElement.innerHTML = `${baseStyle}\n${themes[themeName]}\n`; } } async function initializeScript() { if (document.getElementById('customFloatingBubble')) { return; } const buttonPosition = await getButtonPosition(); const currentTheme = await getTheme(); const style = document.createElement('style'); style.id = 'universalLinkManagerStyle'; document.head.appendChild(style); applyTheme(currentTheme); // Create the bubble bubble = document.createElement('div'); bubble.id = 'customFloatingBubble'; bubble.textContent = 'λ'; document.body.appendChild(bubble); // Create the mini bubble menu bubbleMenu = document.createElement('div'); bubbleMenu.id = 'bubbleMenu'; bubbleMenu.innerHTML = ` <button id="instantAddButton">Add This Site</button> <button id="showFullMenuButton">Settings</button> `; document.body.appendChild(bubbleMenu); // Create the main modal UI mainUI = document.createElement('div'); mainUI.id = 'universalLinkManagerUI'; mainUI.innerHTML = ` <div id="ui-header"> <h2>Universal Links</h2> <button id="closeUI">X</button> </div> <div id="ui-content"> <div id="linkList"></div> <div id="linkForm"> <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" class="modal-button">Save</button> </div> <div id="excludeSection"> <h3>Excluded Websites</h3> <div id="excludeList"></div> <input type="text" id="excludeUrl" placeholder="Domain (e.g. example.com)"> <button id="saveExcludeButton" class="modal-button">Add Exclude</button> <button id="deleteExcludeButton" class="modal-button">Delete Excludes</button> </div> <div id="backupSection"> <h3>Backup & Restore</h3> <div id="exportWrapper"> <button id="exportButton" class="modal-button">Export</button> </div> <div id="importWrapper"> <button id="importButton" class="modal-button">Import</button> </div> </div> <div id="positionControls"> <h3>Button Position</h3> <div class="position-buttons"> <button id="position-top-left" class="modal-button">Top-Left</button> <button id="position-top-right" class="modal-button">Top-Right</button> <button id="position-bottom-left" class="modal-button">Bottom-Left</button> <button id="position-bottom-right" class="modal-button">Bottom-Right</button> </div> </div> <div id="menuControls"> <button id="deleteLinksButton" class="modal-button">Delete Links</button> <button id="themeButton" class="modal-button">Theme</button> <button id="hideButton" class="modal-button">Hide Button</button> </div> </div> `; document.body.appendChild(mainUI); // Create the invisible show bubble button showBubbleButton = document.createElement('div'); showBubbleButton.id = 'showBubbleButton'; document.body.appendChild(showBubbleButton); // Load initial state and position applyButtonPosition(buttonPosition); const isHidden = await getBubbleHiddenState(); bubble.style.display = isHidden ? 'none' : 'flex'; showBubbleButton.style.display = isHidden ? 'block' : 'none'; // --- Event Listeners --- // NEW: Keyboard shortcut to toggle the main UI document.addEventListener('keydown', async (event) => { // Check for Ctrl + Alt + U if (event.ctrlKey && event.altKey && event.key === 'u') { const isVisible = mainUI.style.display === 'flex'; if (isVisible) { mainUI.style.display = 'none'; } else { const links = await getLinks(); const excluded = await getExcludedDomains(); populateLinkList(links, mainUI.querySelector('#linkList')); populateExcludeList(excluded, mainUI.querySelector('#excludeList')); mainUI.style.display = 'flex'; } event.preventDefault(); // Prevents the browser's default action } }); // Bubble click to toggle the mini-menu bubble.addEventListener('click', async () => { const isVisible = bubbleMenu.style.display === 'flex'; bubbleMenu.style.display = isVisible ? 'none' : 'flex'; }); // Triple-click logic for bubble let bubbleClickCount = 0; let bubbleClickTimer = null; bubble.addEventListener('click', () => { bubbleClickCount++; if (bubbleClickTimer) clearTimeout(bubbleClickTimer); bubbleClickTimer = setTimeout(() => { bubbleClickCount = 0; }, 300); if (bubbleClickCount === 3) { clearTimeout(bubbleClickTimer); bubble.style.display = 'none'; showBubbleButton.style.display = 'block'; mainUI.style.display = 'none'; bubbleMenu.style.display = 'none'; saveBubbleHiddenState(true); bubbleClickCount = 0; } }); // Triple-click logic for showBubbleButton let restoreClickCount = 0; let restoreClickTimer = null; showBubbleButton.addEventListener('click', () => { restoreClickCount++; if (restoreClickTimer) clearTimeout(restoreClickTimer); restoreClickTimer = setTimeout(() => { restoreClickCount = 0; }, 400); if (restoreClickCount === 3) { clearTimeout(restoreClickTimer); bubble.style.display = 'flex'; showBubbleButton.style.display = 'none'; saveBubbleHiddenState(false); restoreClickCount = 0; } }); // Instant Add Button bubbleMenu.querySelector('#instantAddButton').addEventListener('click', async () => { const title = document.title; const url = window.location.href; const links = await getLinks(); links.push({ label: title, url: url }); await saveLinks(links); bubbleMenu.style.display = 'none'; const excluded = await getExcludedDomains(); populateLinkList(links, mainUI.querySelector('#linkList')); populateExcludeList(excluded, mainUI.querySelector('#excludeList')); mainUI.style.display = 'flex'; }); // Show Full Menu Button bubbleMenu.querySelector('#showFullMenuButton').addEventListener('click', async () => { bubbleMenu.style.display = 'none'; const links = await getLinks(); const excluded = await getExcludedDomains(); populateLinkList(links, mainUI.querySelector('#linkList')); populateExcludeList(excluded, mainUI.querySelector('#excludeList')); mainUI.style.display = 'flex'; }); mainUI.querySelector('#closeUI').addEventListener('click', () => { mainUI.style.display = 'none'; }); mainUI.querySelector('#hideButton').addEventListener('click', () => { bubble.style.display = 'none'; showBubbleButton.style.display = 'block'; mainUI.style.display = 'none'; bubbleMenu.style.display = 'none'; saveBubbleHiddenState(true); }); mainUI.querySelector('#saveLinkButton').addEventListener('click', async () => { const labelInput = mainUI.querySelector('#linkLabel'); const urlInput = mainUI.querySelector('#linkUrl'); const label = labelInput.value.trim(); const url = urlInput.value.trim(); if (label && url) { try { new URL(url); const links = await getLinks(); links.push({ label, url }); await saveLinks(links); populateLinkList(links, mainUI.querySelector('#linkList')); labelInput.value = ''; urlInput.value = ''; } catch (e) { alert('Please enter a valid URL (e.g., https://example.com).'); } } else { alert('Please enter both a label and a URL.'); } }); mainUI.querySelector('#deleteLinksButton').addEventListener('click', async () => { isDeleteMode = !isDeleteMode; const deleteButton = mainUI.querySelector('#deleteLinksButton'); deleteButton.textContent = isDeleteMode ? 'Exit Delete' : 'Delete Links'; const links = await getLinks(); populateLinkList(links, mainUI.querySelector('#linkList')); }); mainUI.querySelector('#themeButton').addEventListener('click', async () => { const currentTheme = await getTheme(); const newTheme = currentTheme === 'default' ? 'highContrast' : 'default'; await saveTheme(newTheme); applyTheme(newTheme); const themeButton = mainUI.querySelector('#themeButton'); themeButton.textContent = newTheme === 'default' ? 'Theme' : 'Default'; }); mainUI.querySelector('#saveExcludeButton').addEventListener('click', async () => { const domainInput = mainUI.querySelector('#excludeUrl'); const domain = domainInput.value.trim(); if (domain) { const excluded = await getExcludedDomains(); if (!excluded.includes(domain)) { excluded.push(domain); await saveExcludedDomains(excluded); populateExcludeList(excluded, mainUI.querySelector('#excludeList')); domainInput.value = ''; } else { alert('This domain is already on the exclusion list.'); } } else { alert('Please enter a domain to exclude.'); } }); mainUI.querySelector('#deleteExcludeButton').addEventListener('click', async () => { isExcludeDeleteMode = !isExcludeDeleteMode; const deleteExcludeButton = mainUI.querySelector('#deleteExcludeButton'); deleteExcludeButton.textContent = isExcludeDeleteMode ? 'Exit Exclude Delete' : 'Delete Excludes'; const excluded = await getExcludedDomains(); populateExcludeList(excluded, mainUI.querySelector('#excludeList')); }); mainUI.querySelector('#positionControls').querySelectorAll('button').forEach(button => { button.addEventListener('click', async (event) => { const id = event.target.id; const parts = id.split('-'); const newPosition = { vertical: parts[1], horizontal: parts[2] }; await saveButtonPosition(newPosition); applyButtonPosition(newPosition); }); }); mainUI.querySelector('#exportWrapper').addEventListener('click', async (event) => { const button = event.target; if (button.id === 'exportButton') { if (button.textContent === 'Export') { const links = await getLinks(); const exportData = JSON.stringify(links, null, 2); mainUI.querySelector('#exportWrapper').innerHTML = ` <textarea readonly style="width:100%; height:100px;">${exportData}</textarea> <button id="exportButton" class="modal-button">Close</button> `; } else { mainUI.querySelector('#exportWrapper').innerHTML = `<button id="exportButton" class="modal-button">Export</button>`; } } }); mainUI.querySelector('#importWrapper').addEventListener('click', async (event) => { const button = event.target; if (button.id === 'importButton') { if (button.textContent === 'Import') { mainUI.querySelector('#importWrapper').innerHTML = ` <textarea placeholder="Paste your link data here..." style="width:100%; height:100px;"></textarea> <div class="import-buttons"> <button id="loadButton" class="modal-button">Load</button> <button id="importButton" class="modal-button">Cancel</button> </div> `; } else { mainUI.querySelector('#importWrapper').innerHTML = `<button id="importButton" class="modal-button">Import</button>`; } } else if (button.id === 'loadButton') { const dataTextarea = mainUI.querySelector('textarea'); if (dataTextarea) { const data = dataTextarea.value.trim(); if (!data) { alert('Please paste the link data.'); return; } try { const importedLinks = JSON.parse(data); if (Array.isArray(importedLinks)) { await saveLinks(importedLinks); const links = await getLinks(); populateLinkList(links, mainUI.querySelector('#linkList')); alert('Links imported successfully!'); mainUI.querySelector('#importWrapper').innerHTML = `<button id="importButton" class="modal-button">Import</button>`; } else { alert('Invalid data format. Please paste the correct JSON data.'); } } catch (e) { alert('Invalid JSON format. Error: ' + e.message); } } } }); } if (document.body) { initializeScript(); } else { document.addEventListener('DOMContentLoaded', initializeScript); } })();