├── .nvmrc ├── .gitattributes ├── NOTICE ├── .prettierignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── other_issues.yml │ ├── feature_request.yml │ └── bug_report.yml ├── release.yml ├── workflows │ ├── update-information.yml │ ├── publish_configs.yml │ ├── deploy.yml │ ├── build.yml │ └── release.yml ├── pull_request_template.md └── hypertrons.json ├── assets ├── en │ ├── ex.png │ ├── mo.png │ ├── op.png │ ├── set.png │ └── installation-edge-1.png ├── zh-CN │ ├── token-1.png │ ├── token-2.png │ ├── token-3.png │ └── installation-edge-1.png └── keep-service-worker-devtools-open.jpeg ├── src ├── assets │ └── img │ │ ├── main.png │ │ ├── osGraphLogo.png │ │ ├── openDiggerLogo.png │ │ ├── rocketDarkLogo.png │ │ └── rocketLightLogo.png ├── helpers │ ├── sleep.ts │ ├── is-gitee.ts │ ├── is-github.ts │ ├── is-perceptor.ts │ ├── exists.ts │ ├── wait-for.ts │ ├── get-platform.ts │ ├── request.ts │ ├── is-null.ts │ ├── github-token.ts │ ├── linear-map.ts │ ├── is-restoration-visit.ts │ ├── formatter.ts │ ├── should-feature-run.ts │ ├── judge-interval.ts │ ├── i18n.ts │ ├── get-github-theme.ts │ ├── get-gitee-repo-info.ts │ ├── get-gitee-developer-info.ts │ ├── get-github-developer-info.ts │ ├── gitee-token.ts │ ├── get-github-repo-info.ts │ └── generate-data-by-month.ts ├── pages │ ├── Background │ │ └── index.ts │ ├── Popup │ │ ├── index.html │ │ ├── index.jsx │ │ └── Popup.tsx │ ├── Options │ │ ├── index.html │ │ ├── index.jsx │ │ ├── index.css │ │ └── components │ │ │ └── GitHubToken.tsx │ ├── Sandbox │ │ ├── index.html │ │ └── index.jsx │ └── ContentScripts │ │ ├── features │ │ ├── perceptor-layout │ │ │ ├── view.tsx │ │ │ ├── gitee-index.tsx │ │ │ └── index.tsx │ │ ├── fast-pr │ │ │ ├── baseContent.ts │ │ │ ├── handleMessage.tsx │ │ │ ├── giteeUrl.ts │ │ │ └── index.tsx │ │ ├── developer-networks │ │ │ ├── react-modal.scss │ │ │ └── index.tsx │ │ ├── repo-networks │ │ │ ├── DataNotFound.tsx │ │ │ └── index.tsx │ │ ├── developer-hovercard-info │ │ │ ├── gitee-view.tsx │ │ │ └── view.tsx │ │ ├── repo-activity-racing-bar │ │ │ ├── SpeedController.tsx │ │ │ ├── useLoadedAvatars.ts │ │ │ ├── PlayerButton.tsx │ │ │ ├── gitee-index.tsx │ │ │ ├── index.tsx │ │ │ ├── AvatarColorStore.ts │ │ │ └── view.tsx │ │ ├── oss-gpt │ │ │ ├── service.ts │ │ │ └── index.tsx │ │ ├── repo-sidebar-labels │ │ │ ├── OpenDiggerLabel.tsx │ │ │ ├── gitee-OpenDiggerLabel.tsx │ │ │ ├── gitee-index.tsx │ │ │ └── index.tsx │ │ ├── repo-star-tooltip │ │ │ ├── gitee-index.tsx │ │ │ ├── view.tsx │ │ │ └── index.tsx │ │ ├── repo-fork-tooltip │ │ │ ├── index.tsx │ │ │ ├── gitee-index.tsx │ │ │ ├── view.tsx │ │ │ └── ForkChart.tsx │ │ ├── repo-header-labels │ │ │ ├── activityView.tsx │ │ │ ├── openrankView.tsx │ │ │ ├── participantView.tsx │ │ │ ├── index.tsx │ │ │ ├── ContributorChart.tsx │ │ │ ├── ActivityChart.tsx │ │ │ └── OpenRankChart.tsx │ │ ├── repo-issue-tooltip │ │ │ ├── index.tsx │ │ │ ├── gitee-index.tsx │ │ │ └── view.tsx │ │ ├── developer-activity-openrank-trends │ │ │ ├── index.tsx │ │ │ ├── gitee-index.tsx │ │ │ └── view.tsx │ │ ├── repo-pr-tooltip │ │ │ ├── index.tsx │ │ │ └── gitee-index.tsx │ │ ├── repo-activity-openrank-trends │ │ │ ├── gitee-index.tsx │ │ │ ├── index.tsx │ │ │ └── view.tsx │ │ └── perceptor-tab │ │ │ ├── gitee-index.tsx │ │ │ └── icon-svg-path.ts │ │ ├── index.ts │ │ └── components │ │ ├── NativePopover.tsx │ │ └── GiteeNativePopover.tsx ├── components │ ├── TooltipTrigger │ │ ├── icon-svg-path.ts │ │ └── index.tsx │ ├── OSGraph.tsx │ └── Graph.tsx ├── global.d.ts ├── constant.ts ├── api │ ├── giteeApi.ts │ ├── githubApi.ts │ ├── developer.ts │ ├── common.ts │ └── repo.ts ├── options-storage.ts └── manifest.json ├── .husky └── pre-commit ├── .prettierrc ├── utils ├── env.js ├── features-loader.cjs ├── autoReloadClients │ ├── contentScriptClient.js │ └── backgroundClient.js ├── build.js └── crx-webpack-plugin │ └── index.js ├── publish ├── notification.json └── update_information.json ├── .babelrc ├── scripts ├── pre-commit.cjs ├── utils.cjs ├── test_configs.js ├── deploy.js └── bump-version.cjs ├── .gitignore ├── tsconfig.json ├── CODE_OF_CONDUCT.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2023, X-lab 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /assets/en/ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/en/ex.png -------------------------------------------------------------------------------- /assets/en/mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/en/mo.png -------------------------------------------------------------------------------- /assets/en/op.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/en/op.png -------------------------------------------------------------------------------- /assets/en/set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/en/set.png -------------------------------------------------------------------------------- /src/assets/img/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/src/assets/img/main.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn run pretty-quick:check -------------------------------------------------------------------------------- /assets/zh-CN/token-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/zh-CN/token-1.png -------------------------------------------------------------------------------- /assets/zh-CN/token-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/zh-CN/token-2.png -------------------------------------------------------------------------------- /assets/zh-CN/token-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/zh-CN/token-3.png -------------------------------------------------------------------------------- /src/assets/img/osGraphLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/src/assets/img/osGraphLogo.png -------------------------------------------------------------------------------- /assets/en/installation-edge-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/en/installation-edge-1.png -------------------------------------------------------------------------------- /src/assets/img/openDiggerLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/src/assets/img/openDiggerLogo.png -------------------------------------------------------------------------------- /src/assets/img/rocketDarkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/src/assets/img/rocketDarkLogo.png -------------------------------------------------------------------------------- /src/helpers/sleep.ts: -------------------------------------------------------------------------------- 1 | export default function sleep(m: number) { 2 | return new Promise((r) => setTimeout(r, m)); 3 | } 4 | -------------------------------------------------------------------------------- /assets/zh-CN/installation-edge-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/zh-CN/installation-edge-1.png -------------------------------------------------------------------------------- /src/assets/img/rocketLightLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/src/assets/img/rocketLightLogo.png -------------------------------------------------------------------------------- /assets/keep-service-worker-devtools-open.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertrons/hypertrons-crx/HEAD/assets/keep-service-worker-devtools-open.jpeg -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "requirePragma": false, 6 | "arrowParens": "always" 7 | } -------------------------------------------------------------------------------- /src/helpers/is-gitee.ts: -------------------------------------------------------------------------------- 1 | const isGitee = (): boolean => { 2 | return window.location.href.startsWith('https://gitee.com/'); 3 | }; 4 | 5 | export default isGitee; 6 | -------------------------------------------------------------------------------- /src/helpers/is-github.ts: -------------------------------------------------------------------------------- 1 | const isGithub = (): boolean => { 2 | return window.location.href.startsWith('https://github.com/'); 3 | }; 4 | 5 | export default isGithub; 6 | -------------------------------------------------------------------------------- /utils/env.js: -------------------------------------------------------------------------------- 1 | // tiny wrapper with default env vars 2 | export default { 3 | NODE_ENV: process.env.NODE_ENV || 'development', 4 | PORT: process.env.PORT || 3000, 5 | }; 6 | -------------------------------------------------------------------------------- /src/helpers/is-perceptor.ts: -------------------------------------------------------------------------------- 1 | const isPerceptor = (): boolean => { 2 | return window.location.search.includes('?redirect=perceptor'); 3 | }; 4 | 5 | export default isPerceptor; 6 | -------------------------------------------------------------------------------- /publish/notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 2021052301, 3 | "content": { 4 | "zh": "欢迎使用 Hypertrons-crx!", 5 | "en": "Welcome to Hypertrons-crx!" 6 | }, 7 | "is_published": true 8 | } 9 | -------------------------------------------------------------------------------- /src/helpers/exists.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | // a small wrapper for checking if a DOM element exists 4 | export default function exists(selector: string) { 5 | return $(selector).length > 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/Background/index.ts: -------------------------------------------------------------------------------- 1 | chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { 2 | if (changeInfo.url) { 3 | chrome.tabs.sendMessage(tabId, { type: 'urlChanged', url: changeInfo.url }); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /src/pages/Popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | // "@babel/preset-env" 4 | "@babel/preset-react" 5 | // "react-app" 6 | ], 7 | "plugins": [ 8 | // "@babel/plugin-proposal-class-properties", 9 | "react-hot-loader/babel" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/Options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Settings 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/helpers/wait-for.ts: -------------------------------------------------------------------------------- 1 | import delay from 'delay'; 2 | 3 | export default async function waitFor(condition: () => any): Promise { 4 | while (!condition()) { 5 | // eslint-disable-next-line no-await-in-loop 6 | await delay(10); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/TooltipTrigger/icon-svg-path.ts: -------------------------------------------------------------------------------- 1 | export const iconTooltipTrigger = 2 | 'M22 34h4V22h-4v12zm2-30C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16zm-2-22h4v-4h-4v4z'; 3 | -------------------------------------------------------------------------------- /src/pages/Popup/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import Popup from './Popup'; 4 | createRoot(window.document.querySelector('#app-container')).render(); 5 | 6 | if (module.hot) module.hot.accept(); 7 | -------------------------------------------------------------------------------- /src/helpers/get-platform.ts: -------------------------------------------------------------------------------- 1 | import isGitee from './is-gitee'; 2 | import isGithub from './is-github'; 3 | 4 | export const getPlatform = (): 'github' | 'gitee' | 'unknown' => { 5 | if (isGithub()) return 'github'; 6 | if (isGitee()) return 'gitee'; 7 | return 'unknown'; 8 | }; 9 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | type ThemeType = 'light' | 'dark'; 2 | 3 | type FeatureName = string; 4 | type FeatureId = `hypercrx-${FeatureName}`; 5 | 6 | // It should be just for README.md, but 🤷‍♂️ 7 | declare module '*.md' { 8 | export const importedFeatures: FeatureName[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/Sandbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sandbox 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /src/pages/Options/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Options from './Options'; 5 | import './index.css'; 6 | createRoot(window.document.querySelector('#app-container')).render(); 7 | 8 | if (module.hot) module.hot.accept(); 9 | -------------------------------------------------------------------------------- /scripts/pre-commit.cjs: -------------------------------------------------------------------------------- 1 | // according to https://github.com/TriPSs/conventional-changelog-action#pre-commit-hook 2 | // this script should be a CommonJS module 3 | 4 | const { bump } = require('./bump-version.cjs'); 5 | 6 | exports.preCommit = ({ version }) => { 7 | bump({ version: version, deploy: true }); 8 | }; 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other_issues.yml: -------------------------------------------------------------------------------- 1 | name: Other Issues 2 | description: For questions, suggestions, improvements and others. 3 | title: "[xxx] " 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Description 8 | description: | 9 | Please describe the issue. 10 | 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /src/helpers/request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @zh-CN 处理网络请求 3 | * @en-US network request 4 | */ 5 | const request = async (url: string) => { 6 | const response = await fetch(url); 7 | if (!response.ok) { 8 | throw response.status; 9 | } else { 10 | return await response.json(); 11 | } 12 | }; 13 | 14 | export default request; 15 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/perceptor-layout/view.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const View = (): JSX.Element => { 4 | return ( 5 | <> 6 |
7 |
8 | 9 | ); 10 | }; 11 | 12 | export default View; 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request a new feature from Hypercrx 3 | title: "[Feature] " 4 | labels: [kind/feature] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Description 9 | description: | 10 | Please describe what this feature does. 11 | 12 | validations: 13 | required: true 14 | -------------------------------------------------------------------------------- /src/pages/Popup/Popup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | 4 | export default function Popup() { 5 | return ( 6 |
7 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | changelog: 3 | exclude: 4 | labels: 5 | - ignore for release 6 | categories: 7 | - title: New Features 8 | labels: 9 | - kind/feature 10 | - title: Refactor 11 | labels: 12 | - kind/enhancement 13 | - title: Bug Fixes 14 | labels: 15 | - kind/bug 16 | - title: Other Changes 17 | labels: 18 | - '*' 19 | -------------------------------------------------------------------------------- /src/helpers/is-null.ts: -------------------------------------------------------------------------------- 1 | export function isNull(object: any) { 2 | if ( 3 | object === null || 4 | typeof object === 'undefined' || 5 | object === '' || 6 | JSON.stringify(object) === '[]' || 7 | JSON.stringify(object) === '{}' 8 | ) { 9 | return true; 10 | } 11 | return false; 12 | } 13 | 14 | export function isAllNull(obj: Object) { 15 | return Object.values(obj).every((value) => value === null); 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /release 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | .history 20 | .vscode 21 | .idea 22 | package-lock.json 23 | 24 | # secrets 25 | secrets.*.js 26 | 27 | # yarn 28 | yarn-error.log 29 | -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorCode { 2 | OK = 200, 3 | NOT_FOUND = 404, 4 | UNKNOWN = 500, 5 | } 6 | 7 | export const OSS_XLAB_ENDPOINT = 'https://oss.open-digger.cn'; 8 | 9 | export const HYPERTRONS_CRX_NEW_ISSUE = 'https://github.com/hypertrons/hypertrons-crx/issues/new/choose'; 10 | 11 | export const HYPERCRX_GITHUB = 'https://github.com/hypertrons/hypertrons-crx'; 12 | 13 | export const FAST_PR_CONFIG_URL = 'https://hypercrx.cn/configs/fast-pr-url-rules.cjs'; 14 | -------------------------------------------------------------------------------- /publish/update_information.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": { 3 | "latest_version": "1.9.22", 4 | "url": "https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc" 5 | }, 6 | "edge": { 7 | "latest_version": "1.9.22", 8 | "url": "https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome" 9 | }, 10 | "develop": { 11 | "latest_version": "1.9.22", 12 | "url": "https://github.com/hypertrons/hypertrons-crx/releases" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/github-token.ts: -------------------------------------------------------------------------------- 1 | const GITHUB_TOKEN_KEY = 'github_token'; 2 | 3 | export const saveGithubToken = (token: string) => { 4 | return chrome.storage.sync.set({ [GITHUB_TOKEN_KEY]: token }); 5 | }; 6 | 7 | export const getGithubToken = (): Promise => { 8 | return new Promise((resolve) => { 9 | chrome.storage.sync.get(GITHUB_TOKEN_KEY, (result) => { 10 | resolve(result[GITHUB_TOKEN_KEY] || null); 11 | }); 12 | }); 13 | }; 14 | 15 | export const removeGithubToken = () => { 16 | return chrome.storage.sync.remove(GITHUB_TOKEN_KEY); 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/fast-pr/baseContent.ts: -------------------------------------------------------------------------------- 1 | export const PR_TITLE = (file: string) => `docs: Update ${file}`; 2 | export const PR_CONTENT = (file: string) => `Update ${file} by [FastPR](https://github.com/hypertrons/hypertrons-crx).`; 3 | export const generateBranchName = () => `fastPR-${new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '')}`; 4 | export const COMMIT_MESSAGE_DOC = (branch: string, userName: string, userEmail: string) => 5 | `docs: ${branch}\n\nSigned-off-by: ${userName.trim()} <${userEmail.trim()}>`.trim(); 6 | export const COMMIT_MESSAGE = (branch: string) => `docs: ${branch}`; 7 | -------------------------------------------------------------------------------- /utils/features-loader.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | function getImportedFeatures() { 4 | const contents = fs.readFileSync('src/pages/ContentScripts/index.ts', 'utf8'); 5 | const importedFeatures = [ 6 | ...contents.matchAll(/^import '\.\/features\/([^.]+)';/gm), 7 | ] 8 | .map((match) => match[1]) 9 | .sort(); 10 | return importedFeatures; 11 | } 12 | 13 | // a webpack loader 14 | function FeaturesLoader(content, map, meta) { 15 | return ` 16 | export const importedFeatures = ${JSON.stringify(getImportedFeatures())} 17 | `; 18 | } 19 | 20 | module.exports = FeaturesLoader; 21 | -------------------------------------------------------------------------------- /utils/autoReloadClients/contentScriptClient.js: -------------------------------------------------------------------------------- 1 | const logger = (msg) => { 2 | console.log(`[CSC] ${msg}`); 3 | }; 4 | 5 | logger('content script client up.'); 6 | 7 | chrome.runtime.onMessage.addListener((request, _sender, sendResp) => { 8 | const shouldReload = request.from === 'backgroundClient' && request.action === 'reload-yourself'; 9 | if (shouldReload) { 10 | sendResp({ from: 'contentScriptClient', action: 'yes-sir' }); 11 | // wait 100ms for extension reload. 12 | logger('page will reload to reload content script...'); 13 | setTimeout(() => window.location.reload(), 100); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/api/giteeApi.ts: -------------------------------------------------------------------------------- 1 | import { getGiteeToken, saveGiteeToken } from '../helpers/gitee-token'; 2 | 3 | export const giteeRequest = async (endpoint: string, options: RequestInit = {}): Promise => { 4 | const token = await getGiteeToken(); 5 | if (!token) { 6 | return null; 7 | } 8 | 9 | const url = `https://gitee.com/api/v5/${endpoint}?access_token=${token}`; 10 | 11 | try { 12 | const response = await fetch(url, { 13 | ...options, 14 | }); 15 | return response.json(); 16 | } catch (error) { 17 | return null; 18 | } 19 | }; 20 | 21 | export { saveGiteeToken, getGiteeToken }; 22 | -------------------------------------------------------------------------------- /scripts/utils.cjs: -------------------------------------------------------------------------------- 1 | // according to https://github.com/TriPSs/conventional-changelog-action#pre-commit-hook 2 | // this script should be a CommonJS module 3 | 4 | const fs = require('fs'); 5 | 6 | function readJson(filename) { 7 | return JSON.parse(fs.readFileSync(filename)); 8 | } 9 | 10 | function writeJson(filename, content) { 11 | fs.writeFileSync(filename, JSON.stringify(content, null, 2) + '\n'); 12 | } 13 | 14 | function processFile(filename, fn) { 15 | const content = fs.readFileSync(filename, 'utf8'); 16 | fs.writeFileSync(filename, fn(content)); 17 | } 18 | module.exports = { readJson, writeJson, processFile }; 19 | -------------------------------------------------------------------------------- /src/api/githubApi.ts: -------------------------------------------------------------------------------- 1 | import { getGithubToken, saveGithubToken } from '../helpers/github-token'; 2 | 3 | export const githubRequest = async (endpoint: string, options: RequestInit = {}): Promise => { 4 | const token = await getGithubToken(); 5 | if (!token) { 6 | return null; 7 | } 8 | 9 | try { 10 | const response = await fetch(`https://api.github.com${endpoint}`, { 11 | headers: { 12 | Authorization: `Bearer ${token}`, 13 | }, 14 | ...options, 15 | }); 16 | return response.json(); 17 | } catch (error) { 18 | return null; 19 | } 20 | }; 21 | 22 | export { saveGithubToken, getGithubToken }; 23 | -------------------------------------------------------------------------------- /scripts/test_configs.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const { urlRules } = await import('../public_configs/fast-pr-url-rules.cjs'); 3 | let hasError = false; 4 | for (const rule of urlRules) { 5 | const tests = rule.tests; 6 | if (!tests) continue; 7 | for (const test of tests) { 8 | const url = test[0]; 9 | const expected = test[1]; 10 | const result = rule.ruleFunction(url); 11 | if (result?.filePath !== expected) { 12 | console.error(`Test failed for ${url}. Expected ${expected}, got ${result?.filePath}`); 13 | hasError = true; 14 | } 15 | } 16 | } 17 | if (hasError) { 18 | process.exit(-1); 19 | } 20 | })(); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": false, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "noEmit": false, 16 | "jsx": "react", 17 | "experimentalDecorators": true, 18 | "downlevelIteration": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["build", "node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /src/helpers/linear-map.ts: -------------------------------------------------------------------------------- 1 | export default function linearMap(val: number, domain: number[], range: number[]): number { 2 | const d0 = domain[0]; 3 | const d1 = domain[1]; 4 | const r0 = range[0]; 5 | const r1 = range[1]; 6 | 7 | const subDomain = d1 - d0; 8 | const subRange = r1 - r0; 9 | 10 | if (subDomain === 0) { 11 | return subRange === 0 ? r0 : (r0 + r1) / 2; 12 | } 13 | if (subDomain > 0) { 14 | if (val <= d0) { 15 | return r0; 16 | } else if (val >= d1) { 17 | return r1; 18 | } 19 | } else { 20 | if (val >= d0) { 21 | return r0; 22 | } else if (val <= d1) { 23 | return r1; 24 | } 25 | } 26 | 27 | return ((val - d0) / subDomain) * subRange + r0; 28 | } 29 | -------------------------------------------------------------------------------- /src/helpers/is-restoration-visit.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * There are two types of `turbo:visit`: "Restoration Visits" and "Application Visits", 3 | * and we can get the visit type by reading the `detail` property of `turbo:visit` event. 4 | * For more info, see https://turbo.hotwired.dev/handbook/building#understanding-caching 5 | */ 6 | let visitType: 'advance' | 'restore'; 7 | 8 | // the variable will be modified after each turbo:visit 9 | document.addEventListener('turbo:visit', ((event: CustomEvent) => { 10 | visitType = event.detail.action; 11 | }) as EventListener); 12 | 13 | // so this function can return the right visitType whenever called 14 | export default function isRestorationVisit() { 15 | return visitType === 'restore' ? true : false; 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/update-information.yml: -------------------------------------------------------------------------------- 1 | # update-information.yml 2 | # upload `publish/update_information.json` to oss 3 | name: update-information 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-information: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out repository code 13 | uses: actions/checkout@v2 14 | 15 | - name: Upload update_information.json to oss 16 | uses: tvrcgo/upload-to-oss@v0.1.1 17 | with: 18 | key-id: ${{ secrets.ACCESSKEYID }} 19 | key-secret: ${{ secrets.ACCESSKEYSECRET }} 20 | region: ${{ secrets.REGION }} 21 | bucket: ${{ secrets.BUCKET }} 22 | asset-path: ./publish 23 | target-path: ${{ secrets.DEPLOYPATH }} 24 | -------------------------------------------------------------------------------- /src/helpers/formatter.ts: -------------------------------------------------------------------------------- 1 | export const formatNum = (num: number, index: number) => { 2 | let isNegative = false; 3 | if (num < 0) { 4 | isNegative = true; 5 | num = -num; 6 | } 7 | let si = [ 8 | { value: 1, symbol: '' }, 9 | { value: 1e3, symbol: 'k' }, 10 | ]; 11 | let rx = /\.0+$|(\.[0-9]*[1-9])0+$/; 12 | let i; 13 | for (i = si.length - 1; i > 0; i--) { 14 | if (num >= si[i].value) { 15 | break; 16 | } 17 | } 18 | let result = (num / si[i].value).toFixed(2).replace(rx, '$1') + si[i].symbol; 19 | if (isNegative) { 20 | result = '-' + result; 21 | } 22 | return result; 23 | }; 24 | 25 | export const numberWithCommas = (x: number) => { 26 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); 27 | }; 28 | -------------------------------------------------------------------------------- /utils/build.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url'; 2 | import path, { dirname } from 'path'; 3 | import CrxWebpackPlugin from './crx-webpack-plugin/index.js'; 4 | import webpack from 'webpack'; 5 | import config from '../webpack.config.js'; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = dirname(__filename); 9 | 10 | delete config.custom; 11 | 12 | config.plugins.push( 13 | new CrxWebpackPlugin({ 14 | keyFile: path.resolve(__dirname, '../build.pem'), 15 | contentPath: path.resolve(__dirname, '../build'), 16 | outputPath: path.resolve(__dirname, '../release'), 17 | name: 'hypercrx', 18 | }) 19 | ); 20 | 21 | config.mode = 'production'; 22 | 23 | webpack(config, function (err) { 24 | if (err) throw err; 25 | }); 26 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/developer-networks/react-modal.scss: -------------------------------------------------------------------------------- 1 | .ReactModal__Overlay_Custom { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | background-color: rgba(0, 0, 0, 0.4); 8 | z-index: 1000; // to fix visible background issue 9 | } 10 | 11 | .ReactModal__Content_Custom { 12 | position: absolute; 13 | width: 60vw; 14 | left: 50%; 15 | top: 50%; 16 | transform: translate(-50%, -50%); 17 | padding: 24px; 18 | box-sizing: border-box; 19 | box-shadow: 20 | rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, 21 | rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px; 22 | outline: transparent solid 3px; 23 | border-radius: 2px; 24 | background: var(--bgColor-default, var(--color-canvas-default)); 25 | color: var(--fgColor-default, var(--color-fg-default)); 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/repo-networks/DataNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DataNotFound = () => { 4 | return ( 5 |
14 |

Data Not Found

15 |
16 |

Possible reasons are:

17 |
    18 |
  • This repository is too new, so its data has not been generated
  • 19 |
  • This repository is not active enough, so its data are not generated
  • 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default DataNotFound; 27 | -------------------------------------------------------------------------------- /src/components/TooltipTrigger/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'antd'; 3 | import { iconTooltipTrigger } from './icon-svg-path'; 4 | 5 | interface ITooltipTriggerProps { 6 | size?: number; 7 | iconColor?: string; 8 | content?: string; 9 | overlayClassName?: string; 10 | } 11 | 12 | const TooltipTrigger: React.FC = ({ 13 | size = 20, 14 | iconColor = '#FFFFFF', 15 | content, 16 | overlayClassName = 'custom-tooltip', 17 | }) => ( 18 | {content}} overlayClassName={overlayClassName}> 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | export default TooltipTrigger; 26 | -------------------------------------------------------------------------------- /src/options-storage.ts: -------------------------------------------------------------------------------- 1 | import { importedFeatures } from '../README.md'; 2 | 3 | export type HypercrxOptions = typeof defaults; 4 | 5 | export const defaults = Object.assign( 6 | { 7 | locale: 'en', 8 | }, 9 | Object.fromEntries( 10 | importedFeatures.map((name) => [ 11 | `hypercrx-${name}` as FeatureId, 12 | name === 'oss-gpt' ? false : true, // Set oss gpt to not be enabled by default 13 | ]) 14 | ) 15 | ); 16 | 17 | class OptionsStorage { 18 | public async getAll(): Promise { 19 | return (await chrome.storage.sync.get(defaults)) as HypercrxOptions; 20 | } 21 | 22 | public async set(options: Partial): Promise { 23 | await chrome.storage.sync.set(options); 24 | } 25 | } 26 | 27 | const optionsStorage = new OptionsStorage(); 28 | 29 | export default optionsStorage; 30 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/developer-hovercard-info/gitee-view.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../../../helpers/i18n'; 3 | 4 | interface OpenRankProps { 5 | developerName: string; 6 | openrank: string; 7 | } 8 | 9 | const View: React.FC = ({ developerName, openrank }) => { 10 | const textColor = '#636c76'; 11 | const fontSize = '12px'; 12 | 13 | return ( 14 |
15 | 24 | OpenRank {openrank} 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default View; 31 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/repo-activity-racing-bar/SpeedController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Segmented, SegmentedProps } from 'antd'; 3 | 4 | interface SpeedControllerProps { 5 | speed: number; 6 | onSpeedChange: (speed: number) => void; 7 | } 8 | 9 | const options: SegmentedProps['options'] = [ 10 | { 11 | label: '0.5x', 12 | value: 0.5, 13 | }, 14 | { 15 | label: '1x', 16 | value: 1, 17 | }, 18 | { 19 | label: '2x', 20 | value: 2, 21 | }, 22 | ]; 23 | 24 | export const SpeedController = ({ speed, onSpeedChange }: SpeedControllerProps): JSX.Element => { 25 | return ( 26 | onSpeedChange(value as number)} 34 | /> 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/repo-activity-racing-bar/useLoadedAvatars.ts: -------------------------------------------------------------------------------- 1 | import { avatarColorStore } from './AvatarColorStore'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | 5 | export const useLoadedAvatars = (contributors: string[], month: string): [number, number, () => void] => { 6 | const [loadedCount, setLoadedCount] = useState(0); 7 | const totalAvatarCount = contributors.length; 8 | 9 | const load = async () => { 10 | setLoadedCount(0); 11 | 12 | await Promise.all( 13 | contributors.map(async (contributor) => { 14 | try { 15 | await avatarColorStore.getColors(month, contributor); 16 | setLoadedCount((prev) => prev + 1); 17 | } catch (error) { 18 | setLoadedCount((prev) => prev + 1); 19 | } 20 | }) 21 | ); 22 | }; 23 | 24 | useEffect(() => { 25 | load(); 26 | }, [month]); 27 | 28 | return [loadedCount, totalAvatarCount, load]; 29 | }; 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Brief Information 2 | 3 | This pull request is in the type of ([more info](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#type) about types): 4 | 5 | - [ ] build 6 | - [ ] ci 7 | - [ ] docs 8 | - [ ] feat 9 | - [ ] fix 10 | - [ ] perf 11 | - [ ] refactor 12 | - [ ] test 13 | 14 | Related issues ([all available keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)): 15 | 16 | - keyword #xxx 17 | 18 | ## Details 19 | 20 | 21 | ## Checklist 22 | 23 | - [ ] I have read the [CONTRIBUTING](https://github.com/hypertrons/hypertrons-crx/blob/master/CONTRIBUTING.md) doc 24 | - [ ] I have signed the [CLA](https://cla-assistant.io/hypertrons/hypertrons-crx) 25 | 26 | ## Others 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/api/developer.ts: -------------------------------------------------------------------------------- 1 | import { getMetricByName } from './common'; 2 | 3 | // metric names and their implementation names in OpenDigger 4 | const metricNameMap = new Map([ 5 | ['activity', 'activity'], 6 | ['openrank', 'openrank'], 7 | ['developer_network', 'developer_network'], 8 | ['repo_network', 'repo_network'], 9 | ]); 10 | 11 | export const getActivity = async (platform: string, user: string) => { 12 | return getMetricByName(platform, user, metricNameMap, 'activity'); 13 | }; 14 | 15 | export const getOpenrank = async (platform: string, user: string) => { 16 | return getMetricByName(platform, user, metricNameMap, 'openrank'); 17 | }; 18 | 19 | export const getDeveloperNetwork = async (platform: string, user: string) => { 20 | return getMetricByName(platform, user, metricNameMap, 'developer_network'); 21 | }; 22 | 23 | export const getRepoNetwork = async (platform: string, user: string) => { 24 | return getMetricByName(platform, user, metricNameMap, 'repo_network'); 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/publish_configs.yml: -------------------------------------------------------------------------------- 1 | # publish public configs to OSS 2 | name: publish_configs 3 | 4 | on: 5 | push: 6 | branches: 7 | - "master" 8 | pull_request: 9 | branches: 10 | - "master" 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out repository code 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | 22 | - name: Build 23 | run: node scripts/test_configs.js 24 | 25 | - name: Publish to OSS 26 | if: ${{ github.event_name == 'push' }} 27 | uses: tvrcgo/oss-action@master 28 | with: 29 | key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} 30 | key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} 31 | region: ${{ secrets.OSS_REGION }} 32 | bucket: ${{ secrets.OSS_BUCKET }} 33 | assets: | 34 | ./public_configs/**:${{ secrets.OSS_TARGET_PATH }} 35 | -------------------------------------------------------------------------------- /src/helpers/should-feature-run.ts: -------------------------------------------------------------------------------- 1 | export type ShouldRunConditions = { 2 | asLongAs: ((() => boolean) | (() => Promise))[] | undefined; 3 | include: ((() => boolean) | (() => Promise))[] | undefined; 4 | exclude: ((() => boolean) | (() => Promise))[] | undefined; 5 | }; 6 | 7 | export default async function shouldFeatureRun(props: ShouldRunConditions): Promise { 8 | const { 9 | /** Every condition must be true */ 10 | asLongAs = [() => true], 11 | /** At least one condition must be true */ 12 | include = [() => true], 13 | /** No conditions must be true */ 14 | exclude = [() => false], 15 | } = props; 16 | return ( 17 | (await Promise.all(asLongAs.map((c) => c())).then((flags) => flags.every((flag) => flag === true))) && 18 | (await Promise.all(include.map((c) => c())).then((flags) => flags.some((flag) => flag === true))) && 19 | (await Promise.all(exclude.map((c) => c())).then((flags) => flags.every((flag) => flag === false))) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/oss-gpt/service.ts: -------------------------------------------------------------------------------- 1 | const DOCS_GPT_ENDPOINT = 'https://oss-gpt.frankzhao.cn/api'; 2 | 3 | export const getAnswer = async (activeDocs: string, question: string, history: [string, string]) => { 4 | try { 5 | const response = await fetch(`${DOCS_GPT_ENDPOINT}/answer`, { 6 | method: 'POST', 7 | headers: { 8 | Accept: 'application/json', 9 | 'Content-Type': 'application/json', 10 | }, 11 | body: JSON.stringify({ 12 | active_docs: activeDocs, 13 | api_key: null, 14 | embeddings_key: null, 15 | history: JSON.stringify(history), 16 | question, 17 | }), 18 | mode: 'cors', 19 | }); 20 | if (!response.ok) { 21 | return 'Oops, something went wrong.'; 22 | } else { 23 | const data = await response.json(); 24 | const answer = data.answer; 25 | return answer; 26 | } 27 | } catch (error) { 28 | console.error('Error:', error); 29 | return 'error'; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/pages/ContentScripts/features/repo-sidebar-labels/OpenDiggerLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Label } from '../../../../api/common'; 3 | 4 | interface OpenDiggerLabelProps { 5 | label: Label; 6 | } 7 | 8 | const OpenDiggerLabel: React.FC = ({ label }) => { 9 | return ( 10 | 20 | 31 | {label.name} 32 | 33 | ); 34 | }; 35 | 36 | export default OpenDiggerLabel; 37 | -------------------------------------------------------------------------------- /src/helpers/judge-interval.ts: -------------------------------------------------------------------------------- 1 | export function getInterval(data: any) { 2 | const startTime = Number(data[0][0].split('-')[0]); 3 | const endTime = Number(data[data.length - 1][0].split('-')[0]); 4 | const timeLength = endTime - startTime; 5 | const minInterval = timeLength > 2 ? 365 * 24 * 3600 * 1000 : 30 * 3600 * 24 * 1000; 6 | return { timeLength, minInterval }; 7 | } 8 | 9 | export function judgeInterval(instance: any, option: any, timeLength: number) { 10 | if (timeLength > 2) { 11 | instance.on('dataZoom', (params: any) => { 12 | let option = instance.getOption() as { 13 | xAxis: { minInterval?: any }[]; 14 | }; 15 | const startValue = params.batch[0].start; 16 | const endValue = params.batch[0].end; 17 | let minInterval: number; 18 | if (startValue == 0 && endValue == 100) { 19 | minInterval = 365 * 24 * 3600 * 1000; 20 | } else { 21 | minInterval = 30 * 24 * 3600 * 1000; 22 | } 23 | option.xAxis[0].minInterval = minInterval; 24 | instance.setOption(option); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/OSGraph.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | 3 | interface OSGraphProps { 4 | /** 5 | * shareId 6 | */ 7 | readonly shareId: number; 8 | /** 9 | * `style` for OSGraph container 10 | */ 11 | readonly style?: React.CSSProperties; 12 | /** 13 | * OSGraphUrl 14 | */ 15 | readonly OSGraphUrl: string; 16 | } 17 | 18 | const OSGraph: React.FC = ({ shareId, style = {}, OSGraphUrl }) => { 19 | const iframeRef = useRef(null); 20 | 21 | useEffect(() => { 22 | const loadIframe = () => { 23 | if (iframeRef.current) { 24 | iframeRef.current.src = OSGraphUrl; 25 | } 26 | }; 27 | // async 28 | if (shareId < 3) { 29 | setTimeout(loadIframe, shareId * 500); // Each iframe delays 500ms 30 | } else { 31 | loadIframe(); 32 | } 33 | }, [OSGraphUrl, shareId]); 34 | 35 | return ( 36 |
37 |