EZLinkZ

Copy hrefs from selected links with toast at top; toast clickable to show copied links; user can set keyboard shortcut via menu command

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         EZLinkZ
// @namespace    https://github.com/codeMonkeyHopeful/EZLinkZ
// @homepage     https://github.com/codeMonkeyHopeful/EZLinkZ
// @version      1.4
// @description  Copy hrefs from selected links with toast at top; toast clickable to show copied links; user can set keyboard shortcut via menu command
// @author       CodeMonkeyHopeful
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @supportURL   https://github.com/codeMonkeyHopeful/EZLinkZ
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // Default shortcut keys (Ctrl+Shift+C)
  let shortcut = {
    ctrlKey: true,
    shiftKey: true,
    altKey: false,
    metaKey: false,
    key: "c",
  };

  // Load saved shortcut from storage
  async function loadShortcut() {
    const saved = await GM_getValue("shortcut");
    if (saved) {
      try {
        const parsed = JSON.parse(saved);
        if (parsed.key) shortcut = parsed;
      } catch {}
    }
  }

  // Save shortcut to storage
  async function saveShortcut(newShortcut) {
    await GM_setValue("shortcut", JSON.stringify(newShortcut));
    shortcut = newShortcut;
    showToast(`Shortcut updated to ${formatShortcut(newShortcut)}`);
  }

  function formatShortcut(sc) {
    let parts = [];
    if (sc.ctrlKey) parts.push("Ctrl");
    if (sc.shiftKey) parts.push("Shift");
    if (sc.altKey) parts.push("Alt");
    if (sc.metaKey) parts.push("Meta");
    parts.push(sc.key.toUpperCase());
    return parts.join("+");
  }

  // Prompt user to enter new shortcut (simple example, expects something like "Ctrl+Shift+X")
  async function promptShortcut() {
    const input = prompt(
      `Enter new shortcut keys separated by +\nExample: Ctrl+Shift+X\nCurrent: ${formatShortcut(
        shortcut
      )}`
    );
    if (!input) return;

    // Parse input
    const parts = input
      .toLowerCase()
      .split("+")
      .map((s) => s.trim());
    const newSc = {
      ctrlKey: parts.includes("ctrl"),
      shiftKey: parts.includes("shift"),
      altKey: parts.includes("alt"),
      metaKey: parts.includes("meta"),
      key:
        parts.find((k) => !["ctrl", "shift", "alt", "meta"].includes(k)) || "c",
    };

    await saveShortcut(newSc);
  }

  // Toast and popup elements
  const toast = document.createElement("div");
  const popup = document.createElement("div");
  const closeBtn = document.createElement("button");
  let popupVisible = false;

  // Style toast (top center)
  Object.assign(toast.style, {
    position: "fixed",
    top: "20px",
    left: "50%",
    transform: "translateX(-50%)",
    backgroundColor: "rgba(60,60,60,0.9)",
    color: "#fff",
    padding: "10px 20px",
    borderRadius: "5px",
    fontSize: "14px",
    fontFamily: "sans-serif",
    zIndex: 9999,
    opacity: "0",
    transition: "opacity 0.3s ease",
    pointerEvents: "auto",
    cursor: "pointer",
    userSelect: "none",
    maxWidth: "80vw",
    boxSizing: "border-box",
  });
  document.body.appendChild(toast);

  // Style popup (hidden by default)
  Object.assign(popup.style, {
    position: "fixed",
    top: "60px",
    left: "50%",
    transform: "translateX(-50%)",
    backgroundColor: "rgba(30,30,30,0.95)",
    color: "#fff",
    padding: "15px 20px 20px 20px",
    borderRadius: "8px",
    fontSize: "13px",
    fontFamily: "monospace",
    zIndex: 10000,
    maxHeight: "300px",
    maxWidth: "90vw",
    overflowY: "auto",
    boxSizing: "border-box",
    display: "none",
    whiteSpace: "pre-wrap",
    wordBreak: "break-word",
  });
  document.body.appendChild(popup);

  // Close button inside popup
  closeBtn.textContent = "×";
  Object.assign(closeBtn.style, {
    position: "absolute",
    top: "5px",
    right: "10px",
    background: "transparent",
    border: "none",
    color: "#fff",
    fontSize: "20px",
    cursor: "pointer",
    userSelect: "none",
  });
  popup.appendChild(closeBtn);

  // Show toast message for duration (ms)
  let toastTimeout;
  function showToast(message, duration = 2500) {
    if (popupVisible) return; // don't show toast if popup open
    toast.textContent = message;
    toast.style.opacity = "1";
    clearTimeout(toastTimeout);
    toastTimeout = setTimeout(() => {
      toast.style.opacity = "0";
    }, duration);
  }

  // Show popup with links text
  function showPopup(text) {
    popup.textContent = text;
    popup.appendChild(closeBtn);
    popup.style.display = "block";
    popupVisible = true;
    toast.style.opacity = "0";
  }

  // Hide popup
  function hidePopup() {
    popup.style.display = "none";
    popupVisible = false;
  }

  // Get links from selection
  function getSelectedLinks() {
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
      return null;
    }
    const range = selection.getRangeAt(0);
    const container = document.createElement("div");
    container.appendChild(range.cloneContents());

    const links = container.querySelectorAll("a");
    const hrefs = Array.from(links)
      .map((a) => a.href)
      .filter((href) => href);

    return hrefs.length > 0 ? hrefs : null;
  }

  // Copy selected links and handle UI
  function copySelectedLinks() {
    const hrefs = getSelectedLinks();
    if (!hrefs) {
      showToast("No links found in selection");
      return;
    }

    const textToCopy = hrefs.join("\n");
    navigator.clipboard
      .writeText(textToCopy)
      .then(() => {
        showToast(`Copied ${hrefs.length} link(s) to clipboard`);
        // When toast clicked, show popup with links
        toast.onclick = () => showPopup(textToCopy);
      })
      .catch(() => showToast("Failed to copy to clipboard"));
  }

  // Hide popup when clicking outside it
  document.addEventListener("click", (e) => {
    if (!popupVisible) return;
    if (popup.contains(e.target) || toast.contains(e.target)) return;
    hidePopup();
  });

  // Close button click
  closeBtn.addEventListener("click", () => {
    hidePopup();
  });

  // Keyboard shortcut listener
  document.addEventListener("keydown", (e) => {
    const keyMatches =
      e.key.toLowerCase() === shortcut.key.toLowerCase() &&
      e.ctrlKey === !!shortcut.ctrlKey &&
      e.shiftKey === !!shortcut.shiftKey &&
      e.altKey === !!shortcut.altKey &&
      e.metaKey === !!shortcut.metaKey;

    if (keyMatches) {
      e.preventDefault();
      copySelectedLinks();
    }
  });

  // Register Tampermonkey menu command to set shortcut
  GM_registerMenuCommand("Set Keyboard Shortcut", promptShortcut);

  // Load shortcut from storage on start
  loadShortcut();
})();