Greasy Fork

在新标签页中打开链接

在页面上显示一个功能开关,改变网页跳转方式改为打开新标签页进行跳转,请自行配置脚本作用域

// ==UserScript==
// @name         在新标签页中打开链接
// @namespace    http://tampermonkey.net/
// @version      1.33
// @description  在页面上显示一个功能开关,改变网页跳转方式改为打开新标签页进行跳转,请自行配置脚本作用域
// @author       晚风知我意
// @match        https://yesicon.app/*
// @match        https://bbs.binmt.cc/*
// @grant        none
// @license      GNU AGPLv3
// ==/UserScript==

(function() {
    'use strict';

    const domain = location.hostname; 
    const toggleKey = `linkToggleEnabled_${domain}`; 
    const ignoredElementsKey = `ignoredElements_${domain}`; 

    if (localStorage.getItem(toggleKey) === null) {
        localStorage.setItem(toggleKey, 'true');
    }

    let isEnabled = localStorage.getItem(toggleKey) === 'true';
    const ignoredElements = new Set(JSON.parse(localStorage.getItem(ignoredElementsKey)) || []);

    const button = document.createElement("div");
    button.style.position = "fixed";
    button.style.right = "10px";
    button.style.top = "50%";
    button.style.transform = "translateY(-50%)";
    button.style.cursor = "pointer";
    button.style.zIndex = "9999";
    button.style.width = "50px";  
    button.style.height = "50px"; 
    button.style.borderRadius = "50%";
    button.style.display = "flex"; 
    button.style.alignItems = "center"; 
    button.style.justifyContent = "center"; 
    button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="#cccccc" class="icon-path" d="M11.298 2.195a2 2 0 0 1 1.232-.055l.172.055l7 2.625a2 2 0 0 1 1.291 1.708l.007.165v5.363a9 9 0 0 1-4.709 7.911l-.266.139l-3.354 1.677a1.5 1.5 0 0 1-1.198.062l-.144-.062l-3.354-1.677a9 9 0 0 1-4.97-7.75l-.005-.3V6.693a2 2 0 0 1 1.145-1.808l.153-.065zM12 4.068L5 6.693v5.363a7 7 0 0 0 3.635 6.138l.235.123L12 19.882l3.13-1.565a7 7 0 0 0 3.865-5.997l.005-.264V6.693zm3.433 4.561a1 1 0 0 1 1.497 1.32l-.083.094l-5.234 5.235a1.1 1.1 0 0 1-1.46.085l-.096-.085l-2.404-2.404a1 1 0 0 1 1.32-1.498l.094.083l1.768 1.768z"/></g></svg>`;
    document.body.appendChild(button);

    let longPressTimeout;
    let isDragging = false;

    button.addEventListener("mousedown", (e) => {
        e.preventDefault(); 
        longPressTimeout = setTimeout(() => {
            isDragging = true;
            document.addEventListener('mousemove', mouseMoveHandler);
        }, 500); 
    });

    button.addEventListener("click", (e) => {
        if (!isDragging) {
            toggleFunctionality();
        }
    });

    document.addEventListener("mouseup", () => {
        clearTimeout(longPressTimeout);
        isDragging = false;
        document.removeEventListener('mousemove', mouseMoveHandler);
    });

    function mouseMoveHandler(e) {
        if (isDragging) {
            e.preventDefault(); 
            button.style.left = (e.clientX - button.offsetWidth / 2) + 'px';
            button.style.top = (e.clientY - button.offsetHeight / 2) + 'px';
        }
    }
    
    button.addEventListener('touchstart', (e) => {
        e.preventDefault(); 
        longPressTimeout = setTimeout(() => {
            isDragging = true;
            document.addEventListener('touchmove', touchMoveHandler);
        }, 500); 
    });

    button.addEventListener("touchend", () => {
        clearTimeout(longPressTimeout);
        if (!isDragging) {
            toggleFunctionality(); 
        }
        isDragging = false;
        document.removeEventListener('touchmove', touchMoveHandler);
    });

    function touchMoveHandler(e) {
        if (isDragging) {
            e.preventDefault(); 
            button.style.left = (e.touches[0].clientX - button.offsetWidth / 2) + 'px';
            button.style.top = (e.touches[0].clientY - button.offsetHeight / 2) + 'px';
        }
    }

    function toggleFunctionality() {     
        isEnabled = !isEnabled;
        localStorage.setItem(toggleKey, isEnabled);
        updateButtonState(isEnabled);

        if (isEnabled) {
            enableLinkOpening();
        } else {
            disableLinkOpening();
        }
    }

    function updateButtonState(newState) {
        const paths = button.querySelectorAll('.icon-path');
        paths.forEach(path => {
            path.setAttribute('fill', newState ? "#FDB122" : "#cccccc");        
        });
    }

    function enableLinkOpening() {
        document.addEventListener('click', handleLinkClick, true);

        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) {
                        const newLinks = node.querySelectorAll('a');
                        newLinks.forEach(newLink => {
                            newLink.addEventListener('click', handleLinkClick, true);
                        });
                    }
                });
            });
        });

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

    function disableLinkOpening() {
        document.removeEventListener('click', handleLinkClick, true);
    }

    function isExcludedElement(element) {
        return element.closest('input, textarea, button, .modal, .dialog, [data-popup], [data-modal], .sidebar, .sidebar *, .dropdown, .dropdown *, [role="button"], [onclick], [data-ignore-link], .fixed-container *');
    }

    function handleLinkClick(event) {
        const isLinkElement = event.target.closest('a');
        if (!isLinkElement) return;

        if (isExcludedElement(event.target)) {
            return; 
        }

        const url = new URL(event.currentTarget.href);
        const clickedElementInfo = {
            tag: event.target.tagName,
            classes: event.target.className,
            id: event.target.id,
            href: url.href
        };

        let newTab;
        try {
            newTab = window.open(url.href, '_blank');
            if (!newTab || newTab.closed || typeof newTab.closed === 'undefined') {
                throw new Error('Failed to open new tab');
            }
        } catch (e) {
            ignoredElements.add(JSON.stringify(clickedElementInfo));
            localStorage.setItem(ignoredElementsKey, JSON.stringify(Array.from(ignoredElements)));
            console.log(`Added to ignored elements: ${JSON.stringify(clickedElementInfo)}`);
            return;
        }

        event.preventDefault(); 
        event.stopImmediatePropagation(); 
    }

    updateButtonState(isEnabled);
    if (isEnabled) {
        enableLinkOpening();
    }
})();