Skip to content

Commit 5b755fd

Browse files
committed
Merge remote-tracking branch 'origin' into v3
2 parents 570cbc7 + 5aa9782 commit 5b755fd

20 files changed

+245
-70
lines changed

.env

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
NEXT_PUBLIC_SITE_NAME = idea2app
22
NEXT_PUBLIC_SITE_SUMMARY = 全行业信息化转型专家
3+
NEXT_PUBLIC_LOGO = https://github.com/idea2app.png
4+
5+
NEXT_PUBLIC_CACHE_HOST = https://cache.idea2.app
36

47
NEXT_PUBLIC_SENTRY_DSN = https://03e5d951172f411a04c1bab44022e22b@o4506471366852608.ingest.sentry.io/4506484563705856
58
SENTRY_ORG = idea2app
69
SENTRY_PROJECT = ows
710

8-
NEXT_PUBLIC_LARK_BASE = bascnFeH8Q37XWX0LLlBB9ojQzf
9-
NEXT_PUBLIC_CLIENT_TABLE = tblsb0vx4fqjSrGL
10-
NEXT_PUBLIC_PROJECT_TABLE = tblCxasoUUub3buB
11-
NEXT_PUBLIC_MEMBER_TABLE = tblJ98JHGEX0o6ij
12-
NEXT_PUBLIC_MEMBER_VIEW = vewLf4M0P8
11+
LARK_API_HOST = https://open.larksuite.com/open-apis/
12+
NEXT_PUBLIC_LARK_BASE = McGRbzEInauTTOsWX1blxbN1grh
13+
NEXT_PUBLIC_CLIENT_TABLE = tblRNMvpJ1UZwJW2
14+
NEXT_PUBLIC_PROJECT_TABLE = tblwaxbQ35uX2lUS
15+
NEXT_PUBLIC_MEMBER_TABLE = tblD7p7XAcrinGqO
16+
NEXT_PUBLIC_MEMBER_VIEW = vewLspr5VK

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
## Technology stack
1111

1212
- Language: [TypeScript v5][2] + [MDX v3][10]
13-
- Component engine: [Next.js v14][3]
13+
- Component engine: [Next.js v15][3]
1414
- Component suite: [Bootstrap v5][4]
1515
- PWA framework: [Workbox v6][5]
1616
- State management: [MobX v6][11]
@@ -68,16 +68,20 @@ You can check out [the Next.js GitHub repository][27] - your feedback and contri
6868

6969
| name | file | description |
7070
| :----------------------: | :----------: | :---------------------: |
71+
| `CRAWLER_TOKEN` | `.env.local` | Web hooks authorization |
7172
| `SENTRY_AUTH_TOKEN` | `.env.local` | [Official document][28] |
7273
| `SENTRY_ORG` | `.env` | [Official document][29] |
7374
| `SENTRY_PROJECT` | `.env` | [Official document][29] |
7475
| `NEXT_PUBLIC_SENTRY_DSN` | `.env` | [Official document][30] |
76+
| `GITHUB_TOKEN` | `.env.local` | [Official document][31] |
77+
| `LARK_APP_ID` | `.env.local` | [Official document][32] |
78+
| `LARK_APP_SECRET` | `.env.local` | [Official document][32] |
7579

7680
### Vercel
7781

7882
The easiest way to deploy your Next.js app is to use the [Vercel Platform][13] from the creators of Next.js.
7983

80-
Check out our [Next.js deployment documentation][31] for more details.
84+
Check out our [Next.js deployment documentation][33] for more details.
8185

8286
### Docker
8387

@@ -116,4 +120,6 @@ pnpm container
116120
[28]: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-configuration-files-for-source-map-upload
117121
[29]: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-environment-variables
118122
[30]: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#create-initialization-config-files
119-
[31]: https://nextjs.org/docs/deployment
123+
[31]: https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api#authenticating-with-a-personal-access-token
124+
[32]: https://open.larksuite.com/document/server-docs/getting-started/api-access-token/app-access-token-development-guide#95c7f5f5
125+
[33]: https://nextjs.org/docs/deployment

components/Client/Partner.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FC, ReactNode } from 'react';
33

44
import { Client } from '../../models/Client';
55
import { fileURLOf } from '../../pages/api/Lark/file/[id]';
6+
import { LarkImage } from '../LarkImage';
67

78
export interface PartnerProps extends Client {
89
className?: string;
@@ -16,12 +17,7 @@ export interface PartnerOverviewProps extends Record<'name' | 'logo' | 'address'
1617

1718
export const Partner: FC<PartnerProps> = ({ className = '', name, image, summary, address }) => (
1819
<div className={`relative flex flex-col items-center justify-center gap-4 ${className}`}>
19-
<img
20-
className="h-20 object-fill"
21-
loading="lazy"
22-
src={fileURLOf(String(image))}
23-
alt={String(name)}
24-
/>
20+
<LarkImage className="h-20 object-fill" src={fileURLOf(String(image))} alt={String(name)} />
2521
<h3>
2622
<a className="stretched-link" target="_blank" href={String(address)} rel="noreferrer">
2723
{String(name)}

components/LarkImage.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { TableCellValue } from 'mobx-lark';
22
import { ImageProps } from 'next/image';
33
import { FC } from 'react';
44

5-
import { blobURLOf } from '../models/Base';
65
import { DefaultImage, fileURLOf } from '../pages/api/Lark/file/[id]';
76

87
export interface LarkImageProps extends Omit<ImageProps, 'src'> {
@@ -13,7 +12,7 @@ export const LarkImage: FC<LarkImageProps> = ({ src = DefaultImage, alt, ...prop
1312
<img
1413
loading="lazy"
1514
{...props}
16-
src={blobURLOf(src)}
15+
src={fileURLOf(src, true)}
1716
alt={alt}
1817
onError={({ currentTarget: image }) => {
1918
const path = fileURLOf(src),

components/NotFoundCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const NotFoundCard: FC<ErrorProps> = ({ title }) =>
1010
i18n.currentLanguage.startsWith('zh') ? (
1111
<script
1212
src="//cdn.dnpw.org/404/v1.min.js"
13-
// @ts-ignore
13+
// @ts-expect-error https://www.dnpw.org/cn/pa-notfound.html
1414
jumptarget="/"
1515
jumptime="-1"
1616
error={title}

components/Section.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Button } from '@mui/material';
2+
import { observer } from 'mobx-react';
3+
import { FC, PropsWithChildren } from 'react';
4+
5+
import { t } from '../models/Translation';
6+
7+
export type SectionProps = PropsWithChildren<Partial<Record<'id' | 'title' | 'link', string>>>;
8+
9+
export const Section: FC<SectionProps> = observer(({ id, title, children, link }) => (
10+
<section>
11+
<h2 className="my-5 text-center" id={id}>
12+
{title}
13+
</h2>
14+
15+
{children}
16+
17+
{link && (
18+
<footer className="mt-5 text-center">
19+
<Button variant="outlined" size="small" href={link}>
20+
{t('load_more')}
21+
</Button>
22+
</footer>
23+
)}
24+
</section>
25+
));

eslint.config.mjs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// @ts-check
2+
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
3+
import { FlatCompat } from '@eslint/eslintrc';
4+
import eslint from '@eslint/js';
5+
import eslintConfigPrettier from 'eslint-config-prettier';
6+
import reactPlugin from 'eslint-plugin-react';
7+
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
8+
import globals from 'globals';
9+
import tsEslint from 'typescript-eslint';
10+
import { fileURLToPath } from 'url';
11+
12+
const tsconfigRootDir = fileURLToPath(new URL('.', import.meta.url)),
13+
flatCompat = new FlatCompat();
14+
15+
export default tsEslint.config(
16+
// register all of the plugins up-front
17+
{
18+
plugins: {
19+
'@typescript-eslint': tsEslint.plugin,
20+
// @ts-expect-error https://github.com/jsx-eslint/eslint-plugin-react/issues/3699
21+
react: fixupPluginRules(reactPlugin),
22+
'simple-import-sort': simpleImportSortPlugin
23+
}
24+
},
25+
{
26+
// config with just ignores is the replacement for `.eslintignore`
27+
ignores: ['**/node_modules/**', '**/public/**', '**/.next/**']
28+
},
29+
30+
// extends ...
31+
eslint.configs.recommended,
32+
...tsEslint.configs.recommended,
33+
...fixupConfigRules(flatCompat.extends('plugin:@next/next/core-web-vitals')),
34+
35+
// base config
36+
{
37+
languageOptions: {
38+
globals: { ...globals.es2020, ...globals.browser, ...globals.node },
39+
parserOptions: {
40+
projectService: true,
41+
tsconfigRootDir,
42+
warnOnUnsupportedTypeScriptVersion: false
43+
}
44+
},
45+
rules: {
46+
'no-empty-pattern': 'warn',
47+
'simple-import-sort/exports': 'error',
48+
'simple-import-sort/imports': 'error',
49+
'@typescript-eslint/no-unused-vars': 'warn',
50+
'@typescript-eslint/no-explicit-any': 'warn',
51+
'@typescript-eslint/no-empty-object-type': 'off',
52+
'@typescript-eslint/no-unsafe-declaration-merging': 'warn',
53+
'react/jsx-no-target-blank': 'warn',
54+
'react/jsx-sort-props': [
55+
'error',
56+
{
57+
reservedFirst: true,
58+
callbacksLast: true,
59+
noSortAlphabetically: true
60+
}
61+
],
62+
'@next/next/no-sync-scripts': 'warn'
63+
}
64+
},
65+
{
66+
files: ['**/*.js'],
67+
extends: [tsEslint.configs.disableTypeChecked],
68+
rules: {
69+
// turn off other type-aware rules
70+
'@typescript-eslint/internal/no-poorly-typed-ts-props': 'off',
71+
72+
// turn off rules that don't apply to JS code
73+
'@typescript-eslint/explicit-function-return-type': 'off'
74+
}
75+
},
76+
eslintConfigPrettier
77+
);

models/Base.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'core-js/full/array/from-async';
2+
13
import { HTTPClient } from 'koajax';
24
import { TableCellValue } from 'mobx-lark';
35

models/Translation.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ export const i18n = new TranslationModel({
77
'zh-TW': () => import('../translation/zh-TW'),
88
'zh-HK': () => import('../translation/zh-TW'),
99
'zh-MO': () => import('../translation/zh-TW'),
10-
'en-US': () => import('../translation/en-US'),
10+
'en-US': () => import('../translation/en-US')
1111
});
1212

13-
export const LanguageName: Partial<
14-
Record<(typeof i18n)['currentLanguage'], string>
15-
> = {
13+
export const { t } = i18n;
14+
15+
export const LanguageName: Partial<Record<(typeof i18n)['currentLanguage'], string>> = {
1616
'zh-CN': '简体中文',
1717
'zh-TW': '繁體中文',
18-
'en-US': 'English',
18+
'en-US': 'English'
1919
};

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@idea2app/ows",
3-
"version": "2.0.0-rc",
3+
"version": "2.0.0",
44
"description": "React project scaffold based on TypeScript, Next.js, Bootstrap & Workbox.",
55
"private": true,
66
"dependencies": {
@@ -12,6 +12,7 @@
1212
"file-type": "^19.6.0",
1313
"koajax": "^3.0.2",
1414
"lodash": "^4.17.21",
15+
"mime": "^4.0.4",
1516
"mobx": "^6.13.4",
1617
"mobx-github": "^0.3.4",
1718
"mobx-i18n": "^0.6.0",
@@ -37,6 +38,7 @@
3738
"@next/eslint-plugin-next": "^14.2.15",
3839
"@softonus/prettier-plugin-duplicate-remover": "^1.0.1",
3940
"@types/eslint-config-prettier": "^6.11.3",
41+
"@types/eslint__eslintrc": "^2.1.2",
4042
"@types/eslint__js": "^8.42.3",
4143
"@types/lodash": "^4.17.10",
4244
"@types/next-pwa": "^5.6.9",
@@ -79,16 +81,15 @@
7981
"printWidth": 100
8082
},
8183
"lint-staged": {
82-
"*.{html,md,less,json,yml,js,mjs,ts,tsx}": "prettier --write",
83-
"*.{js,mjs,ts,tsx}": "eslint --fix"
84+
"*.{html,md,less,json,yml,js,mjs,ts,tsx}": "prettier --write"
8485
},
8586
"scripts": {
86-
"prepare": "husky",
87+
"prepare": "husky && touch .env.local",
8788
"dev": "next dev",
8889
"build": "next build",
8990
"export": "next build && next export",
9091
"start": "next start",
91-
"lint": "next lint",
92+
"lint": "next lint --fix && git add .",
9293
"test": "lint-staged && npm run lint && tsc --noEmit",
9394
"pack-image": "docker build -t idea2app/web-server .",
9495
"container": "docker rm -f web-server && docker run --name web-server -p 3000:3000 -d idea2app/web-server"

pages/api/GitHub/core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ export const proxyGithub = <T>(dataFilter?: (path: string, data: T) => T) =>
1010
const path = url!.slice(`/api/GitHub/`.length);
1111

1212
const { status, body: data } = await githubClient.request<T>({
13-
// @ts-ignore
13+
// @ts-expect-error KoAJAX type compatibility
1414
method,
1515
path,
16-
// @ts-ignore
16+
// @ts-expect-error KoAJAX type compatibility
1717
headers,
1818
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1919
body: body || undefined

pages/api/Lark/core.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LarkApp, LarkData } from 'mobx-lark';
44
import { safeAPI } from '../core';
55

66
export const lark = new LarkApp({
7+
host: process.env.LARK_API_HOST,
78
id: process.env.LARK_APP_ID!,
89
secret: process.env.LARK_APP_SECRET!
910
});
@@ -17,10 +18,10 @@ export const proxyLark = <T extends LarkData>(dataFilter?: (path: string, data:
1718
const path = url!.slice(`/api/Lark/`.length);
1819

1920
const { status, body: data } = await lark.client.request<T>({
20-
// @ts-ignore
21+
// @ts-expect-error KoAJAX type compatibility
2122
method,
2223
path,
23-
// @ts-ignore
24+
// @ts-expect-error KoAJAX type compatibility
2425
headers,
2526

2627
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment

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

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,49 @@
11
import { fileTypeFromBuffer } from 'file-type';
2+
import MIME from 'mime';
23
import { TableCellMedia, TableCellValue } from 'mobx-lark';
4+
import { parse } from 'path';
35

46
import { safeAPI } from '../../core';
57
import { lark } from '../core';
68

79
export const DefaultImage = '/idea2app.svg';
810

9-
export const fileURLOf = (field: TableCellValue) =>
10-
field instanceof Array
11-
? field[0]
12-
? `/api/Lark/file/${(field[0] as TableCellMedia).file_token}`
13-
: String(field)
14-
: String(field);
11+
export function fileURLOf(field: TableCellValue, cache = false) {
12+
if (!(field instanceof Array) || !field[0]) return field + '';
1513

16-
export default safeAPI(async (req, res) => {
17-
switch (req.method) {
14+
const { file_token, type } = field[0] as TableCellMedia;
15+
16+
let URI = `/api/Lark/file/${file_token}`;
17+
18+
if (cache) URI += '.' + MIME.getExtension(type);
19+
20+
return URI;
21+
}
22+
23+
export const CACHE_HOST = process.env.NEXT_PUBLIC_CACHE_HOST!;
24+
25+
export default safeAPI(async ({ method, url, query, headers }, res) => {
26+
const { ext } = parse(url!);
27+
28+
if (ext)
29+
return void res.redirect(
30+
new URL(new URL(url!, `http://${headers.host}`).pathname, CACHE_HOST) + ''
31+
);
32+
switch (method) {
33+
case 'HEAD':
1834
case 'GET': {
19-
const { id } = req.query;
35+
const { id } = query;
2036

2137
await lark.getAccessToken();
2238

2339
const file = await lark.downloadFile(id as string);
2440

25-
const buffer = Buffer.alloc(file.byteLength),
26-
view = new Uint8Array(file);
27-
28-
for (let i = 0; i < buffer.length; i++) buffer[i] = view[i];
29-
30-
const { mime } = (await fileTypeFromBuffer(buffer)) ?? {};
41+
const { mime } = (await fileTypeFromBuffer(file)) || {};
3142

3243
res.setHeader('Content-Type', mime as string);
33-
res.send(buffer);
44+
45+
return void (method === 'GET' ? res.send(Buffer.from(file)) : res.end());
3446
}
3547
}
48+
res.status(405).end();
3649
});

0 commit comments

Comments
 (0)