-
Notifications
You must be signed in to change notification settings - Fork 19
Upgrade Next.js patch, migrate PWA integration to Serwist, and adopt shared ESLint config #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
19396d9
a950fa8
f847dd7
2e98592
f12c4fb
6bc968c
29dcf99
b8c0e1b
d72d064
187c4bf
9919d83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| ); | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里的类型已经都兼容了,既不需要类型注释,也不需要断言! |
||
| }; | ||
|
|
||
| router.head('/:id/:name', safeAPI, downloader).get('/:id/:name', safeAPI, downloader); | ||
|
|
||
| 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 修改时如遇以下情况导致的构建失败,要先把代码提交上来:
你改的这一类代码,在运行时测试都是完全通过的,不需要你这样画蛇添足,全部回退! |
||
|
|
||
| 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<pwa-install />