Skip to content

Commit

Permalink
feat: Migrate developer hovercard info to Gitee (#970)
Browse files Browse the repository at this point in the history
* feat: Migrate repo labels to Gitee

* feat: Migrate developer hovercard to Gitee

* fix

* fix

* fix: Fixed the timer issue and removed unnecessary parameters.

* feat: Fixed openrank display for different home pages

* fix: Fixed a bug where openrank would not appear on the first mouse hover

* fix: Remove unnecessary code
  • Loading branch information
Xsy41 authored Mar 8, 2025
1 parent 33f5c35 commit a53b027
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import features from '../../../../feature-manager';
import { getOpenrank } from '../../../../api/developer';
import React from 'react';
import View from './gitee-view';
import { createRoot } from 'react-dom/client';
import { getPlatform } from '../../../../helpers/get-platform';
import isGitee from '../../../../helpers/is-gitee';
const featureId = features.getFeatureID(import.meta.url);
let isInitialized = false;
let platform: string;

interface OpenrankCacheEntry {
timestamp: number;
data: string | null;
}
const OPENRANK_CACHE_EXPIRY = 5 * 60 * 1000;
const openrankCache = new Map<string, OpenrankCacheEntry>();

const getDeveloperLatestOpenrank = async (developerName: string): Promise<string | null> => {
const cached = openrankCache.get(developerName);
if (cached && Date.now() - cached.timestamp > OPENRANK_CACHE_EXPIRY) {
openrankCache.delete(developerName);
} else if (cached) {
return cached.data;
}

try {
const data = await getOpenrank(platform, developerName);
if (data) {
const monthKeys = Object.keys(data).filter((key) => /^\d{4}-\d{2}$/.test(key));
if (monthKeys.length === 0) {
openrankCache.set(developerName, { timestamp: Date.now(), data: null });
return null;
}
monthKeys.sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
const latestMonthKey = monthKeys[monthKeys.length - 1];
const result = data[latestMonthKey];
openrankCache.set(developerName, { timestamp: Date.now(), data: result });
return result;
}
} catch (error) {
openrankCache.set(developerName, { timestamp: Date.now(), data: null });
}
return null;
};
const waitForValidPopover = async (): Promise<HTMLElement | null> => {
return new Promise((resolve) => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.attributeName === 'class') {
const target = mutation.target as HTMLElement;
if (target.classList.contains('popper-profile-card') && !target.classList.contains('hidden')) {
observer.disconnect();
resolve(target);
return;
}
}
}
});

observer.observe(document.body, {
attributeFilter: ['class'],
subtree: true,
childList: true,
});
});
};

const processElement = async (element: Element) => {
let abortController: AbortController | null = null;
element.addEventListener(
'mouseover',
async () => {
abortController?.abort();
abortController = new AbortController();

function waitForProfileLink(popover: HTMLElement, waitfor: string): Promise<HTMLAnchorElement> {
return new Promise((resolve) => {
let profileLink = popover.querySelector(waitfor) as HTMLAnchorElement;
if (profileLink) return resolve(profileLink);

const observer = new MutationObserver(() => {
profileLink = popover.querySelector(waitfor) as HTMLAnchorElement;
if (profileLink) {
observer.disconnect();
resolve(profileLink);
}
});

observer.observe(popover, { childList: true, subtree: true });
});
}

const popover = (await Promise.race([waitForValidPopover()])) as HTMLElement;
const profileLink = await waitForProfileLink(popover, '.popper-profile-card__body a');
const rawHref = profileLink.getAttribute('href');
if (!rawHref) return;
const url = new URL(rawHref, window.location.origin);
const cardUsername = url.pathname.replace('/', '');

if (!cardUsername) return;

const existing = popover.querySelector(`[data-username="${cardUsername}"]`);
if (existing) return;

const existingOpenRank = popover.querySelector('.hypercrx-openrank-info');
if (existingOpenRank) return;
const openrank = await getDeveloperLatestOpenrank(cardUsername);
if (!openrank) {
return;
}
const footer = popover.querySelector('.popper-profile-card__content') as HTMLElement;
if (footer && !footer.querySelector(`[data-username="${cardUsername}"]`)) {
const openrankContainer = document.createElement('div');
openrankContainer.dataset.username = cardUsername;
footer.appendChild(openrankContainer);
createRoot(openrankContainer).render(<View {...{ developerName: cardUsername, openrank }} />);
}
},
{ once: true }
);
};

const init = async (): Promise<void> => {
platform = getPlatform();
if (isInitialized) return;
isInitialized = true;

const hovercardSelectors = [
'a.js-popover-card',
'div.d-flex',
'span.author',
'a.avatar',
'span.js-popover-card',
'img.js-popover-card',
];

const processExisting = () => {
hovercardSelectors.forEach((selector) => {
document.querySelectorAll(selector).forEach((element) => {
if (!element.hasAttribute('data-hypercrx-processed')) {
element.setAttribute('data-hypercrx-processed', 'true');
processElement(element);
}
});
});
};

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
processExisting();
}
});
});

observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
});
};

features.add(featureId, {
asLongAs: [isGitee],
awaitDomReady: false,
init,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import '../../../../helpers/i18n';

interface OpenRankProps {
developerName: string;
openrank: string;
}

const View: React.FC<OpenRankProps> = ({ developerName, openrank }) => {
const textColor = '#636c76';
const fontSize = '12px';

return (
<div className={`hypercrx-openrank-info`} data-developer-name={developerName}>
<span
style={{
display: 'inline-block',
verticalAlign: 'middle',
lineHeight: '1.25 !important ',
color: textColor,
fontSize: fontSize,
}}
>
OpenRank {openrank}
</span>
</div>
);
};

export default View;
1 change: 1 addition & 0 deletions src/pages/ContentScripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import './features/oss-gpt';
import './features/repo-activity-racing-bar';
import './features/repo-activity-racing-bar/gitee-index';
import './features/developer-hovercard-info';
import './features/developer-hovercard-info/gitee-index';
import './features/repo-sidebar-labels';
import './features/repo-sidebar-labels/gitee-index';
import './features/fast-pr';

0 comments on commit a53b027

Please sign in to comment.