Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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;

const getDeveloperLatestOpenrank = async (developerName: string): Promise<string | null> => {
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) {
return null;
}
monthKeys.sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
const latestMonthKey = monthKeys[monthKeys.length - 1];
return data[latestMonthKey];
}
return null;
};

const waitForValidPopover = async (signal: AbortSignal): Promise<HTMLElement | null> => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the signal parameter set when it is not used

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

return new Promise((resolve) => {
const observer = new MutationObserver((mutations) => {
try {
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') &&
target.querySelector('.popper-profile-card__footer')
) {
observer.disconnect();
resolve(target);
return;
}
}
}
} catch (error) {
observer.disconnect();
}
});

const initialCheck = document.querySelector('.popper-profile-card:not(.hidden)');
if (initialCheck?.querySelector('.popper-profile-card__footer')) {
resolve(initialCheck as HTMLElement);
return;
}
observer.observe(document.body, {
attributeFilter: ['class'],
subtree: true,
childList: false,
});
});
};

const processElement = async (element: Element) => {
const developername = element.getAttribute('data-username');
if (!developername) return;

let abortController = new AbortController();

element.addEventListener('mouseover', async () => {
abortController.abort();
abortController = new AbortController();
const signal = abortController.signal;

const popover = (await Promise.race([
waitForValidPopover(signal),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 1500)),
])) as HTMLElement;

const cardUsername = popover.querySelector('.username')?.textContent?.replace('@', '');
if (cardUsername !== developername) return;
const existing = popover.querySelector(`[data-username="${developername}"]`);
if (existing) return;

const openrank = await getDeveloperLatestOpenrank(developername);
if (!openrank) {
return;
}
const existingOpenRank = popover.querySelector('.hypercrx-openrank-info');
if (existingOpenRank) return;

const footer = popover.querySelector('.popper-profile-card__content') as HTMLElement;
if (footer) {
const openrankContainer = document.createElement('div');
openrankContainer.dataset.username = developername;
if (footer && footer.parentNode) {
footer.appendChild(openrankContainer);
}
createRoot(openrankContainer).render(<View {...{ developerName: developername, openrank }} />);
}
});
};

const init = async (): Promise<void> => {
platform = getPlatform();
if (isInitialized) return;
isInitialized = true;
const hovercardSelector = 'a.js-popover-card[data-username]';

const processExisting = () => {
document.querySelectorAll(hovercardSelector).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,5 +24,6 @@ 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/fast-pr';
Loading