Skip to content

Commit ab8cc9a

Browse files
committed
feat: add contribution ranking
1 parent 441a3c2 commit ab8cc9a

File tree

8 files changed

+287
-1
lines changed

8 files changed

+287
-1
lines changed

public/locales/en/rank.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,18 @@
4444
"nginx": "A free, open-source, lightweight, high-performance web server developed by Igor Sysoev for Rambler.ru, the second most visited site in Russia.",
4545
"openresty": "A web platform based on Nginx that can run Lua scripts using its LuaJIT engine, created by Yichun Zhang."
4646
}
47+
},
48+
"contribution": {
49+
"title": "Contribution Ranking",
50+
"nav": "{{month}} {{year}} Contribution Ranking",
51+
"p_text": "<strong>HelloGitHub Contribution Ranking</strong> is a ranking based on the contribution value of users in the HelloGitHub open source community, updated with the current month's data on the 15th of each month. Users can earn contribution points in the following ways: sharing an open source project earns 5 points, posting a comment earns 2 points, and if a comment is selected as a hot comment, an additional 10 contribution points will be awarded.",
52+
"thead": {
53+
"position": "Rank",
54+
"name": "User",
55+
"rating": "Level",
56+
"change": "Change",
57+
"total": "Total",
58+
"md_change": "📊"
59+
}
4760
}
4861
}

public/locales/zh/rank.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,18 @@
4444
"nginx": "免费开源、轻量级、高性能 Web 服务器,由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点开发。",
4545
"openresty": "一个基于 Nginx 的 Web 平台,可以使用其 LuaJIT 引擎运行 Lua 脚本,由章亦春创建。"
4646
}
47+
},
48+
"contribution": {
49+
"title": "用户贡献排名",
50+
"nav": "{{year}} 年 {{month}} 月用户贡献排行榜",
51+
"p_text": "<strong>「HelloGitHub 贡献排名」</strong>是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,每月 15 日更新当月的数据。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。",
52+
"thead": {
53+
"position": "排名",
54+
"name": "用户",
55+
"rating": "等级",
56+
"change": "对比上月",
57+
"total": "总贡献",
58+
"md_change": "本月"
59+
}
4760
}
4861
}

src/components/rankTable/RankTable.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const RankTable = ({ columns, list, i18n_lang }: TableProps) => {
6666
return (
6767
<td
6868
key={key}
69-
className='truncate whitespace-nowrap bg-white px-3 py-2 text-left text-sm font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-300 md:px-6 md:py-4'
69+
className='truncate whitespace-nowrap bg-white px-4 py-2 text-left text-sm font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-300 md:px-6 md:py-4'
7070
>
7171
{content}
7272
</td>
@@ -114,11 +114,13 @@ export const RankSearchBar = ({
114114
return i18n_lang == 'en'
115115
? [
116116
{ key: '/report/tiobe', value: 'Language' },
117+
{ key: '/report/contribution', value: 'Contribution' },
117118
{ key: '/report/netcraft', value: 'Server' },
118119
{ key: '/report/db-engines', value: 'Database' },
119120
]
120121
: [
121122
{ key: '/report/tiobe', value: '编程语言' },
123+
{ key: '/report/contribution', value: '用户贡献' },
122124
{ key: '/report/netcraft', value: '服务器' },
123125
{ key: '/report/db-engines', value: '数据库' },
124126
];

src/components/report/Report.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { AiFillCaretDown, AiFillCaretUp } from 'react-icons/ai';
22
import { IoMdRemove, IoMdTrendingDown, IoMdTrendingUp } from 'react-icons/io';
33

4+
import { NoPrefetchLink } from '../links/CustomLink';
5+
46
import { RankDataItem } from '@/types/rank';
57

68
export const ChangeColumnRender = (
@@ -54,3 +56,55 @@ export const TrendColumnRender = (
5456
}
5557
return <span>{icon}</span>;
5658
};
59+
60+
export const ContributionColumnRender = (
61+
row: RankDataItem,
62+
i18n_lang: string
63+
) => {
64+
let text = '-';
65+
if (row.change !== null) {
66+
text = `+${row.change}`;
67+
} else {
68+
return <span>{text}</span>;
69+
}
70+
71+
return (
72+
<div className='flex items-center'>
73+
{i18n_lang === 'en' ? (
74+
<span className='text-green-500'>{text}</span>
75+
) : (
76+
<span className='text-red-500'>{text}</span>
77+
)}
78+
</div>
79+
);
80+
};
81+
82+
export const UserColumnRender = (row: RankDataItem) => {
83+
return (
84+
<NoPrefetchLink href={`/user/${row.uid}`}>
85+
<div className='flex cursor-pointer items-center'>
86+
<img
87+
width='20'
88+
height='20'
89+
src={row.avatar}
90+
alt={`${row.name} avatar`}
91+
className='block rounded'
92+
/>
93+
<span className='ml-1 '>{row.name}</span>
94+
</div>
95+
</NoPrefetchLink>
96+
);
97+
};
98+
99+
export const PositionColumnRender = (row: RankDataItem) => {
100+
const positionList = ['🥇', '🥈', '🥉'];
101+
return (
102+
<div className='flex items-center'>
103+
{row.position > 3 ? (
104+
<span>{row.position}</span>
105+
) : (
106+
<span>{positionList[row.position - 1]}</span>
107+
)}
108+
</div>
109+
);
110+
};

src/pages/report/contribution.tsx

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { GetServerSideProps, NextPage } from 'next';
2+
import { useRouter } from 'next/router';
3+
import { Trans, useTranslation } from 'next-i18next';
4+
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5+
import { useMemo } from 'react';
6+
7+
import Loading from '@/components/loading/Loading';
8+
import Navbar from '@/components/navbar/Navbar';
9+
import {
10+
getMonthName,
11+
RankSearchBar,
12+
RankTable,
13+
} from '@/components/rankTable/RankTable';
14+
import {
15+
ContributionColumnRender,
16+
PositionColumnRender,
17+
UserColumnRender,
18+
} from '@/components/report/Report';
19+
import Seo from '@/components/Seo';
20+
21+
import { getContributionRank } from '@/services/rank';
22+
import { getClientIP } from '@/utils/util';
23+
24+
import { RankPageProps } from '@/types/rank';
25+
26+
const ContributionPage: NextPage<RankPageProps> = ({
27+
year,
28+
month,
29+
monthList,
30+
list,
31+
}) => {
32+
const { t, i18n } = useTranslation('rank');
33+
const router = useRouter();
34+
35+
const onSearch = (key: string, value: string) => {
36+
if (key === 'month') {
37+
router.push(`/report/contribution/?month=${value}`);
38+
}
39+
if (key === 'target') {
40+
router.push(`${value}`);
41+
}
42+
};
43+
44+
// 排名 用户 等级 贡献值 对比上月
45+
const columns: any[] = useMemo(
46+
() => [
47+
{
48+
key: 'position',
49+
title: t('contribution.thead.position'),
50+
render: PositionColumnRender,
51+
width: 80,
52+
},
53+
{
54+
key: 'name',
55+
title: t('contribution.thead.name'),
56+
render: UserColumnRender,
57+
width: 180,
58+
},
59+
{ key: 'rating', title: t('contribution.thead.rating') },
60+
{
61+
key: 'change',
62+
title: t('contribution.thead.change'),
63+
render: ContributionColumnRender,
64+
},
65+
{
66+
key: 'total',
67+
title: t('contribution.thead.total'),
68+
},
69+
],
70+
[i18n.language]
71+
);
72+
73+
// 排名 用户 本月 贡献值
74+
const md_columns: any[] = useMemo(
75+
() =>
76+
columns
77+
.map((col) => {
78+
if (col.key === 'position') {
79+
return { ...col, width: 60 };
80+
}
81+
if (col.key === 'name') {
82+
return { ...col, width: 140 };
83+
}
84+
if (col.key === 'change') {
85+
return {
86+
...col,
87+
title: t('contribution.thead.md_change'),
88+
render: ContributionColumnRender,
89+
width: 80,
90+
};
91+
}
92+
if (col.key === 'rating') {
93+
return null;
94+
}
95+
return col;
96+
})
97+
.filter(Boolean),
98+
[i18n.language]
99+
);
100+
101+
return (
102+
<>
103+
<Seo title={t('contribution.title')} />
104+
{list ? (
105+
<div>
106+
<Navbar
107+
middleText={t('contribution.nav', {
108+
year: year,
109+
month: getMonthName(month, i18n.language, { forceEnglish: true }),
110+
})}
111+
/>
112+
113+
<div className='my-2 bg-white px-2 pt-2 dark:bg-gray-800 md:rounded-lg'>
114+
<RankSearchBar
115+
title='HelloGitHub'
116+
logo='https://img.hellogithub.com/logo/logo.png'
117+
i18n_lang={i18n.language}
118+
monthList={monthList}
119+
onChange={onSearch}
120+
/>
121+
<div className='md:hidden'>
122+
<RankTable
123+
columns={md_columns}
124+
list={list}
125+
i18n_lang={i18n.language}
126+
/>
127+
</div>
128+
<div className='hidden md:block'>
129+
<RankTable
130+
columns={columns}
131+
list={list}
132+
i18n_lang={i18n.language}
133+
/>
134+
</div>
135+
<div className='mt-2 rounded-lg border bg-white p-2 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300'>
136+
<div className='whitespace-pre-wrap leading-8'>
137+
<p>
138+
<Trans ns='rank' i18nKey='contribution.p_text' />
139+
</p>
140+
</div>
141+
</div>
142+
<div className='h-2' />
143+
</div>
144+
</div>
145+
) : (
146+
<Loading />
147+
)}
148+
</>
149+
);
150+
};
151+
152+
export const getServerSideProps: GetServerSideProps = async ({
153+
query,
154+
req,
155+
locale,
156+
}) => {
157+
const ip = getClientIP(req);
158+
const data = await getContributionRank(
159+
ip,
160+
query['month'] as unknown as number
161+
);
162+
if (!data.success) {
163+
return {
164+
notFound: true,
165+
};
166+
} else {
167+
return {
168+
props: {
169+
year: data.year,
170+
month: data.month,
171+
list: data.data,
172+
monthList: data.month_list,
173+
...(await serverSideTranslations(locale as string, ['common', 'rank'])),
174+
},
175+
};
176+
}
177+
};
178+
179+
export default ContributionPage;

src/pages/report/db-engines.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ const DBEnginesPage: NextPage<RankPageProps> = ({
6565
if (col.key === 'position') {
6666
return { ...col, width: 60 };
6767
}
68+
if (col.key === 'name') {
69+
return { ...col, width: 160 };
70+
}
6871
if (col.key === 'rating') {
6972
return { ...col, width: 80 };
7073
}

src/services/rank.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,23 @@ export const getDBRank = async (
6363
return {} as RankData;
6464
}
6565
};
66+
67+
// 贡献值排名
68+
export const getContributionRank = async (
69+
ip: string,
70+
month?: number
71+
): Promise<RankData> => {
72+
const req: RequestInit = {};
73+
req.headers = { 'x-real-ip': ip, 'x-forwarded-for': ip };
74+
75+
let url = '/report/contribution/';
76+
if (month) {
77+
url = `${url}?month=${month}`;
78+
}
79+
try {
80+
const data = await fetcher<RankData>(makeUrl(url), req);
81+
return data;
82+
} catch (error) {
83+
return {} as RankData;
84+
}
85+
};

src/types/rank.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export interface RankDataItem {
77
star?: string;
88
percent?: boolean;
99
total?: number;
10+
avatar?: string;
11+
uid?: string;
1012
}
1113

1214
export interface RankData {

0 commit comments

Comments
 (0)