From 32c63691d645585288453f718384a39b78cf54ec Mon Sep 17 00:00:00 2001 From: Tenth-crew <68376741+Tenth-crew@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:58:29 +0800 Subject: [PATCH] feat: display openrank information in developer information hover card (#832) * Modified the developer name acquisition method and tried other monitoring methods * modify evenlistener method and add openrank info * feat: add the developer-avator-openrank * Modified the presentation of OpenRank * Improved the situation when openrank does not exist * Introduce openrank information to the bottom of the hovercard * Modify the names of some functions and fields * Add view.tsx and Adjust some details * Adjust openrank icon position * Solved the problem of mismatch between OpenRank information and people * Fixed the bug that OpenRank information was added multiple times * Adjust the display position of OpenRank * Introducing the renderTo function --- .../developer-hovercard-info/base64.ts | 5 ++ .../developer-hovercard-info/index.tsx | 82 +++++++++++++++++++ .../developer-hovercard-info/view.tsx | 35 ++++++++ src/pages/ContentScripts/index.ts | 1 + 4 files changed, 123 insertions(+) create mode 100644 src/pages/ContentScripts/features/developer-hovercard-info/base64.ts create mode 100644 src/pages/ContentScripts/features/developer-hovercard-info/index.tsx create mode 100644 src/pages/ContentScripts/features/developer-hovercard-info/view.tsx diff --git a/src/pages/ContentScripts/features/developer-hovercard-info/base64.ts b/src/pages/ContentScripts/features/developer-hovercard-info/base64.ts new file mode 100644 index 00000000..7d2a7452 --- /dev/null +++ b/src/pages/ContentScripts/features/developer-hovercard-info/base64.ts @@ -0,0 +1,5 @@ +export const rocketLight = + 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdD0iMTY2NDg5Mjg4OTU4NiIKICAgY2xhc3M9Imljb24iCiAgIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiCiAgIHZlcnNpb249IjEuMSIKICAgcC1pZD0iNzQzMTAiCiAgIHdpZHRoPSIzMiIKICAgaGVpZ2h0PSIzMiIKICAgaWQ9InN2ZzYiCiAgIHNvZGlwb2RpOmRvY25hbWU9IueBq+eurSAoNSkucG5nIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjIgKGRjMmFlZGEsIDIwMjItMDUtMTUpIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMxMCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9Im5hbWVkdmlldzgiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjMDAwMDAwIgogICAgIGJvcmRlcm9wYWNpdHk9IjAuMjUiCiAgICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iI2QxZDFkMSIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iNy4zNzUiCiAgICAgaW5rc2NhcGU6Y3g9IjE1LjkzMjIwMyIKICAgICBpbmtzY2FwZTpjeT0iMTQuOTE1MjU0IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTMxMyIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI0NTgiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjM0NiIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMTI5MSIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzYiIC8+CiAgPHBhdGgKICAgICBkPSJtIDUwNy4zODUxMywzNzYuNTk3ODIgYyAtNDYuODk5OTMsMCAtODUuMDU3NjIsMzguMTU3NjkgLTg1LjA1NzYyLDg1LjA1NzYxIDAsNDYuODk5OTMgMzguMTU3NjksODUuMDU2NDkgODUuMDU3NjIsODUuMDU2NDkgNDYuODk5OTIsMCA4NS4wNTc2MSwtMzguMTU3NjggODUuMDU3NjEsLTg1LjA1NzYxIDAsLTQ2Ljg5OTkzIC0zOC4xNTc2OSwtODUuMDU2NDkgLTg1LjA1NzYxLC04NS4wNTY0OSB6IG0gMCwxMDYuMzIwODkgYyAtMTEuNzI3NTEsMCAtMjEuMjY0NDEsLTkuNTQyNTEgLTIxLjI2NDQxLC0yMS4yNjQ0IDAsLTExLjcyMTg5IDkuNTM2OSwtMjEuMjY0NCAyMS4yNjQ0MSwtMjEuMjY0NCAxMS43Mjc1MSwwIDIxLjI2NDQsOS41NDI1MSAyMS4yNjQ0LDIxLjI2NDQgMCwxMS43MjMwMSAtOS41MzY4OSwyMS4yNjQ0IC0yMS4yNjQ0LDIxLjI2NDQgeiIKICAgICBwLWlkPSI3NDMxMSIKICAgICBmaWxsPSIjNTc2MDZhIgogICAgIGlkPSJwYXRoMiIKICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjA7c3Ryb2tlLWRhc2hhcnJheTpub25lIiAvPgogIDxwYXRoCiAgICAgZD0ibSA3MzguMjk3MDUsNjIzLjUwOTQ3IHYgLTk1Ljk3MDI0IGMgMCwtODcuNjk0NDUgLTI1LjE0NzcyLC0xNTEuNjY0MTIgLTcyLjcyMjAzLC0yMjUuMjA2NjUgLTMxLjY1Nzc2LC00OC45MzU0NCAtNzIuMjM0MjMsLTkwLjc5NDM2IC0xMTkuMjAxNiwtMTI0LjA5NzYxIC0xMi41NjgyNCwtOC45MDg1OSAtMjUuNTIwODgsLTE3LjI5MjI5IC0zOC45ODgyOSwtMjQuOTEzOTQgLTEzLjQ2MTgsNy42MjE2NSAtMjYuNDE0NDQsMTYuMDA1MzUgLTM4Ljk4MjY4LDI0LjkxMzk0IC00Ni45NjczNywzMy4zMDMyNSAtODcuNTQzODQsNzUuMTYyMTcgLTExOS4yMDE2LDEyNC4wOTc2MSAtNDcuNTc0MzEsNzMuNTQyNTMgLTcyLjcyMjAzLDEzNy41MTIyIC03Mi43MjIwMywyMjUuMjA2NjUgdiA5NS45NzAyNCBsIC00MS45ODM2OCw4NC41MTY5OCB2IDk3Ljg1OTY0IGggNTQ1Ljc4NTU5IHYgLTk3Ljg1OTY0IHogbSAtMjEuODA5NTMsMTE4LjU4MzQxIGggLTQxOC4yMDAzIHYgLTcuNjUxOTkgTCAzNDAuMjcwOSw2NDkuOTIzOTEgViA1MjcuNTM5MjMgYyAwLC03NS4zNzAxMSAyMS42MTE3MSwtMTI3LjM1NzEzIDYyLjQ4OTQxLC0xOTAuNTU4MDEgMjcuNzA3LC00Mi44MjQ0MSA2My4zMDk5LC03OS4zNjY5NCAxMDQuNjIzNjksLTEwOC4xOTU2NyA0MS4zMTM4LDI4LjgyODczIDc2LjkyMjMxLDY1LjM3MTI2IDEwNC42MjkzMiwxMDguMTk1NjcgNDAuODc3NjksNjMuMjAwODggNjIuNDg5NCwxMTUuMTg5MDIgNjIuNDg5NCwxOTAuNTU4MDEgdiAxMjIuMzgzNTYgbCA0MS45ODM2OCw4NC41MTY5OCB2IDcuNjUzMTEgeiBtIC0yMDkuMDk3OSwxMjcuMzg0MTEgYyAtMjUuMjA1MDQsLTEyLjg3NTA5IC00MS4zNjU0OSwtMjguNDU2NyAtNjAuNjAwMDEsLTQ2LjMyODk1IGggLTkwLjY5NTQ0IGMgMzkuOTc0MDIsNDYuNDkxOTIgODEuMDQzOTEsODQuMzgyMSAxMzcuODgwODYsMTEwLjczNDcyIGwgMTMuNDE0NTksNi4yMTg5MyAxMy40MTQ1OSwtNi4yMTg5MyBjIDU2LjgzMTM0LC0yNi4zNTE0OSA5Ny44OTU2MSwtNjQuMjM5NDMgMTM3Ljg3MDc1LC0xMTAuNzM0NzIgaCAtOTAuNjg1MzMgYyAtMTkuMjMzMzksMTcuODcxMTMgLTM1LjM5NDk2LDMzLjQ1Mzg2IC02MC42MDAwMSw0Ni4zMjg5NSB6IgogICAgIHAtaWQ9Ijc0MzEyIgogICAgIGZpbGw9IiM1NzYwNmEiCiAgICAgaWQ9InBhdGg0IgogICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MDtzdHJva2UtZGFzaGFycmF5Om5vbmUiIC8+Cjwvc3ZnPgo='; + +export const rocketDark = + 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdD0iMTY2NDg5Mjg4OTU4NiIKICAgY2xhc3M9Imljb24iCiAgIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiCiAgIHZlcnNpb249IjEuMSIKICAgcC1pZD0iNzQzMTAiCiAgIHdpZHRoPSIzMiIKICAgaGVpZ2h0PSIzMiIKICAgaWQ9InN2ZzYiCiAgIHNvZGlwb2RpOmRvY25hbWU9IueBq+eurSAoNSlkYXJrLnBuZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4yIChkYzJhZWRhLCAyMDIyLTA1LTE1KSIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzMTAiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJuYW1lZHZpZXc4IgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzAwMDAwMCIKICAgICBib3JkZXJvcGFjaXR5PSIwLjI1IgogICAgIGlua3NjYXBlOnNob3dwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMCIKICAgICBpbmtzY2FwZTpkZXNrY29sb3I9IiNkMWQxZDEiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjcuMzc1IgogICAgIGlua3NjYXBlOmN4PSIxNS45MzIyMDMiCiAgICAgaW5rc2NhcGU6Y3k9IjE2IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTMxMyIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI0NTgiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjM1NyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMTM3NSIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzYiIC8+CiAgPHBhdGgKICAgICBkPSJNNTExLjk5OCAzNjAuNjQ5Yy00MS43MjcgMC03NS42NzYgMzMuOTQ5LTc1LjY3NiA3NS42NzZTNDcwLjI3MSA1MTIgNTExLjk5OCA1MTJzNzUuNjc2LTMzLjk0OSA3NS42NzYtNzUuNjc2LTMzLjk0OS03NS42NzUtNzUuNjc2LTc1LjY3NXogbTAgOTQuNTk0Yy0xMC40MzQgMC0xOC45MTktOC40OS0xOC45MTktMTguOTE5czguNDg1LTE4LjkxOSAxOC45MTktMTguOTE5YzEwLjQzNCAwIDE4LjkxOSA4LjQ5IDE4LjkxOSAxOC45MTkgMCAxMC40My04LjQ4NSAxOC45MTktMTguOTE5IDE4LjkxOXoiCiAgICAgcC1pZD0iNzQzMTEiCiAgICAgZmlsbD0iIzU3NjA2YSIKICAgICBpZD0icGF0aDIiCiAgICAgc3R5bGU9ImZpbGw6IzhiOTQ5ZTtmaWxsLW9wYWNpdHk6MSIgLz4KICA8cGF0aAogICAgIGQ9Ik03MTcuNDQxIDU4MC4zMjd2LTg1LjM4NWMwLTc4LjAyMi0yMi4zNzQtMTM0LjkzNi02NC43MDEtMjAwLjM2Ny0yOC4xNjYtNDMuNTM4LTY0LjI2Ny04MC43OC0xMDYuMDU0LTExMC40MS0xMS4xODItNy45MjYtMjIuNzA2LTE1LjM4NS0zNC42ODgtMjIuMTY2LTExLjk3NyA2Ljc4MS0yMy41MDEgMTQuMjQtMzQuNjgzIDIyLjE2Ni00MS43ODcgMjkuNjMtNzcuODg4IDY2Ljg3Mi0xMDYuMDU0IDExMC40MS00Mi4zMjcgNjUuNDMxLTY0LjcwMSAxMjIuMzQ1LTY0LjcwMSAyMDAuMzY3djg1LjM4NWwtMzcuMzUzIDc1LjE5NXY4Ny4wNjZoNDg1LjU4N3YtODcuMDY2bC0zNy4zNTMtNzUuMTk1eiBtLTE5LjQwNCAxMDUuNTA0SDMyNS45NjN2LTYuODA4bDM3LjM1My03NS4xOTVWNDk0Ljk0MmMwLTY3LjA1NyAxOS4yMjgtMTEzLjMxIDU1LjU5Ny0xNjkuNTQgMjQuNjUxLTM4LjEwMSA1Ni4zMjctNzAuNjEzIDkzLjA4NC05Ni4yNjIgMzYuNzU3IDI1LjY0OSA2OC40MzggNTguMTYxIDkzLjA4OSA5Ni4yNjIgMzYuMzY5IDU2LjIzIDU1LjU5NyAxMDIuNDg0IDU1LjU5NyAxNjkuNTR2MTA4Ljg4NWwzNy4zNTMgNzUuMTk1djYuODA5ek01MTIuMDAyIDc5OS4xNjVjLTIyLjQyNS0xMS40NTUtMzYuODAzLTI1LjMxOC01My45MTYtNDEuMjE5aC04MC42OTJjMzUuNTY1IDQxLjM2NCA3Mi4xMDUgNzUuMDc1IDEyMi42NzMgOTguNTIxTDUxMi4wMDIgODYybDExLjkzNS01LjUzM2M1MC41NjMtMjMuNDQ1IDg3LjA5OC01Ny4xNTQgMTIyLjY2NC05OC41MjFoLTgwLjY4M2MtMTcuMTEyIDE1LjktMzEuNDkxIDI5Ljc2NC01My45MTYgNDEuMjE5eiIKICAgICBwLWlkPSI3NDMxMiIKICAgICBmaWxsPSIjNTc2MDZhIgogICAgIGlkPSJwYXRoNCIKICAgICBzdHlsZT0iZmlsbDojOGI5NDllO2ZpbGwtb3BhY2l0eToxIiAvPgo8L3N2Zz4K'; diff --git a/src/pages/ContentScripts/features/developer-hovercard-info/index.tsx b/src/pages/ContentScripts/features/developer-hovercard-info/index.tsx new file mode 100644 index 00000000..b6f5c6cf --- /dev/null +++ b/src/pages/ContentScripts/features/developer-hovercard-info/index.tsx @@ -0,0 +1,82 @@ +import features from '../../../../feature-manager'; +import { getOpenrank } from '../../../../api/developer'; +import elementReady from 'element-ready'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import View from './view'; + +const featureId = features.getFeatureID(import.meta.url); + +const getDeveloperLatestOpenrank = async (developerName: string): Promise => { + const data = await getOpenrank(developerName); + if (data) { + const values = Object.values(data) as string[]; + const latestValue = values[values.length - 1]; + return latestValue; + } + return null; +}; + +const getDeveloperName = (target: HTMLElement): string | null => { + const hovercardUrlAttribute = target.getAttribute('data-hovercard-url'); + if (!hovercardUrlAttribute) return null; + + const matches = hovercardUrlAttribute.match(/\/users\/([^\/]+)\/hovercard/); + return matches ? matches[1] : null; +}; + +const renderTo = (container: HTMLElement, developerName: string, openrank: string) => { + const openRankContainer = document.createElement('div'); + container.appendChild(openRankContainer); + ReactDOM.render(, openRankContainer); +}; + +const init = async (): Promise => { + let abortController = new AbortController(); + const hovercardSelector = '[data-hovercard-type="user"]'; + document.querySelectorAll(hovercardSelector).forEach((element) => { + // isProcessing is used to Prevent OpenRank from adding duplicates + element.addEventListener('mouseover', async () => { + abortController.abort(); + abortController = new AbortController(); + const signal = abortController.signal; + + const developerName = getDeveloperName(element as HTMLElement) as string; + + // Create a unique identifier for the popover + const popoverId = `popover-${developerName}`; + + // Get the floating card container + const $popoverContainer = + 'body > div.logged-in.env-production.page-responsive > div.Popover.js-hovercard-content.position-absolute > div > div > div'; + const popover = await elementReady($popoverContainer, { stopOnDomReady: false }); + + const openRankDiv = popover?.querySelector('.hypercrx-openrank-info'); + const existingDeveloperName = openRankDiv?.getAttribute('data-developer-name'); + if (existingDeveloperName === developerName) { + return; + } + openRankDiv?.remove(); + + // Set the popover's unique identifier + // make the current OpenRank information and person match + popover?.setAttribute('data-popover-id', popoverId); + + const openrank = await getDeveloperLatestOpenrank(developerName); + + if (!openrank) { + return; + } + + if (!signal.aborted && popover && popover.getAttribute('data-popover-id') === popoverId) { + // Check if the popover is still associated with the correct developer + renderTo(popover, developerName, openrank); + } + }); + }); +}; + +features.add(featureId, { + awaitDomReady: false, + init, +}); diff --git a/src/pages/ContentScripts/features/developer-hovercard-info/view.tsx b/src/pages/ContentScripts/features/developer-hovercard-info/view.tsx new file mode 100644 index 00000000..7c317137 --- /dev/null +++ b/src/pages/ContentScripts/features/developer-hovercard-info/view.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import getGithubTheme from '../../../../helpers/get-github-theme'; +import '../../../../helpers/i18n'; +import { rocketLight, rocketDark } from './base64'; + +interface OpenRankProps { + developerName: string; + openrank: string; +} + +const View: React.FC = ({ developerName, openrank }) => { + const theme = getGithubTheme() as 'light' | 'dark'; + + const textColor = theme === 'light' ? '#717981' : '#878f98'; + const fontSize = '13px'; + + return ( +
+
+ + + OpenRank {openrank} + +
+
+ ); +}; + +export default View; diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index bab274a3..11a5dc6f 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -13,3 +13,4 @@ import './features/repo-networks'; import './features/developer-networks'; import './features/oss-gpt'; import './features/repo-activity-racing-bar'; +import './features/developer-hovercard-info';