// ==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
// @downloadURL none
// ==/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 = 'Loading...';
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 `Description: ${description}
View Channel`;
} 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();
})();