/**
* @module toggleLinks
* @file toggleLinks.js - Toggle between magnet/torrent links and copy magnet links to clipboard.
* @description Provides UI controls for switching between magnet link and direct torrent link
* display modes in I2PSnark. Also enables copying magnet links to the system clipboard with
* a toast notification on success.
* @author dr|z3d
* @license AGPL3 or later
*/
"use strict";
/**
* @type {Document}
* @description Shorthand reference to the global document object.
*/
const d = document;
/**
* @type {HTMLElement}
* @description The root HTML element of the document.
*/
const htmlTag = d.documentElement;
/**
* @type {?HTMLStyleElement}
* @description The style element controlling link display toggling.
*/
const toggleCss = d.head.querySelector("#toggleLinks");
/**
* @type {?HTMLInputElement}
* @description The checkbox input controlling magnet/link display mode.
*/
const toggle = d.getElementById("linkswitch");
/**
* @type {?HTMLElement}
* @description The toast notification element for clipboard copy feedback.
*/
const toast = d.getElementById("toast");
/**
* @type {?HTMLElement}
* @description The main page container element.
*/
const page = d.getElementById("page");
/**
* @type {string}
* @description Current link display mode: "magnets" or "links". Persisted in localStorage.
*/
let linkToggleConfig = localStorage.getItem("linkToggle") || "magnets";
/**
* @type {boolean}
* @description Whether magnet links are currently visible in the UI.
*/
let magnetsVisible = false;
/**
* @function initLinkToggler
* @description Initializes the link toggling system. Sets up event listeners for the
* toggle checkbox, magnet link copying, and toast notifications. Manages scroll position
* preservation during link mode changes.
* @returns {void}
*/
function initLinkToggler() { // NOPMD - ConsistentReturn (nested scrollToTop return is misdetected)
if (!toggle) { return; }
/**
* @async
* @function scrollToTop
* @description Scrolls to the top of the window (and parent window if iframed),
* waits for the specified timeout, then smoothly scrolls back to the original position.
* Used to ensure toast notifications are visible.
* @param {number} timeout - Delay in milliseconds before scrolling back to original position.
* @returns {Promise<void>}
*/
async function scrollToTop(timeout) {
const X = window.pageXOffset, Y = window.pageYOffset;
const iframed = htmlTag.classList.contains("iframed") || window.top !== window.self;
const delay = iframed ? 3500 : 3750;
return new Promise((resolve) => {
window.scrollTo(0, 0);
let parentX, parentY;
if (iframed) {
parentX = parent.window.pageXOffset;
parentY = parent.window.pageYOffset;
parent.window.scrollTo(0, 0);
}
const scrollToOriginal = () => {
window.scrollTo({ top: Y, left: X, behavior: "smooth" });
if (iframed) {
parent.window.scrollTo({ top: parentY, left: parentX, behavior: "smooth" });
}
resolve();
};
setTimeout(scrollToOriginal, timeout);
});
}
/**
* @function setLinkMode
* @description Applies the current link toggle configuration to the UI by setting
* body classes and the toggle checkbox state.
* @returns {void}
*/
function setLinkMode() {
const isMagnetMode = linkToggleConfig === "magnets";
toggle.checked = isMagnetMode;
d.body.classList.toggle("magnets", isMagnetMode);
d.body.classList.toggle("tlinks", !isMagnetMode);
}
/**
* @function doToggle
* @description Toggles between magnet and link display modes, persists the choice
* to localStorage, and updates the UI.
* @returns {void}
*/
function doToggle() {
linkToggleConfig = linkToggleConfig === "magnets" ? "links" : "magnets";
localStorage["linkToggle"] = linkToggleConfig;
setLinkMode();
}
/**
* @function showToast
* @description Displays a toast notification with the given message, auto-dismisses
* after 3.5 seconds, and scrolls to ensure visibility.
* @param {string} msg - The HTML message to display in the toast.
* @returns {void}
*/
function showToast(msg) {
toast.classList.remove("dismiss");
toast.innerHTML = msg;
toast.removeAttribute("hidden");
scrollToTop(3500);
setTimeout(() => { toast.classList.add("dismiss"); }, 3500);
}
/**
* @function copyMagnetHandler
* @description Handles click events on .copyMagnet elements. Extracts the magnet link
* from the anchor's href, copies it to the clipboard, and shows a confirmation toast.
* @param {MouseEvent} event - The click event.
* @returns {void}
*/
function copyMagnetHandler(event) {
const magnetLink = event.target.closest(".copyMagnet, a.magnetlink");
if (!magnetLink) { return; }
const anchorEl = magnetLink.matches("a.magnetlink") ? magnetLink : magnetLink.closest("a.magnetlink");
if (!anchorEl) { return; }
d.body.classList.add("copyingToClipboard");
event.preventDefault();
event.stopPropagation();
const anchor = anchorEl.href;
if (anchor && anchor.startsWith("magnet:?xt=urn:btih:")) {
copyToClipboard(anchor);
const magnetURL = new URL(anchor);
let magnetHash = magnetURL.searchParams.get("xt") || "";
magnetHash = magnetHash.replace("urn:btih:", "");
let magnetName = magnetURL.searchParams.get("dn") || "";
magnetName = magnetName.replace(/</g, "<").replace(/>/g, ">");
showToast("Magnet link copied to clipboard: <b>" + magnetName + "</b><br>Hash: <b>" + magnetHash + "</b>");
setTimeout(() => { d.body.classList.remove("copyingToClipboard"); }, 4000);
} else {
d.body.classList.remove("copyingToClipboard");
showToast("Invalid magnet link.");
}
}
/**
* @async
* @function copyToClipboard
* @description Copies the given text to the system clipboard using the Clipboard API.
* Silently handles permission or API errors.
* @param {string} text - The text to copy to the clipboard.
* @returns {Promise<void>}
*/
async function copyToClipboard(text) {
try { await navigator.clipboard.writeText(text); }
catch (error) {}
}
setLinkMode();
page.addEventListener("click", copyMagnetHandler);
page.addEventListener("change", (event) => {
if (event.target.id === "linkswitch") { doToggle(); }
});
}
d.addEventListener("DOMContentLoaded", () => {
initLinkToggler();
});