/**
* @module convertTooltips
* @file convertTooltips.js - Convert title attributes to data-tooltips for enhanced styling.
* @description Converts native browser title attributes on progress bar and torrent status
* elements into custom data-tooltip attributes with CSS-based tooltip styling. Uses a
* MutationObserver to handle dynamically added elements. Provides styled pseudo-element
* tooltips with arrow indicators.
* @author dr|z3d
* @license AGPL3 or later
*/
/**
* @description Self-executing IIFE that initializes the tooltip conversion system.
* Converts title attributes to data-tooltip, injects tooltip CSS styles, and sets
* up a MutationObserver on the torrents tbody to handle dynamically added rows.
*/
(() => {
/**
* @function convertTooltip
* @description Finds all elements matching the selector that have a title attribute,
* converts the title to a data-tooltip attribute (reformatting bullet-separated content),
* removes the native title, and adds the "barTooltip" class for CSS styling.
* @param {string} selector - CSS selector to find elements with title attributes.
* @returns {void}
*/
function convertTooltip(selector) {
const elements = document.querySelectorAll(selector);
if (!elements.length) {return;}
elements.forEach((element) => {
let tooltipContent = element.getAttribute("title");
tooltipContent = tooltipContent.replace(/^(.*)\s•\s(.*)$/, '• $1\n• $2');
element.setAttribute("data-tooltip", tooltipContent);
element.removeAttribute("title");
element.classList.add("barTooltip");
});
}
const styleElement = document.createElement("style");
styleElement.innerHTML =
'.barTooltip{position:relative}' +
'.barTooltip:hover,#snarkTbody:hover .barTooltip{overflow:visible}' +
'.barTooltip::before,.barTooltip::after{position:absolute;left:50%;opacity:0;box-shadow:2px 2px 2px #0004;pointer-events:none;transition:ease .2s opacity .2s}' +
'.barTooltip::before{padding:5px 10px;position:absolute;bottom:120%;z-index:999;line-height:1.3;white-space:pre;font-size:90%;font-weight:500;color:var(--ink);border-radius:4px;background:var(--tooltip);transform:translateX(-50%);content:attr(data-tooltip)}' +
'.barTooltip::after{bottom:calc(120% - 6px);z-index:999;border:6px solid var(--tooltip);content:"";transform:translateX(-50%) rotate(45deg)}'+
'.txd .barTooltip::before{bottom:calc(128%)}' +
'.txd .barTooltip::after{bottom:calc(128% - 6px)}' +
'.barTooltip:hover::before,.barTooltip:hover::after,.txd:hover .barTooltip::before,.txd:hover .barTooltip::after{opacity:1}';
document.head.appendChild(styleElement);
convertTooltip(".tx[title], .barComplete[title]");
let convertPending = false;
/**
* @function queueConvert
* @description Schedules a single tooltip conversion per animation frame, coalescing
* the observer's per-record callbacks so a morphdom row update triggers one table
* scan instead of one per mutation.
* @returns {void}
*/
function queueConvert() {
if (convertPending) {return;}
convertPending = true;
requestAnimationFrame(() => {
convertPending = false;
convertTooltip(".tx[title], .barComplete[title]");
});
}
const observer = new MutationObserver(() => {queueConvert();});
const targetNode = document.querySelector('#torrents tbody');
if (targetNode) {
observer.observe(targetNode, { childList: true, subtree: true });
}
})();