diff --git a/src/pages/ContentScripts/features/developer-avator-openrank/index.tsx b/src/pages/ContentScripts/features/developer-avator-openrank/index.tsx index 8ad1db8d..8e6dff38 100644 --- a/src/pages/ContentScripts/features/developer-avator-openrank/index.tsx +++ b/src/pages/ContentScripts/features/developer-avator-openrank/index.tsx @@ -1,5 +1,4 @@ import features from '../../../../feature-manager'; - import { getOpenrank } from '../../../../api/developer'; import elementReady from 'element-ready'; @@ -12,54 +11,129 @@ const getData = async (developerName: string): Promise => { const keys = Object.keys(jsonData); if (keys.length === 0) return null; - keys.sort(); // 按键排序,假设键是 ISO 日期格式 + keys.sort(); // 假设键是按时间顺序排列的 const latestKey = keys[keys.length - 1]; return jsonData[latestKey]; }; -// 获取开发者名称的函数 const getDeveloperName = (target: HTMLElement): string | null => { const hovercardUrl = target.getAttribute('data-hovercard-url'); if (!hovercardUrl) return null; - // 假设 URL 格式为 /users/tyn1998/hovercard const matches = hovercardUrl.match(/\/users\/([^\/]+)\/hovercard/); return matches ? matches[1] : null; }; -// 初始化函数 +const createTooltip = (text: string): HTMLDivElement => { + const tooltip = document.createElement('div'); + tooltip.id = 'openrank-tooltip'; + tooltip.style.position = 'absolute'; + tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; + tooltip.style.color = 'white'; + tooltip.style.padding = '5px 10px'; + tooltip.style.borderRadius = '4px'; + tooltip.style.zIndex = '1000'; + tooltip.style.pointerEvents = 'none'; // 避免阻挡鼠标事件 + tooltip.innerText = text; + document.body.appendChild(tooltip); + return tooltip; +}; + const init = async (): Promise => { - // 监听具有 data-hovercard-type="user" 属性的元素 - document.querySelectorAll('[data-hovercard-type="user"]').forEach((element) => { - element.addEventListener('mouseover', async () => { - console.log('mouseover', element); - - // 获取开发者名称 - const developerName = getDeveloperName(element as HTMLElement); - if (!developerName) { - console.error('Developer name not found'); - return; + // 函数来处理鼠标悬停事件 + const handleMouseOver = async (element: HTMLElement, event: MouseEvent) => { + const developerName = getDeveloperName(element); + if (!developerName) { + console.error('Developer name not found'); + return; + } + + console.log('Developer Name:', developerName); + + const openrank = await getData(developerName); + if (openrank === null) { + console.error('Rank data not found'); + return; + } + + const openrankText = `OpenRank ${openrank}`; + let tooltip = document.getElementById('openrank-tooltip') as HTMLDivElement; + + if (!tooltip) { + tooltip = createTooltip(openrankText); + } else { + tooltip.innerText = openrankText; + } + + const updateTooltipPosition = (e: MouseEvent) => { + const tooltipWidth = tooltip.offsetWidth; + const tooltipHeight = tooltip.offsetHeight; + const offsetX = 15; + const offsetY = 15; + + let left = e.clientX + offsetX + window.scrollX; + let top = e.clientY + offsetY + window.scrollY; + + // Check if the tooltip goes beyond the right edge of the viewport + if (left + tooltipWidth > window.innerWidth + window.scrollX) { + left = e.clientX - tooltipWidth - offsetX + window.scrollX; } - console.log('developerName', developerName); + // Check if the tooltip goes beyond the bottom edge of the viewport + if (top + tooltipHeight > window.innerHeight + window.scrollY) { + top = e.clientY - tooltipHeight - offsetY + window.scrollY; + } - // 获取悬浮卡片容器 - const $popoverContainer = 'body > div.sr-only.mt-n1'; - const popover = await elementReady($popoverContainer, { stopOnDomReady: false }); - console.log('popover', popover); + // Ensure tooltip doesn't go beyond the left edge of the viewport + if (left < window.scrollX) { + left = window.scrollX; + } - // 获取开发者的排名信息 - const openrank = await getData(developerName); - if (openrank === null) { - console.error('Rank data not found'); - return; + // Ensure tooltip doesn't go beyond the top edge of the viewport + if (top < window.scrollY) { + top = window.scrollY; } - // 将 OpenRank 信息作为红色文本直接插入到悬浮卡片容器内容的前面 - if (popover) { - popover.innerHTML = `OpenRank ${openrank} ` + popover.innerHTML; + tooltip.style.left = `${left}px`; + tooltip.style.top = `${top}px`; + }; + + updateTooltipPosition(event); + const mouseMoveHandler = updateTooltipPosition as EventListener; + element.addEventListener('mousemove', mouseMoveHandler); + + element.addEventListener( + 'mouseout', + () => { + if (tooltip) { + tooltip.remove(); + } + element.removeEventListener('mousemove', mouseMoveHandler); + }, + { once: true } + ); + }; + + const bindEventListeners = () => { + document.querySelectorAll('[data-hovercard-type="user"]').forEach((element) => { + if (!element.getAttribute('data-openrank-bound')) { + element.setAttribute('data-openrank-bound', 'true'); + element.addEventListener('mouseover', (event) => handleMouseOver(element as HTMLElement, event as MouseEvent)); } }); + }; + + // 初始绑定 + bindEventListeners(); + + // 观察 DOM 变化,动态为新添加的元素绑定事件 + const observer = new MutationObserver(() => { + bindEventListeners(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, }); };