Skip to content

Commit a16b186

Browse files
CopilotTechQuery
andauthored
[add] IT weekly pages (#44)
Co-authored-by: South Drifter <shiy2008@gmail.com>
1 parent ad25c1f commit a16b186

File tree

19 files changed

+2361
-1889
lines changed

19 files changed

+2361
-1889
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI & CD
22
on:
33
push:
44
branches:
5-
- '*'
5+
- '**'
66
jobs:
77
Build-and-Deploy:
88
env:

components/Git/ArticleEditor.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { readAs } from 'koajax';
33
import { debounce } from 'lodash';
44
import { marked } from 'marked';
55
import { computed, observable } from 'mobx';
6-
import { GitContent } from 'mobx-github';
6+
import { Content, ContentModel } from 'mobx-github';
77
import { observer } from 'mobx-react';
88
import { ObservedComponent } from 'mobx-react-helper';
99
import { DataObject } from 'mobx-restful';
1010
import { SearchableInput } from 'mobx-restful-table';
1111
import { ChangeEvent, FormEvent } from 'react';
1212
import { Button, Col, Form } from 'react-bootstrap';
13-
import { blobOf, formatDate, uniqueID } from 'web-utility';
13+
import { blobOf, encodeBase64, formatDate, uniqueID } from 'web-utility';
1414
import YAML from 'yaml';
1515

1616
import { GitFileSearchModel } from '../../models/GitFile';
@@ -67,7 +67,7 @@ export class ArticleEditor extends ObservedComponent<{}, typeof i18n> {
6767
@observable
6868
accessor meta: PostMeta | null = null;
6969

70-
static contentFilter({ type, name }: GitContent) {
70+
static contentFilter({ type, name }: Content) {
7171
return (
7272
type === 'dir' ||
7373
(type === 'file' && Object.values(fileType).flat().includes(name.split('.').slice(-1)[0]))
@@ -181,30 +181,31 @@ export class ArticleEditor extends ObservedComponent<{}, typeof i18n> {
181181

182182
if (!editorContent) return;
183183

184-
const root = document.querySelector('div[contenteditable]');
184+
const contentStore = new ContentModel(currentRepository.owner, currentRepository.name),
185+
root = document.querySelector('div[contenteditable]');
185186
const media: HTMLMediaElement[] = [].filter.call(
186187
root!.querySelectorAll('img[src], audio[src], video[src]'),
187188
({ src }) => new URL(src).protocol === 'blob:',
188189
);
189190

190191
for (const file of media) {
191192
const blob = await blobOf(file.src);
193+
const [, content] = ((await readAs(blob, 'dataURL').result) as string).split(',');
192194

193195
const filePath = this.path.replace(/\.\w+$/, `/${uniqueID()}.${blob.type.split('/')[1]}`);
194-
const { download_url } = await repositoryStore.updateContent(
196+
197+
const { download_url } = await contentStore.updateOne(
198+
{ content },
195199
filePath,
196-
blob,
197200
'[Upload] from Git-Pager',
198-
currentRepository.name,
199201
);
200202
file.src = download_url!;
201203
}
202204

203-
await repositoryStore.updateContent(
205+
await contentStore.updateOne(
206+
{ content: encodeBase64(this.getContent() || '') },
204207
this.path,
205-
this.getContent() as string,
206208
message.value.trim(),
207-
currentRepository.name,
208209
);
209210
window.alert('Submitted');
210211
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.issueCard {
2+
transition:
3+
transform 0.2s ease,
4+
box-shadow 0.2s ease;
5+
6+
&:hover {
7+
transform: translateY(-2px);
8+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
9+
}
10+
}
11+
.issueTitle {
12+
transition: color 0.2s ease;
13+
14+
&:hover {
15+
color: #028dfa !important;
16+
}
17+
}
18+
.authorInfo {
19+
color: #586069;
20+
font-size: 14px;
21+
}

components/Git/IssueCard.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Issue } from 'mobx-github';
2+
import { observer } from 'mobx-react';
3+
import { FC, useContext } from 'react';
4+
import { Badge, Card, CardProps } from 'react-bootstrap';
5+
6+
import { I18nContext } from '../../models/Translation';
7+
import styles from './IssueCard.module.less';
8+
import { LabelBar } from './LabelBar';
9+
10+
export type IssueCardProps = Omit<Issue, 'id'> & Omit<CardProps, 'body'>;
11+
12+
export const IssueCard: FC<IssueCardProps> = observer(
13+
({ className = '', number, title, labels, body, user, created_at, state }) => {
14+
const { t } = useContext(I18nContext);
15+
16+
return (
17+
<Card className={`shadow-sm ${styles.issueCard} ${className}`}>
18+
<Card.Header className="d-flex justify-content-between align-items-center">
19+
<Badge bg={state === 'open' ? 'success' : 'secondary'}>
20+
{state === 'open' ? t('open') : t('closed')}
21+
</Badge>
22+
<small className="text-muted">#{number}</small>
23+
</Card.Header>
24+
25+
<Card.Body className="d-flex flex-column gap-3">
26+
<Card.Title className="h5">
27+
<a
28+
href={`/weekly/${number}`}
29+
className={`text-decoration-none text-dark ${styles.issueTitle}`}
30+
>
31+
{title}
32+
</a>
33+
</Card.Title>
34+
35+
{body && (
36+
<Card.Text className="text-muted flex-grow-1">
37+
{body.slice(0, 150)}
38+
{body.length > 150 && '...'}
39+
</Card.Text>
40+
)}
41+
{labels?.[0] && <LabelBar labels={labels} />}
42+
</Card.Body>
43+
44+
<Card.Footer
45+
className={`d-flex justify-content-between align-items-center small text-muted ${styles.authorInfo}`}
46+
>
47+
<span>{user && `${t('weekly_author')}: ${user.login}`}</span>
48+
49+
<time dateTime={created_at}>{new Date(created_at).toLocaleDateString('zh-CN')}</time>
50+
</Card.Footer>
51+
</Card>
52+
);
53+
},
54+
);

components/Git/LabelBar.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Issue } from 'mobx-github';
2+
import { FC } from 'react';
3+
4+
export type LabelBarProps = Pick<Issue, 'labels'>;
5+
6+
export const LabelBar: FC<LabelBarProps> = ({ labels }) => (
7+
<ul className="list-unstyled d-flex flex-wrap gap-2">
8+
{labels.map((label, index) => {
9+
const { name, color } = typeof label === 'object' ? label : {};
10+
11+
return (
12+
<li
13+
key={index}
14+
className="p-2 rounded small"
15+
style={{ backgroundColor: color || 'lightgray' }}
16+
>
17+
{typeof label === 'string' ? label : name}
18+
</li>
19+
);
20+
})}
21+
</ul>
22+
);

components/Navigator/MainNavigator.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const MainNavigator: FC = observer(() => {
2626

2727
<Nav.Link href="/activity">{t('activity')}</Nav.Link>
2828

29+
<Nav.Link href="/weekly">{t('weekly')}</Nav.Link>
30+
2931
<Nav.Link href="/community">{t('community')}</Nav.Link>
3032

3133
<Nav.Link href="/article/Wiki/_posts/Profile/about">{t('about')}</Nav.Link>

package.json

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,76 @@
77
"node": ">=22"
88
},
99
"dependencies": {
10-
"@koa/router": "^13.1.1",
11-
"@mdx-js/loader": "^3.1.0",
12-
"@mdx-js/react": "^3.1.0",
13-
"@next/mdx": "^15.4.1",
14-
"@sentry/nextjs": "^9.39.0",
15-
"copy-webpack-plugin": "^13.0.0",
16-
"core-js": "^3.44.0",
10+
"@mdx-js/loader": "^3.1.1",
11+
"@mdx-js/react": "^3.1.1",
12+
"@next/mdx": "^15.5.2",
13+
"@sentry/nextjs": "^10.10.0",
14+
"copy-webpack-plugin": "^13.0.1",
15+
"core-js": "^3.45.1",
1716
"file-type": "^21.0.0",
1817
"idea-react": "^2.0.0-rc.13",
19-
"koa": "^2.16.1",
18+
"koa": "^3.0.1",
2019
"koajax": "^3.1.2",
21-
"less": "^4.3.0",
20+
"less": "^4.4.1",
2221
"less-loader": "^12.3.0",
2322
"lodash": "^4.17.21",
24-
"marked": "^16.0.0",
23+
"marked": "^16.2.1",
2524
"mime": "^4.0.7",
2625
"mobx": "^6.13.7",
27-
"mobx-github": "^0.3.11",
26+
"mobx-github": "^0.5.1",
2827
"mobx-i18n": "^0.7.1",
29-
"mobx-lark": "^2.2.0",
28+
"mobx-lark": "^2.4.1",
3029
"mobx-react": "^9.2.0",
3130
"mobx-react-helper": "^0.5.1",
3231
"mobx-restful": "^2.1.0",
33-
"mobx-restful-table": "^2.5.2",
34-
"next": "^15.4.1",
32+
"mobx-restful-table": "^2.5.3",
33+
"next": "^15.5.2",
3534
"next-pwa": "~5.6.0",
36-
"next-ssr-middleware": "^1.0.1",
35+
"next-ssr-middleware": "^1.0.3",
3736
"next-with-less": "^3.0.1",
38-
"react": "^19.1.0",
37+
"react": "^19.1.1",
3938
"react-bootstrap": "^2.10.10",
4039
"react-bootstrap-editor": "^2.1.1",
41-
"react-dom": "^19.1.0",
40+
"react-dom": "^19.1.1",
4241
"remark-frontmatter": "^5.0.0",
4342
"remark-gfm": "^4.0.1",
4443
"remark-mdx-frontmatter": "^5.2.0",
45-
"undici": "^7.11.0",
46-
"web-utility": "^4.4.3",
47-
"webpack": "^5.100.2",
48-
"yaml": "^2.8.0"
44+
"undici": "^7.15.0",
45+
"web-utility": "^4.5.3",
46+
"webpack": "^5.101.3",
47+
"yaml": "^2.8.1"
4948
},
5049
"devDependencies": {
5150
"@babel/plugin-proposal-decorators": "^7.28.0",
5251
"@babel/plugin-transform-typescript": "^7.28.0",
5352
"@babel/preset-react": "^7.27.1",
54-
"@cspell/eslint-plugin": "^9.1.5",
55-
"@eslint/compat": "^1.3.1",
53+
"@cspell/eslint-plugin": "^9.2.1",
54+
"@eslint/compat": "^1.3.2",
5655
"@eslint/eslintrc": "^3.3.1",
57-
"@eslint/js": "^9.31.0",
58-
"@next/eslint-plugin-next": "^15.4.1",
56+
"@eslint/js": "^9.35.0",
57+
"@next/eslint-plugin-next": "^15.5.2",
5958
"@octokit/openapi-types": "^25.1.0",
6059
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
61-
"@stylistic/eslint-plugin": "^5.1.0",
60+
"@stylistic/eslint-plugin": "^5.3.1",
6261
"@types/eslint-config-prettier": "^6.11.3",
63-
"@types/koa": "^2.15.0",
64-
"@types/koa__router": "^12.0.4",
62+
"@types/koa": "^3.0.0",
6563
"@types/lodash": "^4.17.20",
6664
"@types/next-pwa": "^5.6.9",
67-
"@types/node": "^22.16.4",
68-
"@types/react": "^19.1.8",
69-
"eslint": "^9.31.0",
70-
"eslint-config-next": "^15.4.1",
71-
"eslint-config-prettier": "^10.1.5",
65+
"@types/node": "^22.18.1",
66+
"@types/react": "^19.1.12",
67+
"eslint": "^9.35.0",
68+
"eslint-config-next": "^15.5.2",
69+
"eslint-config-prettier": "^10.1.8",
7270
"eslint-plugin-react": "^7.37.5",
7371
"eslint-plugin-simple-import-sort": "^12.1.1",
7472
"globals": "^16.3.0",
7573
"husky": "^9.1.7",
76-
"jiti": "^2.4.2",
77-
"lint-staged": "^16.1.2",
74+
"jiti": "^2.5.1",
75+
"lint-staged": "^16.1.6",
7876
"prettier": "^3.6.2",
7977
"prettier-plugin-css-order": "^2.1.2",
80-
"typescript": "~5.8.3",
81-
"typescript-eslint": "^8.37.0"
78+
"typescript": "~5.9.2",
79+
"typescript-eslint": "^8.42.0"
8280
},
8381
"resolutions": {
8482
"next": "$next"
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1+
import { Context } from 'koa';
12
import { LarkPageData, TableRecord, TableRecordData } from 'mobx-lark';
23
import { DataObject } from 'mobx-restful';
3-
import { createKoaRouter } from 'next-ssr-middleware';
4+
import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware';
45

5-
import { withSafeKoaRouter } from '../../../core';
6+
import { safeAPI } from '../../../core';
67
import { proxyLark, proxyLarkAll } from '../../core';
78

89
export const config = { api: { bodyParser: false } };
910

1011
const router = createKoaRouter(import.meta.url);
1112

1213
function filterData(fields: DataObject) {
13-
for (const key of Object.keys(fields))
14-
if (!/^\w+$/.test(key)) delete fields[key];
14+
for (const key of Object.keys(fields)) if (!/^\w+$/.test(key)) delete fields[key];
1515
}
1616

17-
router.get('/apps/:app/tables/:table/records/:record', async context => {
18-
const { status, body } =
19-
await proxyLark<TableRecordData<DataObject>>(context);
17+
router.get('/apps/:app/tables/:table/records/:record', safeAPI, async (context: Context) => {
18+
const { status, body } = await proxyLark<TableRecordData<DataObject>>(context);
2019

2120
const { fields } = body!.data!.record;
2221

@@ -26,9 +25,8 @@ router.get('/apps/:app/tables/:table/records/:record', async context => {
2625
context.body = body;
2726
});
2827

29-
router.get('/apps/:app/tables/:table/records', async context => {
30-
const { status, body } =
31-
await proxyLark<LarkPageData<TableRecord<DataObject>>>(context);
28+
router.get('/apps/:app/tables/:table/records', safeAPI, async (context: Context) => {
29+
const { status, body } = await proxyLark<LarkPageData<TableRecord<DataObject>>>(context);
3230

3331
const list = body!.data!.items || [];
3432

@@ -38,6 +36,6 @@ router.get('/apps/:app/tables/:table/records', async context => {
3836
context.body = body;
3937
});
4038

41-
router.all('/(.*)', proxyLarkAll);
39+
router.all('/(.*)', safeAPI, proxyLarkAll);
4240

43-
export default withSafeKoaRouter(router);
41+
export default withKoaRouter(router);

pages/api/Lark/file/[id]/[name].ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { fileTypeFromStream } from 'file-type';
2+
import { Middleware } from 'koa';
23
import MIME from 'mime';
3-
import { createKoaRouter } from 'next-ssr-middleware';
4+
import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware';
45
import { Readable } from 'stream';
56

67
import { CACHE_HOST } from '../../../../../models/configuration';
7-
import { withSafeKoaRouter } from '../../../core';
8+
import { safeAPI } from '../../../core';
89
import { lark } from '../../core';
910

1011
const router = createKoaRouter(import.meta.url);
1112

12-
router.all('/:id/:name', async context => {
13+
const downloader: Middleware = async context => {
1314
const { method, url, params, query } = context;
1415
const { id, name } = params;
1516

@@ -21,10 +22,9 @@ router.all('/:id/:name', async context => {
2122

2223
const token = await lark.getAccessToken();
2324

24-
const response = await fetch(
25-
lark.client.baseURI + `drive/v1/medias/${id}/download`,
26-
{ headers: { Authorization: `Bearer ${token}` } },
27-
);
25+
const response = await fetch(lark.client.baseURI + `drive/v1/medias/${id}/download`, {
26+
headers: { Authorization: `Bearer ${token}` },
27+
});
2828
const { ok, status, headers, body } = response;
2929

3030
if (!ok) {
@@ -47,6 +47,8 @@ router.all('/:id/:name', async context => {
4747
if (method === 'GET')
4848
// @ts-expect-error Web type compatibility
4949
context.body = Readable.fromWeb(stream2);
50-
});
50+
};
5151

52-
export default withSafeKoaRouter(router);
52+
router.head('/:id/:name', safeAPI, downloader).get('/:id/:name', safeAPI, downloader);
53+
54+
export default withKoaRouter(router);

0 commit comments

Comments
 (0)