Skip to content

Commit e2aca7a

Browse files
CopilotTechQuery
andcommitted
Implement code review feedback - use RepositoryModel, add translations, improve HTML structure
Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com>
1 parent b7dad36 commit e2aca7a

File tree

7 files changed

+196
-329
lines changed

7 files changed

+196
-329
lines changed

components/IssueCard.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { FC, useContext } from 'react';
2+
import { Badge, Card } from 'react-bootstrap';
3+
4+
import type { Issue } from '../models/Base';
5+
import { I18nContext } from '../models/Translation';
6+
import styles from '../styles/Weekly.module.less';
7+
8+
interface IssueCardProps {
9+
issue: Issue;
10+
}
11+
12+
export const IssueCard: FC<IssueCardProps> = ({ issue }) => {
13+
const { t } = useContext(I18nContext);
14+
15+
return (
16+
<Card className={`h-100 shadow-sm ${styles.issueCard}`}>
17+
<Card.Body className="d-flex flex-column">
18+
<div className="d-flex justify-content-between align-items-start mb-3">
19+
<Badge bg={issue.state === 'open' ? 'success' : 'secondary'}>
20+
{issue.state === 'open' ? t('open') : t('closed')}
21+
</Badge>
22+
<small className="text-muted">#{issue.number}</small>
23+
</div>
24+
25+
<Card.Title className="h5">
26+
<a
27+
href={`/weekly/${issue.number}`}
28+
className={`text-decoration-none text-dark ${styles.issueTitle}`}
29+
>
30+
{issue.title}
31+
</a>
32+
</Card.Title>
33+
34+
{issue.body && (
35+
<Card.Text className="text-muted mb-3 flex-grow-1">
36+
{issue.body.slice(0, 150)}
37+
{issue.body.length > 150 && '...'}
38+
</Card.Text>
39+
)}
40+
41+
<div className="mt-auto">
42+
{issue.labels && issue.labels.length > 0 && (
43+
<div className="mb-2">
44+
{issue.labels.slice(0, 3).map((label, index) => (
45+
<Badge
46+
key={index}
47+
bg="light"
48+
text="dark"
49+
className={`me-1 ${styles.labelBadge}`}
50+
>
51+
{typeof label === 'string' ? label : label.name}
52+
</Badge>
53+
))}
54+
{issue.labels.length > 3 && (
55+
<Badge bg="light" text="dark" className="small">
56+
+{issue.labels.length - 3}
57+
</Badge>
58+
)}
59+
</div>
60+
)}
61+
62+
<div
63+
className={`d-flex justify-content-between align-items-center text-small text-muted ${styles.authorInfo}`}
64+
>
65+
<span>
66+
{issue.user && (
67+
<>
68+
{t('weekly_author')}: {issue.user.login}
69+
</>
70+
)}
71+
</span>
72+
<time dateTime={issue.created_at}>
73+
{new Date(issue.created_at).toLocaleDateString('zh-CN')}
74+
</time>
75+
</div>
76+
</div>
77+
</Card.Body>
78+
</Card>
79+
);
80+
};

models/Base.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'core-js/full/array/from-async';
22

33
import { HTTPClient } from 'koajax';
4-
import { githubClient, RepositoryModel } from 'mobx-github';
4+
import { githubClient, RepositoryModel, Issue as GitHubIssue } from 'mobx-github';
55
import { TableCellAttachment, TableCellMedia, TableCellValue } from 'mobx-lark';
66
import { DataObject } from 'mobx-restful';
77
import { isEmpty } from 'web-utility';
@@ -30,7 +30,8 @@ githubClient.use(({ request }, next) => {
3030
return next();
3131
});
3232

33-
export { githubClient };
33+
export { githubClient, RepositoryModel };
34+
export type { GitHubIssue as Issue };
3435

3536
export const githubRawClient = new HTTPClient({
3637
baseURI: `${ProxyBaseURL}/raw.githubusercontent.com/`,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"copy-webpack-plugin": "^13.0.0",
1616
"core-js": "^3.44.0",
1717
"file-type": "^21.0.0",
18+
"github-markdown-css": "^5.8.1",
1819
"idea-react": "^2.0.0-rc.13",
1920
"koa": "^2.16.1",
2021
"koajax": "^3.1.2",

pages/weekly/[id].tsx

Lines changed: 78 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'github-markdown-css/github-markdown-light.css';
2+
13
import { marked } from 'marked';
24
import { observer } from 'mobx-react';
35
import { GetStaticPaths, GetStaticProps } from 'next';
@@ -6,82 +8,51 @@ import { FC, useContext } from 'react';
68
import { Badge, Breadcrumb, Button, Card, Container } from 'react-bootstrap';
79

810
import { PageHead } from '../../components/Layout/PageHead';
9-
import { githubClient } from '../../models/Base';
11+
import type { Issue } from '../../models/Base';
12+
import { RepositoryModel } from '../../models/Base';
1013
import { I18nContext } from '../../models/Translation';
1114
import styles from '../../styles/Weekly.module.less';
1215

13-
// GitHub Issue type definition
14-
interface GitHubIssue {
15-
id: number;
16-
number: number;
17-
title: string;
18-
body: string | null;
19-
state: 'open' | 'closed';
20-
labels: Array<{ name: string; color: string } | string>;
21-
user: {
22-
login: string;
23-
avatar_url: string;
24-
} | null;
25-
created_at: string;
26-
updated_at: string;
27-
html_url: string;
28-
}
29-
3016
interface WeeklyDetailParams extends ParsedUrlQuery {
3117
id: string;
3218
}
3319

3420
interface WeeklyDetailProps {
35-
issue: GitHubIssue;
21+
issue: Issue;
3622
}
3723

3824
export const getStaticPaths: GetStaticPaths<WeeklyDetailParams> = async () => {
39-
try {
40-
const { body: issues } = await githubClient.get<GitHubIssue[]>(
41-
'repos/FreeCodeCamp-Chengdu/IT-Technology-weekly/issues?state=all&sort=created&direction=desc',
42-
);
43-
44-
const paths = (issues || []).map(issue => ({
45-
params: { id: issue.number.toString() },
46-
}));
47-
48-
return { paths, fallback: 'blocking' };
49-
} catch (error) {
50-
console.error('Failed to generate static paths:', error);
51-
52-
return { paths: [], fallback: 'blocking' };
53-
}
25+
const repository = new RepositoryModel('FreeCodeCamp-Chengdu');
26+
const repo = await repository.getOne('IT-Technology-weekly', ['issues']);
27+
28+
const paths = (repo.issues || []).map(issue => ({
29+
params: { id: issue.number.toString() },
30+
}));
31+
32+
return { paths, fallback: 'blocking' };
5433
};
5534

5635
export const getStaticProps: GetStaticProps<WeeklyDetailProps, WeeklyDetailParams> = async ({
5736
params,
5837
}) => {
5938
const { id } = params!;
60-
61-
try {
62-
const { body: issue } = await githubClient.get<GitHubIssue>(
63-
`repos/FreeCodeCamp-Chengdu/IT-Technology-weekly/issues/${id}`,
64-
);
65-
66-
if (!issue) {
67-
return {
68-
notFound: true,
69-
};
70-
}
71-
72-
return {
73-
props: {
74-
issue: JSON.parse(JSON.stringify(issue)),
75-
},
76-
revalidate: 3600, // Revalidate every hour
77-
};
78-
} catch (error) {
79-
console.error('Failed to fetch issue:', error);
80-
39+
const repository = new RepositoryModel('FreeCodeCamp-Chengdu');
40+
const repo = await repository.getOne('IT-Technology-weekly', ['issues']);
41+
42+
const issue = repo.issues?.find(issue => issue.number.toString() === id);
43+
44+
if (!issue) {
8145
return {
8246
notFound: true,
8347
};
8448
}
49+
50+
return {
51+
props: {
52+
issue: JSON.parse(JSON.stringify(issue)),
53+
},
54+
revalidate: 3600, // Revalidate every hour
55+
};
8556
};
8657

8758
const WeeklyDetailPage: FC<WeeklyDetailProps> = observer(({ issue }) => {
@@ -96,7 +67,7 @@ const WeeklyDetailPage: FC<WeeklyDetailProps> = observer(({ issue }) => {
9667
/>
9768

9869
<Breadcrumb className="mb-4">
99-
<Breadcrumb.Item href="/">首页</Breadcrumb.Item>
70+
<Breadcrumb.Item href="/">{t('home_page')}</Breadcrumb.Item>
10071
<Breadcrumb.Item href="/weekly">{t('weekly')}</Breadcrumb.Item>
10172
<Breadcrumb.Item active>#{issue.number}</Breadcrumb.Item>
10273
</Breadcrumb>
@@ -112,62 +83,70 @@ const WeeklyDetailPage: FC<WeeklyDetailProps> = observer(({ issue }) => {
11283

11384
<h1 className="display-5 mb-3">{issue.title}</h1>
11485

115-
{issue.labels && issue.labels.length > 0 && (
116-
<div className="mb-3">
86+
{issue.labels?.[0] && (
87+
<ul className="list-unstyled mb-3">
11788
{issue.labels.map((label, index) => (
118-
<Badge
119-
key={index}
120-
bg="light"
121-
text="dark"
122-
className={`me-2 mb-2 ${styles.labelBadge}`}
123-
>
124-
{typeof label === 'string' ? label : label.name}
125-
</Badge>
89+
<li key={index} className="d-inline-block me-2 mb-2">
90+
<Badge
91+
bg="light"
92+
text="dark"
93+
className={styles.labelBadge}
94+
>
95+
{typeof label === 'string' ? label : label.name}
96+
</Badge>
97+
</li>
12698
))}
127-
</div>
99+
</ul>
128100
)}
129101

130-
<div className="d-flex justify-content-between align-items-center mb-4 pb-3 border-bottom">
131-
<div className="text-muted">
132-
{issue.user && (
133-
<span>
134-
{t('weekly_author')}: <strong>{issue.user.login}</strong>
135-
</span>
136-
)}
137-
{issue.created_at && (
138-
<span className="ms-3">
139-
{t('weekly_published')}:{' '}
102+
<dl className="row mb-4 pb-3 border-bottom">
103+
{issue.user && (
104+
<>
105+
<dt className="col-sm-3 text-muted">{t('weekly_author')}:</dt>
106+
<dd className="col-sm-9">
107+
<strong>{issue.user.login}</strong>
108+
</dd>
109+
</>
110+
)}
111+
{issue.created_at && (
112+
<>
113+
<dt className="col-sm-3 text-muted">{t('weekly_published')}:</dt>
114+
<dd className="col-sm-9">
140115
<time dateTime={issue.created_at}>
141116
{new Date(issue.created_at).toLocaleString('zh-CN')}
142117
</time>
143-
</span>
144-
)}
145-
{issue.updated_at && issue.updated_at !== issue.created_at && (
146-
<span className="ms-3">
147-
{t('weekly_updated')}:{' '}
118+
</dd>
119+
</>
120+
)}
121+
{issue.updated_at && issue.updated_at !== issue.created_at && (
122+
<>
123+
<dt className="col-sm-3 text-muted">{t('weekly_updated')}:</dt>
124+
<dd className="col-sm-9">
148125
<time dateTime={issue.updated_at}>
149126
{new Date(issue.updated_at).toLocaleString('zh-CN')}
150127
</time>
151-
</span>
152-
)}
153-
</div>
154-
155-
<div className="d-flex gap-2">
156-
<Button
157-
variant="outline-primary"
158-
size="sm"
159-
href={issue.html_url}
160-
target="_blank"
161-
rel="noopener noreferrer"
162-
>
163-
{t('view_on_github')}
164-
</Button>
165-
</div>
128+
</dd>
129+
</>
130+
)}
131+
</dl>
132+
<div className="d-flex justify-content-end mb-4">
133+
<Button
134+
variant="outline-primary"
135+
size="sm"
136+
href={issue.html_url}
137+
target="_blank"
138+
rel="noopener noreferrer"
139+
>
140+
{t('view_on_github')}
141+
</Button>
166142
</div>
167143
</header>
168144

169145
{htmlContent ? (
170-
<div dangerouslySetInnerHTML={{ __html: htmlContent }} className={styles.markdownBody} />
146+
<div
147+
dangerouslySetInnerHTML={{ __html: htmlContent }}
148+
className="markdown-body"
149+
/>
171150
) : (
172151
<Card>
173152
<Card.Body className="text-center text-muted">
@@ -184,7 +163,7 @@ const WeeklyDetailPage: FC<WeeklyDetailProps> = observer(({ issue }) => {
184163
</Button>
185164

186165
<div className="text-muted small">
187-
<p className="mb-0">
166+
<p>
188167
{t('github_document_description')}
189168
<a href={issue.html_url} target="_blank" rel="noopener noreferrer" className="ms-1">
190169
{t('view_original_on_github')}

0 commit comments

Comments
 (0)