YouTube Channel Hover Popup

Display a hover popup with channel info on YouTube after dynamic content load, with immediate loading indicator

目前為 2024-05-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube Channel Hover Popup
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  Display a hover popup with channel info on YouTube after dynamic content load, with immediate loading indicator
// @author       @dmtri
// @match        https://www.youtube.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const MAGIC_NUMBER = 2500;
    const initTag = '.youtube-popup-desc-init';

    const createPopup = () => {
        const popup = document.createElement('div');
        popup.style.position = 'fixed';
        popup.style.zIndex = '1000';
        popup.style.width = '300px';
        popup.style.background = 'white';
        popup.style.border = '1px solid black';
        popup.style.borderRadius = '8px';
        popup.style.padding = '16px';
        popup.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
        popup.style.display = 'none';
        popup.style.fontSize = '16px';
        document.body.appendChild(popup);
        return popup;
    };

    const popup = createPopup(); // Initialize the popup

    const showLoadingPopup = (popup, x, y) => {
        popup.innerHTML = '<strong>Loading...</strong>';
        popup.style.left = `${x}px`;
        popup.style.top = `${y}px`;
        popup.style.display = 'block';
    };

    const updatePopupContent = (popup, content) => {
        popup.innerHTML = content;
        // Close button
        const closeButton = document.createElement('button');
        closeButton.textContent = 'X';
        closeButton.style.position = 'absolute';
        closeButton.style.top = '5px';
        closeButton.style.right = '10px';
        closeButton.style.border = 'none';
        closeButton.style.background = 'none';
        closeButton.style.cursor = 'pointer';
        closeButton.style.color = '#333';
        closeButton.style.fontSize = '16px';
        closeButton.style.fontWeight = 'bold';

        closeButton.onclick = () => {
            popup.style.display = 'none';
        };

        popup.appendChild(closeButton);
    };

    const hidePopup = (popup) => {
        popup.style.display = 'none';
    };

    const fetchChannelInfo = async (url) => {
        try {
            const response = await fetch(url);
            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const meta = doc.querySelector('meta[property="og:description"]');
            const description = meta ? meta.getAttribute('content') : 'No description available.';
            return `<strong>Description:</strong> ${description}<br><a href="${url}">View Channel</a>`;
        } catch (error) {
            return 'Failed to load description.';
        }
    };

    const init = () => {
        document.querySelectorAll('.ytd-channel-name#text-container').forEach(channelElement => {
            let popupTimeout;

            channelElement.addEventListener('mouseenter', async (e) => {
                clearTimeout(popupTimeout);
                popupTimeout = setTimeout(async () => {
                    const url = channelElement.querySelector('a').href;
                    showLoadingPopup(popup, e.clientX, e.clientY + 20);
                    const content = await fetchChannelInfo(url);
                    updatePopupContent(popup, content);
                }, 500); // Reduced the delay to show the loading message sooner
            });

            channelElement.addEventListener('mouseleave', setTimeout(() => {
                clearTimeout(popupTimeout);
                hidePopup(popup);
            }, 1000));

            channelElement.addEventListener('mousemove', (e) => {
                if (popup.style.display !== 'none') {
                    popup.style.left = `${e.clientX}px`;
                    popup.style.top = `${e.clientY + 20}px`;
                }
            });
        });
    };
    const tryInit = () => {
		if (!document.querySelector(initTag)) {
			setTimeout(() => {
				init();
				tryInit();
			}, MAGIC_NUMBER);
		}
	};
	tryInit();
})();