显示新标签页打开图标(带暴力猴菜单设置)

鼠标悬停在链接上时,如果会在新标签页打开,则显示箭头图标,支持在暴力猴菜单中设置图标大小、位置、颜色、透明度和描边等

目前為 2024-11-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name:en-us         Show new tab open icon (with Violent Monkey menu settings)
// @name  显示新标签页打开图标(带暴力猴菜单设置)
// @namespace    https://violentmonkey.github.io/
// @version      1.7
// @description:en-us   Show arrow icon when hovering over a link if it will be opened in a new tab, support to set icon size, position, color, transparency and stroke in Violence Monkey menu, etc.
// @description 鼠标悬停在链接上时,如果会在新标签页打开,则显示箭头图标,支持在暴力猴菜单中设置图标大小、位置、颜色、透明度和描边等
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license GNU GPLv3 
// ==/UserScript==

(function() {
    'use strict';

    // 默认图标
    const defaultIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWV4dGVybmFsLWxpbmsiPjxwYXRoIGQ9Ik0xNSAzaDZ2NiIvPjxwYXRoIGQ9Ik0xMCAxNCAyMSAzIi8+PHBhdGggZD0iTTE4IDEzdjZhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJWOGEyIDIgMCAwIDEgMi0yaDYiLz48L3N2Zz4=";

    // 从存储中读取设置,或使用默认值
    const settings = {
        iconUrl: GM_getValue("iconUrl", defaultIcon),
        size: GM_getValue("size", 24),
        offsetX: GM_getValue("offsetX", 10),
        offsetY: GM_getValue("offsetY", 10),
        iconOpacity: GM_getValue("iconOpacity", 0.5),
        outlineSize: GM_getValue("outlineSize", 2),
        outlineColor: GM_getValue("outlineColor", "black"),
        outlineOpacity: GM_getValue("outlineOpacity", 1),
        invertForDarkMode: GM_getValue("invertForDarkMode", true)
    };

    // 创建图标元素
    const icon = document.createElement("div");
    icon.style.position = "absolute";
    icon.style.width = `${settings.size}px`;
    icon.style.height = `${settings.size}px`;
    icon.style.backgroundImage = `url(${settings.iconUrl})`;
    icon.style.backgroundSize = "contain";
    icon.style.pointerEvents = "none";
    icon.style.zIndex = "10000";
    icon.style.opacity = settings.iconOpacity;
    icon.style.display = "none";
    updateIconFilter();
    document.body.appendChild(icon);

    // 监听鼠标悬停事件
    document.addEventListener("mouseover", (event) => {
        const link = event.target.closest("a");
        if (link && link.target === "_blank") {
            icon.style.display = "block";
        } else {
            icon.style.display = "none";
        }
    });

    // 跟随鼠标移动
    document.addEventListener("mousemove", (event) => {
        icon.style.left = `${event.pageX + settings.offsetX}px`;
        icon.style.top = `${event.pageY + settings.offsetY}px`;
    });

    // 隐藏图标
    document.addEventListener("mouseout", (event) => {
        if (event.target.closest("a")) {
            icon.style.display = "none";
        }
    });

    // 暴力猴菜单设置
    GM_registerMenuCommand("设置图标 URL", () => {
        const iconUrl = prompt("请输入图标的 Data URL:", settings.iconUrl);
        if (iconUrl) {
            settings.iconUrl = iconUrl;
            GM_setValue("iconUrl", iconUrl);
            icon.style.backgroundImage = `url(${iconUrl})`;
        }
    });

    GM_registerMenuCommand("设置图标大小", () => {
        const size = prompt("请输入图标大小(像素):", settings.size);
        if (size) {
            settings.size = parseInt(size, 10);
            GM_setValue("size", settings.size);
            icon.style.width = `${settings.size}px`;
            icon.style.height = `${settings.size}px`;
        }
    });

    GM_registerMenuCommand("设置横向偏移", () => {
        const offsetX = prompt("请输入横向偏移(像素):", settings.offsetX);
        if (offsetX) {
            settings.offsetX = parseInt(offsetX, 10);
            GM_setValue("offsetX", settings.offsetX);
        }
    });

    GM_registerMenuCommand("设置纵向偏移", () => {
        const offsetY = prompt("请输入纵向偏移(像素):", settings.offsetY);
        if (offsetY) {
            settings.offsetY = parseInt(offsetY, 10);
            GM_setValue("offsetY", settings.offsetY);
        }
    });

    GM_registerMenuCommand("设置透明度", () => {
        const opacity = prompt("请输入透明度(0-1):", settings.iconOpacity);
        if (opacity) {
            settings.iconOpacity = parseFloat(opacity);
            GM_setValue("iconOpacity", settings.iconOpacity);
            icon.style.opacity = settings.iconOpacity;
        }
    });

    GM_registerMenuCommand("设置描边大小", () => {
        const outlineSize = prompt("请输入描边大小(像素):", settings.outlineSize);
        if (outlineSize) {
            settings.outlineSize = parseInt(outlineSize, 10);
            GM_setValue("outlineSize", settings.outlineSize);
            updateIconFilter();
        }
    });

    GM_registerMenuCommand("设置描边颜色", () => {
        const outlineColor = prompt("请输入描边颜色(CSS颜色):", settings.outlineColor);
        if (outlineColor) {
            settings.outlineColor = outlineColor;
            GM_setValue("outlineColor", settings.outlineColor);
            updateIconFilter();
        }
    });

    GM_registerMenuCommand("设置描边透明度", () => {
        const outlineOpacity = prompt("请输入描边透明度(0-1):", settings.outlineOpacity);
        if (outlineOpacity) {
            settings.outlineOpacity = parseFloat(outlineOpacity);
            GM_setValue("outlineOpacity", settings.outlineOpacity);
            updateIconFilter();
        }
    });

    GM_registerMenuCommand("启用夜间模式反色", () => {
        settings.invertForDarkMode = !settings.invertForDarkMode;
        GM_setValue("invertForDarkMode", settings.invertForDarkMode);
        updateIconFilter();
    });

    // 更新图标滤镜(用于描边和夜间模式反色)
    function updateIconFilter() {
        const invertFilter = settings.invertForDarkMode && window.matchMedia("(prefers-color-scheme: dark)").matches ? "invert(1)" : "";
        icon.style.filter = `drop-shadow(0 0 ${settings.outlineSize}px ${settings.outlineColor}) opacity(${settings.outlineOpacity}) ${invertFilter}`;
    }

    // 监听系统颜色主题变化
    window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", updateIconFilter);
})();