Skip to content

Commit 65d20ad

Browse files
committed
[refactor] make Next.js i18n smoother with MobX-i18n 0.7
[refactor] rewrite Lark back-end API with Koa middlewares [optimize] use TS for ESLint configuration [optimize] upgrade Upstream packages [remove] useless Example files
1 parent ca58de9 commit 65d20ad

26 files changed

+3678
-2910
lines changed

components/Git/Card.tsx

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { text2color } from 'idea-react';
22
import { GitRepository } from 'mobx-github';
33
import { observer } from 'mobx-react';
4-
import { FC } from 'react';
4+
import { FC, useContext } from 'react';
55
import { Badge, Button, Card, CardProps, Col, Row } from 'react-bootstrap';
66

7-
import { t } from '../../models/Translation';
7+
import { I18nContext } from '../../models/Translation';
88
import { GitLogo } from './Logo';
99

1010
export interface GitCardProps
@@ -24,45 +24,49 @@ export const GitCard: FC<GitCardProps> = observer(
2424
description,
2525
homepage,
2626
...props
27-
}) => (
28-
<Card className={className} {...props}>
29-
<Card.Body className="d-flex flex-column gap-3">
30-
<Card.Title as="h3" className="h5">
31-
<a target="_blank" href={html_url} rel="noreferrer">
32-
{full_name}
33-
</a>
34-
</Card.Title>
27+
}) => {
28+
const { t } = useContext(I18nContext);
3529

36-
<nav className="flex-fill">
37-
{topics.map(topic => (
38-
<Badge
39-
key={topic}
40-
className="me-1 text-decoration-none"
41-
bg={text2color(topic, ['light'])}
42-
as="a"
43-
target="_blank"
44-
href={`https://github.com/topics/${topic}`}
45-
>
46-
{topic}
47-
</Badge>
48-
))}
49-
</nav>
50-
<Row as="ul" className="list-unstyled g-4" xs={4}>
51-
{languages.map(language => (
52-
<Col key={language} as="li">
53-
<GitLogo name={language} />
54-
</Col>
55-
))}
56-
</Row>
57-
<Card.Text>{description}</Card.Text>
58-
</Card.Body>
59-
<Card.Footer className="d-flex justify-content-between align-items-center">
60-
{homepage && (
61-
<Button variant="success" target="_blank" href={homepage}>
62-
{t('home_page')}
63-
</Button>
64-
)}
65-
</Card.Footer>
66-
</Card>
67-
),
30+
return (
31+
<Card className={className} {...props}>
32+
<Card.Body className="d-flex flex-column gap-3">
33+
<Card.Title as="h3" className="h5">
34+
<a target="_blank" href={html_url} rel="noreferrer">
35+
{full_name}
36+
</a>
37+
</Card.Title>
38+
39+
<nav className="flex-fill">
40+
{topics.map(topic => (
41+
<Badge
42+
key={topic}
43+
className="me-1 text-decoration-none"
44+
bg={text2color(topic, ['light'])}
45+
as="a"
46+
target="_blank"
47+
href={`https://github.com/topics/${topic}`}
48+
>
49+
{topic}
50+
</Badge>
51+
))}
52+
</nav>
53+
<Row as="ul" className="list-unstyled g-4" xs={4}>
54+
{languages.map(language => (
55+
<Col key={language} as="li">
56+
<GitLogo name={language} />
57+
</Col>
58+
))}
59+
</Row>
60+
<Card.Text>{description}</Card.Text>
61+
</Card.Body>
62+
<Card.Footer className="d-flex justify-content-between align-items-center">
63+
{homepage && (
64+
<Button variant="success" target="_blank" href={homepage}>
65+
{t('home_page')}
66+
</Button>
67+
)}
68+
</Card.Footer>
69+
</Card>
70+
);
71+
},
6872
);

components/LarkImage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { TableCellValue } from 'mobx-lark';
22
import { FC } from 'react';
33
import { Image, ImageProps } from 'react-bootstrap';
44

5-
import { DefaultImage, fileURLOf } from '../pages/api/Lark/file/[id]';
5+
import { fileURLOf } from '../models/Base';
6+
import { DefaultImage } from '../models/configuration';
67

78
export interface LarkImageProps extends Omit<ImageProps, 'src'> {
89
src?: TableCellValue;

components/Layout/NotFoundCard.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { observer } from 'mobx-react';
12
import { ErrorProps } from 'next/error';
2-
import { FC } from 'react';
3+
import { FC, useContext } from 'react';
34

4-
import { i18n } from '../../models/Translation';
5+
import { I18nContext } from '../../models/Translation';
56

6-
export const NotFoundCard: FC<ErrorProps> = ({ title }) =>
7-
i18n.currentLanguage.startsWith('zh') ? (
7+
export const NotFoundCard: FC<ErrorProps> = observer(({ title }) => {
8+
const { currentLanguage } = useContext(I18nContext);
9+
10+
return currentLanguage.startsWith('zh') ? (
811
<script
912
src="//cdn.dnpw.org/404/v1.min.js"
1013
// @ts-expect-error https://www.dnpw.org/cn/pa-notfound.html
@@ -18,3 +21,4 @@ export const NotFoundCard: FC<ErrorProps> = ({ title }) =>
1821
src="https://notfound-static.fwebservices.be/en/404?key=66abb751ed312"
1922
/>
2023
);
24+
});

components/Layout/PageHead.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import Head from 'next/head';
22
import type { FC, PropsWithChildren } from 'react';
33

4+
import { Name, Summary } from '../../models/configuration';
5+
46
export type PageHeadProps = PropsWithChildren<{
57
title?: string;
68
description?: string;
79
}>;
810

9-
const Name = process.env.NEXT_PUBLIC_SITE_NAME,
10-
Summary = process.env.NEXT_PUBLIC_SITE_SUMMARY;
11-
1211
export const PageHead: FC<PageHeadProps> = ({
1312
title,
1413
description = Summary,
1514
children,
1615
}) => (
1716
<Head>
18-
<title>{`${title}${title && ' - '}${Name}`}</title>
17+
<title>{`${title ? `${title} - ` : ''}${Name}`}</title>
1918

2019
{description && <meta name="description" content={description} />}
2120

components/Navigator/LanguageMenu.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Option, Select } from 'idea-react';
22
import { observer } from 'mobx-react';
3-
import { FC } from 'react';
3+
import { FC, useContext } from 'react';
44

5-
import { i18n, LanguageName } from '../../models/Translation';
5+
import { I18nContext, LanguageName } from '../../models/Translation';
66

77
const LanguageMenu: FC = observer(() => {
8-
const { currentLanguage } = i18n;
8+
const i18n = useContext(I18nContext);
99

1010
return (
1111
<Select
12-
value={currentLanguage}
13-
onChange={key => i18n.changeLanguage(key as typeof currentLanguage)}
12+
value={i18n.currentLanguage}
13+
onChange={key => i18n.loadLanguages(key as typeof i18n.currentLanguage)}
1414
>
1515
{Object.entries(LanguageName).map(([key, name]) => (
1616
<Option key={key} value={key}>
Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,46 @@
11
import { observer } from 'mobx-react';
22
import dynamic from 'next/dynamic';
3-
import { FC } from 'react';
3+
import { FC, useContext } from 'react';
44
import { Container, Nav, Navbar } from 'react-bootstrap';
55

6-
import { t } from '../../models/Translation';
6+
import { Name } from '../../models/configuration';
7+
import { I18nContext } from '../../models/Translation';
78

8-
const LanguageMenu = dynamic(import('./LanguageMenu'), { ssr: false });
9+
const LanguageMenu = dynamic(() => import('./LanguageMenu'), { ssr: false });
910

10-
const Name = process.env.NEXT_PUBLIC_SITE_NAME || '';
11+
export const MainNavigator: FC = observer(() => {
12+
const { t } = useContext(I18nContext);
1113

12-
export const MainNavigator: FC = observer(() => (
13-
<Navbar bg="light" variant="light" fixed="top" expand="sm" collapseOnSelect>
14-
<Container>
15-
<Navbar.Brand href="/">{Name}</Navbar.Brand>
14+
return (
15+
<Navbar bg="light" variant="light" fixed="top" expand="sm" collapseOnSelect>
16+
<Container>
17+
<Navbar.Brand href="/">{Name}</Navbar.Brand>
1618

17-
<Navbar.Toggle aria-controls="navbar-inner" />
19+
<Navbar.Toggle aria-controls="navbar-inner" />
1820

19-
<Navbar.Collapse id="navbar-inner">
20-
<Nav className="me-auto">
21-
<Nav.Link href="/article">{t('article')}</Nav.Link>
21+
<Navbar.Collapse id="navbar-inner">
22+
<Nav className="me-auto">
23+
<Nav.Link href="/article">{t('article')}</Nav.Link>
2224

23-
<Nav.Link href="/activity">{t('activity')}</Nav.Link>
25+
<Nav.Link href="/activity">{t('activity')}</Nav.Link>
2426

25-
<Nav.Link href="/community">{t('community')}</Nav.Link>
27+
<Nav.Link href="/community">{t('community')}</Nav.Link>
2628

27-
<Nav.Link href="/article/Wiki/_posts/Profile/about">
28-
{t('about')}
29-
</Nav.Link>
29+
<Nav.Link href="/article/Wiki/_posts/Profile/about">
30+
{t('about')}
31+
</Nav.Link>
3032

31-
<Nav.Link
32-
target="_blank"
33-
href="https://github.com/FreeCodeCamp-Chengdu/FreeCodeCamp-Chengdu.github.io"
34-
>
35-
{t('source_code')}
36-
</Nav.Link>
37-
</Nav>
33+
<Nav.Link
34+
target="_blank"
35+
href="https://github.com/FreeCodeCamp-Chengdu/FreeCodeCamp-Chengdu.github.io"
36+
>
37+
{t('source_code')}
38+
</Nav.Link>
39+
</Nav>
3840

39-
<LanguageMenu />
40-
</Navbar.Collapse>
41-
</Container>
42-
</Navbar>
43-
));
41+
<LanguageMenu />
42+
</Navbar.Collapse>
43+
</Container>
44+
</Navbar>
45+
);
46+
});

components/Navigator/SearchBar.tsx

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { observer } from 'mobx-react';
2-
import { FC } from 'react';
2+
import { FC, useContext } from 'react';
33
import {
44
Button,
55
Form,
@@ -9,7 +9,7 @@ import {
99
InputGroupProps,
1010
} from 'react-bootstrap';
1111

12-
import { t } from '../../models/Translation';
12+
import { I18nContext } from '../../models/Translation';
1313
import styles from './SearchBar.module.less';
1414

1515
export interface SearchBarProps
@@ -27,24 +27,30 @@ export const SearchBar: FC<SearchBarProps> = observer(
2727
action = '/search',
2828
size,
2929
name = 'keywords',
30-
placeholder = t('keywords'),
30+
placeholder,
3131
expanded = true,
3232
defaultValue,
3333
value,
3434
onChange,
3535
...props
36-
}) => (
37-
<Form {...{ action, ...props }}>
38-
<InputGroup size={size}>
39-
<Form.Control
40-
className={expanded ? '' : styles.input}
41-
type="search"
42-
{...{ name, placeholder, defaultValue, value, onChange }}
43-
/>
44-
<Button type="submit" variant="light">
45-
🔍
46-
</Button>
47-
</InputGroup>
48-
</Form>
49-
),
36+
}) => {
37+
const { t } = useContext(I18nContext);
38+
39+
placeholder ??= t('keywords');
40+
41+
return (
42+
<Form {...{ action, ...props }}>
43+
<InputGroup size={size}>
44+
<Form.Control
45+
className={expanded ? '' : styles.input}
46+
type="search"
47+
{...{ name, placeholder, defaultValue, value, onChange }}
48+
/>
49+
<Button type="submit" variant="light">
50+
🔍
51+
</Button>
52+
</InputGroup>
53+
</Form>
54+
);
55+
},
5056
);
File renamed without changes.

models/Base.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,26 @@ import 'core-js/full/array/from-async';
22

33
import { HTTPClient } from 'koajax';
44
import { githubClient, RepositoryModel } from 'mobx-github';
5+
import { TableCellAttachment, TableCellMedia, TableCellValue } from 'mobx-lark';
56

6-
export const isServer = () => typeof window === 'undefined';
7-
8-
const VercelHost = process.env.VERCEL_URL,
9-
GithubToken = process.env.GITHUB_TOKEN;
10-
11-
const API_Host = isServer()
12-
? VercelHost
13-
? `https://${VercelHost}`
14-
: 'http://localhost:3000'
15-
: globalThis.location.origin;
7+
import { GITHUB_TOKEN, LARK_API_HOST } from './configuration';
168

179
export const larkClient = new HTTPClient({
18-
baseURI: `${API_Host}/api/Lark/`,
10+
baseURI: LARK_API_HOST,
1911
responseType: 'json',
2012
});
2113

2214
githubClient.use(({ request }, next) => {
23-
if (GithubToken)
15+
if (GITHUB_TOKEN)
2416
request.headers = {
2517
...request.headers,
26-
Authorization: `Bearer ${GithubToken}`,
18+
Authorization: `Bearer ${GITHUB_TOKEN}`,
2719
};
20+
2821
return next();
2922
});
3023

31-
export const repositoryStore = new RepositoryModel('freecodecamp-chengdu');
24+
export const repositoryStore = new RepositoryModel('idea2app');
3225

3326
type UploadedFile = Record<'originalname' | 'filename' | 'location', string>;
3427
/**
@@ -42,5 +35,18 @@ export async function upload(file: Blob) {
4235
'https://api.escuelajs.co/api/v1/files/upload',
4336
form,
4437
);
38+
4539
return body!.location;
4640
}
41+
42+
export function fileURLOf(field: TableCellValue, cache = false) {
43+
if (!(field instanceof Array) || !field[0]) return field + '';
44+
45+
const file = field[0] as TableCellMedia | TableCellAttachment;
46+
47+
let URI = `/api/Lark/file/${'file_token' in file ? file.file_token : file.attachmentToken}/${file.name}`;
48+
49+
if (cache) URI += '?cache=1';
50+
51+
return URI;
52+
}

0 commit comments

Comments
 (0)