Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 5 additions & 101 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,116 +1,20 @@
import cspellPlugin from '@cspell/eslint-plugin';
import eslint from '@eslint/js';
import nextPlugin from '@next/eslint-plugin-next';
import stylistic from '@stylistic/eslint-plugin';
import { defineConfig } from 'eslint/config';
import eslintConfigPrettier from 'eslint-config-prettier';
import react from 'eslint-plugin-react';
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
import globals from 'globals';
import tsEslint from 'typescript-eslint';
import { fileURLToPath } from 'url';
import nextJS from 'eslint-idea2app-next-js';

/**
* @see{@link https://github.com/typescript-eslint/typescript-eslint/blob/main/eslint.config.mjs}
* @see{@link https://github.com/vercel/next.js/issues/71763#issuecomment-2476838298}
*/

const tsconfigRootDir = fileURLToPath(new URL('.', import.meta.url));

export default defineConfig(
// register all of the plugins up-front
{
plugins: {
'@cspell': cspellPlugin,
'@stylistic': stylistic,
'simple-import-sort': simpleImportSortPlugin,
'@typescript-eslint': tsEslint.plugin,
react,
'@next/next': nextPlugin,
},
},
export default [
...nextJS,
{
// config with just ignores is the replacement for `.eslintignore`
ignores: ['**/node_modules/**', '**/public/**', '**/.next/**', '.github/scripts/**'],
},

// extends ...
eslint.configs.recommended,
...tsEslint.configs.recommended,

// base config
{
languageOptions: {
globals: { ...globals.es2020, ...globals.browser, ...globals.node },
parserOptions: {
projectService: true,
tsconfigRootDir,
warnOnUnsupportedTypeScriptVersion: false,
},
},
rules: {
// spellchecker
'@cspell/spellchecker': [
'warn',
{
cspell: {
language: 'en',
dictionaries: ['typescript', 'node', 'html', 'css', 'bash', 'npm', 'pnpm'],
words: ['hackathon'],
words: ['aliyun', 'datav', 'Giscus', 'hackathon', 'Serwist'],
},
},
],
// stylistic
'@stylistic/padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: '*', next: 'return' },
{ blankLine: 'always', prev: 'directive', next: '*' },
{ blankLine: 'any', prev: 'directive', next: 'directive' },
{
blankLine: 'always',
prev: '*',
next: ['enum', 'interface', 'type'],
},
],
'arrow-body-style': ['error', 'as-needed'],
'no-empty-pattern': 'warn',
'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
'no-restricted-syntax': [
'error',
{
selector: "TSPropertySignature[key.name='children']",
message: 'Please use PropsWithChildren<T> instead of defining children manually',
},
],
'consistent-return': 'warn',
'prefer-destructuring': ['error', { object: true, array: true }],
// simple-import-sort
'simple-import-sort/exports': 'error',
'simple-import-sort/imports': 'error',
// TypeScript
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unsafe-declaration-merging': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
// React
'react/no-unescaped-entities': 'off',
'react/self-closing-comp': ['error', { component: true, html: true }],
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
'react/jsx-no-target-blank': 'warn',
'react/jsx-sort-props': [
'error',
{
reservedFirst: true,
callbacksLast: true,
noSortAlphabetically: true,
},
],
// Next.js
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
'@next/next/no-sync-scripts': 'warn',
},
},
eslintConfigPrettier,
);
];
1 change: 1 addition & 0 deletions models/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const LarkAppMeta = {
id: process.env.NEXT_PUBLIC_LARK_APP_ID!,
secret: process.env.LARK_APP_SECRET!,
};
export const hasLarkServerAccess = Boolean(process.env.LARK_APP_SECRET);
const { hostname, pathname } = new URL(process.env.NEXT_PUBLIC_LARK_WIKI_URL!);

export const LarkWikiDomain = hostname;
Expand Down
11 changes: 5 additions & 6 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import setMDX from '@next/mdx';
import withSerwistInit from '@serwist/next';
import { NextConfig } from 'next';
import setPWA from 'next-pwa';
// @ts-expect-error no official types
import withLess from 'next-with-less';
import remarkFrontmatter from 'remark-frontmatter';
Expand All @@ -17,10 +17,9 @@ const withMDX = setMDX({
},
extension: /\.mdx?$/,
}),
withPWA = setPWA({
dest: 'public',
register: true,
skipWaiting: true,
withSerwist = withSerwistInit({
swSrc: 'service-worker.ts',
swDest: 'public/sw.js',
disable: isDev,
});

Expand Down Expand Up @@ -51,7 +50,7 @@ const redirects: NextConfig['redirects'] = async () =>
},
]);

export default withPWA(
export default withSerwist(
withLess(
withMDX({
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
Expand Down
26 changes: 9 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@koa/router": "^15.7.0",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/mdx": "16.3.0",
"@next/mdx": "16.3.5",
"core-js": "^3.49.0",
"echarts-jsx": "^0.6.0",
"file-type": "^22.0.1",
Expand All @@ -38,9 +38,9 @@
"mobx-restful": "^2.1.4",
"mobx-restful-table": "^2.6.6",
"mobx-strapi": "^0.8.1",
"next": "16.3.0",
"next-pwa": "^5.6.0",
"next": "16.3.5",
"next-ssr-middleware": "^1.1.1",
"@serwist/next": "^9.5.12",
"nodemailer": "^9.0.4",
"open-react-map": "^0.9.1",
"react": "^19.2.8",
Expand All @@ -49,30 +49,23 @@
"react-typed-component": "^1.0.6",
"remark-frontmatter": "^5.0.0",
"remark-mdx-frontmatter": "^5.2.0",
"serwist": "^9.5.12",
"web-utility": "^4.7.2",
"yaml": "^2.9.0"
},
"devDependencies": {
"@cspell/eslint-plugin": "^10.0.1",
"@eslint/js": "^10.0.1",
"@next/eslint-plugin-next": "16.3.0",
"@next/eslint-plugin-next": "16.3.5",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^5.10.0",
"@types/eslint-config-prettier": "^6.11.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/koa": "^3.0.3",
"@types/next-pwa": "^5.6.9",
"@types/node": "^24.13.3",
"@types/nodemailer": "^8.0.1",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"cross-env": "^10.1.0",
"eslint": "^10.8.0",
"eslint-config-next": "16.3.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^14.0.0",
"globals": "^17.9.0",
"eslint": "^10.10.0",
"eslint-config-next": "16.3.5",
"eslint-idea2app-next-js": "^0.1.1",
"husky": "^9.1.7",
"jiti": "^2.7.0",
"less": "^4.8.1",
Expand All @@ -81,8 +74,7 @@
"next-with-less": "^3.0.1",
"prettier": "^3.9.6",
"prettier-plugin-css-order": "^2.2.0",
"typescript": "~6.0.3",
"typescript-eslint": "^8.66.0"
"typescript": "~6.0.3"
},
"resolutions": {
"next": "$next"
Expand Down
12 changes: 11 additions & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '../styles/globals.css';

import { SerwistProvider } from '@serwist/next/react';
import { HTTPError } from 'koajax';
import { configure } from 'mobx';
import { enableStaticRendering, observer } from 'mobx-react';
Expand Down Expand Up @@ -70,6 +71,7 @@ export default class CustomApp extends App<I18nProps> {
render() {
const { Component, pageProps, router } = this.props,
{ t } = this.i18nStore;
const isDev = process.env.NODE_ENV === 'development';
const { asPath } = router;
const isArticlePage = asPath.startsWith('/article/') || asPath.startsWith('/policy/'),
isActivityPage = asPath.startsWith('/hackathon'),
Expand All @@ -91,7 +93,7 @@ export default class CustomApp extends App<I18nProps> {
],
};

return (
const frame = (
<I18nContext.Provider value={this.i18nStore}>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand All @@ -109,5 +111,13 @@ export default class CustomApp extends App<I18nProps> {
<Footer {...footerProps} />
</I18nContext.Provider>
);

return isServer() ? (
frame
) : (
<SerwistProvider swUrl="/sw.js" disable={isDev}>
Comment on lines +115 to +118

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return isServer() ? (
frame
) : (
<SerwistProvider swUrl="/sw.js" disable={isDev}>
return <SerwistProvider swUrl="/sw.js">
  1. 插件配置处已经做了控制,此处就不做了。
  2. 还缺了 <pwa-install />
  3. 这个插件能自己处理好服务端渲染,不需要手工切换!

{frame}
</SerwistProvider>
);
}
}
4 changes: 2 additions & 2 deletions pages/api/Lark/file/[id]/[name].ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ const downloader: Middleware = async context => {
context.set('Content-Disposition', headers.get('Content-Disposition') || '');
context.set('Content-Length', headers.get('Content-Length') || '');

// @ts-expect-error Web type compatibility
context.body = method === 'GET' ? Readable.fromWeb(stream2) : '';
context.body =
method === 'GET' ? Readable.fromWeb(stream2 as globalThis.ReadableStream<Uint8Array>) : '';
Comment on lines -47 to +48

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的类型已经都兼容了,既不需要类型注释,也不需要断言!

};

router.head('/:id/:name', safeAPI, downloader).get('/:id/:name', safeAPI, downloader);
Expand Down
66 changes: 61 additions & 5 deletions pages/award/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
import { GetStaticProps } from 'next';
import { FC } from 'react';
import { FC, useContext } from 'react';
import { Card, Col, Container, Row } from 'react-bootstrap';
import { Minute, Second } from 'web-utility';

import { PageHead } from '../../components/Layout/PageHead';
import { Award, AwardModel } from '../../models/Award';
import { hasLarkServerAccess } from '../../models/configuration';
import { I18nContext } from '../../models/Translation';
import { lark } from '../api/Lark/core';

export const getStaticProps: GetStaticProps<{ awards: Award[] }> = async () => {
const formatAwardField = (value?: Award[keyof Award]) =>
typeof value === 'string' || typeof value === 'number'
? `${value}`
: Array.isArray(value)
? value.filter((item): item is string => typeof item === 'string').join(' ')
: '';

interface AwardPageProps {
awards: Award[];
awardsUnavailable: boolean;
}

export const getStaticProps: GetStaticProps<AwardPageProps> = async () => {
if (!hasLarkServerAccess)
return {
props: { awards: [], awardsUnavailable: true },
revalidate: Minute / Second,
};
Comment on lines +12 to +29

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修改时如遇以下情况导致的构建失败,要先把代码提交上来:

  1. 环境变量缺失时尝试伪造,不成功就忽略报错
  2. 类型兼容性报错时尝试断言,不成功就先用类型注释忽略报错

你改的这一类代码,在运行时测试都是完全通过的,不需要你这样画蛇添足,全部回退!


await lark.getAccessToken();

const store = new AwardModel();
store.client = lark.client;

const awards = await store.getAll();

return { props: { awards } };
return { props: { awards, awardsUnavailable: false }, revalidate: Minute / Second };
};

const AwardPage: FC<{ awards: Award[] }> = ({ awards }) => {
return <></>;
Comment on lines -18 to -19

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此处是留给别的 PR 的占位符,不要画蛇添足给我加东西!我布置的任务里没有“把这个页面写完”!

const AwardPage: FC<AwardPageProps> = ({ awards, awardsUnavailable }) => {
const { t } = useContext(I18nContext);

return (
<Container className="py-4">
<PageHead title={t('open_collaborator_award')} />
<h1 className="mb-4 text-center text-md-start">{t('open_collaborator_award')}</h1>

{awards.length ? (
<Row as="ul" className="list-unstyled g-4" xs={1} md={2}>
{awards.map(({ awardName, nomineeName, nomineeDesc, reason }, index) => (
<Col
key={`${formatAwardField(awardName) || formatAwardField(nomineeName) || 'award'}-${index}`}
as="li"
>
<Card body className="h-100 shadow-sm">
<h2 className="h5">
{formatAwardField(awardName) ||
formatAwardField(nomineeName) ||
t('open_collaborator_award')}
</h2>
{nomineeName && <p className="mb-2 fw-bold">{formatAwardField(nomineeName)}</p>}
{nomineeDesc && <p>{formatAwardField(nomineeDesc)}</p>}
{reason && <p className="mb-0">{formatAwardField(reason)}</p>}
</Card>
</Col>
))}
</Row>
) : awardsUnavailable ? (
<p className="py-4 text-center text-muted">{t('remote_content_unavailable')}</p>
) : (
<p className="py-4 text-center text-muted">{t('no_awards_yet')}</p>
)}
</Container>
);
};

export default AwardPage;
Loading
Loading