diff --git a/src/pages/ContentScripts/features/contributor_button/DataNotFound.tsx b/src/pages/ContentScripts/features/contributor_button/DataNotFound.tsx
new file mode 100644
index 00000000..9ee69d34
--- /dev/null
+++ b/src/pages/ContentScripts/features/contributor_button/DataNotFound.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+const DataNotFound = () => {
+ return (
+
+
Data Not Found
+
+
Possible reasons are:
+
+ - This repository is too new, so its data has not been generated
+ - This repository is not active enough, so its data are not generated
+
+
+
+ );
+};
+
+export default DataNotFound;
diff --git a/src/pages/ContentScripts/features/contributor_button/index.tsx b/src/pages/ContentScripts/features/contributor_button/index.tsx
new file mode 100644
index 00000000..f23725ab
--- /dev/null
+++ b/src/pages/ContentScripts/features/contributor_button/index.tsx
@@ -0,0 +1,77 @@
+/**
+ * This module is responsible for enhancing the contributor list on the GitHub repository page.
+ * It uses React to render the contributor list and fetches additional network data through the API.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { render } from 'react-dom';
+import $ from 'jquery';
+import View from './view';
+import { getRepoName } from '../../../../helpers/get-repo-info';
+import { getDeveloperNetwork, getRepoNetwork } from '../../../../api/repo';
+import features from '../../../../feature-manager';
+import * as pageDetect from 'github-url-detection';
+import elementReady from 'element-ready';
+
+// Global variables are used to store the repository name and network data for sharing between different functions.
+// Define global variables to store the repository name and network data.
+let repoName: string;
+let developerNetworks: any;
+let repoNetworks: any;
+let target: any;
+
+// Get the feature ID of the current module for feature management.
+const featureId = features.getFeatureID(import.meta.url);
+
+/**
+ * Asynchronously fetch network data of the repository developer and the repository.
+ */
+const getData = async () => {
+ developerNetworks = await getDeveloperNetwork(repoName);
+ repoNetworks = await getRepoNetwork(repoName);
+};
+
+/**
+ * Replace the contributor list with a React component.
+ * @param target The target element to be replaced.
+ */
+const renderTo = (target: HTMLElement) => {
+ const originalHTML = target.innerHTML;
+
+ render(
+
+
+ ,
+ document.querySelector('.list-style-none.d-flex.flex-wrap.mb-n2') as HTMLElement
+ );
+};
+
+/**
+ * Initialize the feature, including fetching the repository name and data, and replacing the contributor list.
+ */
+const init = async (): Promise => {
+ repoName = getRepoName();
+ const targetElement = document.querySelector('.list-style-none.d-flex.flex-wrap.mb-n2') as HTMLElement;
+ await getData();
+ renderTo(targetElement);
+};
+
+/**
+ * Restore the feature when the page is refreshed or navigated, reload the data and render the list.
+ */
+const restore = async () => {
+ if (repoName !== getRepoName()) {
+ repoName = getRepoName();
+ await getData();
+ }
+ $('div.ReactModalPortal').remove();
+ renderTo(target);
+};
+
+// Add the feature to the feature manager, configure the initialization and restore functions.
+features.add(featureId, {
+ // asLongAs: [pageDetect.isUserProfile],
+ awaitDomReady: false,
+ init,
+ restore,
+});
diff --git a/src/pages/ContentScripts/features/contributor_button/view.tsx b/src/pages/ContentScripts/features/contributor_button/view.tsx
new file mode 100644
index 00000000..451f5673
--- /dev/null
+++ b/src/pages/ContentScripts/features/contributor_button/view.tsx
@@ -0,0 +1,95 @@
+import React, { useState, useEffect } from 'react';
+import ReactModal from 'react-modal';
+import Graph from '../../../../components/Graph';
+import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage';
+import { useTranslation } from 'react-i18next';
+import '../../../../helpers/i18n';
+
+// Define the time period for the developer and the repository
+const DEVELOPER_PERIOD = 90;
+const REPO_PERIOD = 90;
+
+// Define the Props interface, including the developer network and the target HTML
+interface Props {
+ developerNetwork: any;
+ target: any;
+}
+
+const borderStyle = {
+ 'margin-top': '0',
+};
+
+// Define the chart style
+const graphStyle = {
+ width: '296px',
+ height: '400px',
+};
+
+const targetStyle = {
+ width: '296px',
+ height: '80px',
+ display: 'flex',
+ 'justify-content': 'flex-start',
+ 'align-items': 'flex-start',
+ 'align-content': 'flex-start',
+ 'flex-wrap': 'wrap',
+};
+
+const buttonStyle = {
+ margin: '-5px 0px 10px 0px',
+ padding: '6px 14px',
+ 'font-size': '14px',
+ 'font-family': 'inherit',
+ 'font-weight': '500',
+ 'box-shadow':
+ 'var(--button-default-shadow-resting, var(--color-btn-shadow, 0 1px 0 rgba(27, 31, 36, 0.04))), var(--button-default-shadow-inset, var(--color-btn-inset-shadow, inset 0 1px 0 rgba(255, 255, 255, 0.25)))',
+ 'border-radius': '6px',
+ 'border-width': '1px',
+ 'border-style': 'solid',
+ 'border-image': 'initial',
+ 'border-color':
+ 'var(--button-default-borderColor-rest, var(--button-default-borderColor-rest, var(--color-btn-border, rgba(27, 31, 36, 0.15))))',
+ 'text-decoration': 'none',
+};
+
+// Define the View component
+const View = ({ developerNetwork, target }: Props): JSX.Element => {
+ // Define state variables, including options, whether to show the chart, and whether to show the repository network
+ const [options, setOptions] = useState(defaults);
+ const [showGraph, setShowGraph] = useState(true);
+ const [showRepoNetwork, setShowRepoNetwork] = useState(false);
+
+ // Use the translation function
+ const { t, i18n } = useTranslation();
+
+ // Use the useEffect hook to handle side effects, including fetching options and changing the language
+ useEffect(() => {
+ (async function () {
+ setOptions(await optionsStorage.getAll());
+ i18n.changeLanguage(options.locale);
+ })();
+ }, [options.locale]);
+
+ // Return JSX elements, including a button and a conditionally rendered chart or target HTML
+ return (
+
+
+ {showGraph ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+// Export the View component
+export default View;
diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts
index bab274a3..c2e3cbb8 100644
--- a/src/pages/ContentScripts/index.ts
+++ b/src/pages/ContentScripts/index.ts
@@ -1,5 +1,5 @@
import './index.scss';
-
+import './features/contributor_button';
import './features/repo-activity-openrank-trends';
import './features/developer-activity-openrank-trends';
import './features/repo-header-labels';