From 3b5b6d44eba0c1861d6cf7235fbd5348ce49f941 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Wed, 9 Jul 2025 01:58:18 +0800 Subject: [PATCH] [refactor] make Next.js i18n smoother with MobX-i18n 0.7 [refactor] rewrite Lark back-end API with Koa middlewares [add] Main Navigator & Lark Image components [migrate] upgrade ESLint configuration & Lark notification from the Upstream Scaffold [optimize] upgrade to PNPM 10 & other latest Upstream packages --- .env | 3 + .github/scripts/deno.json | 3 + .github/scripts/transform-message.ts | 196 + .github/workflows/Lark-notification.yml | 366 +- .github/workflows/main.yml | 2 +- components/LarkImage.tsx | 36 + components/{ => Layout}/PageHead.tsx | 11 +- components/Navigator/LanguageMenu.tsx | 10 +- components/Navigator/MainNavigator.tsx | 64 + components/Navigator/SearchBar.tsx | 40 +- components/PageContent/index.tsx | 11 +- eslint.config.mjs | 76 - eslint.config.ts | 132 + models/Base.ts | 25 +- models/Translation.ts | 65 +- models/configuration.ts | 23 + package.json | 92 +- pages/_app.tsx | 188 +- pages/_document.tsx | 72 +- pages/api/Lark/bitable/v1/[...slug].ts | 42 +- pages/api/Lark/core.ts | 74 +- pages/api/Lark/file/[id].ts | 49 - pages/api/Lark/file/[id]/[name].ts | 52 + pages/api/core.ts | 76 +- pages/api/hello.ts | 13 - pages/{ => article}/about.mdx | 0 pages/{ => article}/code-of-conduct.mdx | 6 +- pages/{ => article}/history.mdx | 0 pages/{ => article}/join-us.mdx | 0 .../{ => article}/open-collaborator-award.mdx | 0 pages/index.tsx | 2 +- pages/search/[model]/index.tsx | 13 +- pnpm-lock.yaml | 5253 +++++++++++------ styles/Home.module.scss | 7 +- styles/globals.css | 24 +- tsconfig.json | 1 + 36 files changed, 4313 insertions(+), 2714 deletions(-) create mode 100644 .env create mode 100644 .github/scripts/deno.json create mode 100644 .github/scripts/transform-message.ts create mode 100644 components/LarkImage.tsx rename components/{ => Layout}/PageHead.tsx (61%) create mode 100644 components/Navigator/MainNavigator.tsx delete mode 100644 eslint.config.mjs create mode 100644 eslint.config.ts create mode 100644 models/configuration.ts delete mode 100644 pages/api/Lark/file/[id].ts create mode 100644 pages/api/Lark/file/[id]/[name].ts delete mode 100644 pages/api/hello.ts rename pages/{ => article}/about.mdx (100%) rename pages/{ => article}/code-of-conduct.mdx (92%) rename pages/{ => article}/history.mdx (100%) rename pages/{ => article}/join-us.mdx (100%) rename pages/{ => article}/open-collaborator-award.mdx (100%) diff --git a/.env b/.env new file mode 100644 index 0000000..a4e365e --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +NEXT_PUBLIC_SITE_NAME = 开源市集 +NEXT_PUBLIC_SITE_SUMMARY = +NEXT_PUBLIC_LOGO = https://github.com/Open-Source-Bazaar.png diff --git a/.github/scripts/deno.json b/.github/scripts/deno.json new file mode 100644 index 0000000..38af402 --- /dev/null +++ b/.github/scripts/deno.json @@ -0,0 +1,3 @@ +{ + "nodeModulesDir": "none" +} diff --git a/.github/scripts/transform-message.ts b/.github/scripts/transform-message.ts new file mode 100644 index 0000000..fd0519f --- /dev/null +++ b/.github/scripts/transform-message.ts @@ -0,0 +1,196 @@ +import { components } from 'npm:@octokit/openapi-types'; +import { stdin } from 'npm:zx'; + +type GitHubSchema = components['schemas']; + +type GitHubUser = GitHubSchema['simple-user']; + +interface GitHubAction + extends Record<'event_name' | 'actor' | 'server_url' | 'repository', string> { + action?: string; + ref?: string; + ref_name?: string; + event: { + head_commit?: GitHubSchema['git-commit']; + issue?: GitHubSchema['webhook-issues-opened']['issue']; + pull_request?: GitHubSchema['pull-request']; + discussion?: GitHubSchema['discussion']; + comment?: GitHubSchema['issue-comment']; + release?: GitHubSchema['release']; + }; +} + +// Helper functions +const getActionText = (action?: string) => + action === 'closed' ? '关闭' : action?.includes('open') ? '打开' : '编辑'; + +const createLink = (href: string, text = href) => ({ tag: 'a', href, text }); + +const createText = (text: string) => ({ tag: 'text', text }); + +// create user link +const createUserLink = (user: GitHubUser) => + user ? createLink(user.html_url, user.login) : createText('无'); + +const createContentItem = ( + label: string, + value?: string | { tag: string; text: string }, +) => + [ + createText(label), + typeof value === 'string' + ? createText(value || '无') + : value || createText('无'), + ] as [object, object]; + +type EventHandler = ( + event: GitHubAction, + actionText: string, +) => { + title: string; + content: [object, object][]; +}; + +// Event handlers +const eventHandlers: Record = { + push: ({ + event: { head_commit }, + ref, + ref_name, + server_url, + repository, + actor, + }) => ({ + title: 'GitHub 代码提交', + content: [ + [createText('提交链接:'), createLink(head_commit!.url)], + [ + createText('代码分支:'), + createLink(`${server_url}/${repository}/tree/${ref_name}`, ref), + ], + [createText('提交作者:'), createLink(`${server_url}/${actor}`, actor)], + [createText('提交信息:'), createText(head_commit!.message)], + ], + }), + + issues: ({ event: { issue } }, actionText) => ({ + title: `GitHub issue ${actionText}:${issue?.title}`, + content: [ + [createText('链接:'), createLink(issue!.html_url)], + [ + createText('作者:'), + createLink(issue!.user!.html_url!, issue!.user!.login), + ], + [ + createText('指派:'), + issue?.assignee + ? createLink(issue.assignee.html_url!, issue.assignee.login) + : createText('无'), + ], + [ + createText('标签:'), + createText(issue?.labels?.map(({ name }) => name).join(', ') || '无'), + ], + [createText('里程碑:'), createText(issue?.milestone?.title || '无')], + [createText('描述:'), createText(issue?.body || '无')], + ], + }), + + pull_request: ({ event: { pull_request } }, actionText) => ({ + title: `GitHub PR ${actionText}:${pull_request?.title}`, + content: [ + [createText('链接:'), createLink(pull_request!.html_url)], + [ + createText('作者:'), + createLink(pull_request!.user.html_url, pull_request!.user.login), + ], + [ + createText('指派:'), + pull_request?.assignee + ? createLink( + pull_request.assignee.html_url, + pull_request.assignee.login, + ) + : createText('无'), + ], + [ + createText('标签:'), + createText( + pull_request?.labels?.map(({ name }) => name).join(', ') || '无', + ), + ], + [ + createText('里程碑:'), + createText(pull_request?.milestone?.title || '无'), + ], + [createText('描述:'), createText(pull_request?.body || '无')], + ], + }), + + discussion: ({ event: { discussion } }, actionText) => ({ + title: `GitHub 讨论 ${actionText}:${discussion?.title || '无'}`, + content: [ + createContentItem('链接:', discussion?.html_url), + createContentItem( + '作者:', + createUserLink(discussion!.user as GitHubUser), + ), + createContentItem('描述:', discussion?.body || '无'), + ], + }), + + issue_comment: ({ event: { comment, issue } }) => ({ + title: `GitHub issue 评论:${issue?.title || '未知 issue'}`, + content: [ + createContentItem('链接:', comment?.html_url), + createContentItem('作者:', createUserLink(comment!.user!)), + createContentItem('描述:', comment?.body || '无'), + ], + }), + + discussion_comment: ({ event: { comment, discussion } }) => ({ + title: `GitHub 讨论评论:${discussion?.title || '无'}`, + content: [ + createContentItem('链接:', comment?.html_url), + createContentItem('作者:', createUserLink(comment!.user!)), + createContentItem('描述:', comment?.body || '无'), + ], + }), + + release: ({ event: { release } }) => ({ + title: `GitHub Release 发布:${release!.name || release!.tag_name}`, + content: [ + createContentItem('链接:', release!.html_url), + createContentItem('作者:', createUserLink(release!.author)), + createContentItem('描述:', release!.body!), + ], + }), +}; + +// Main processor +const processEvent = (event: GitHubAction) => { + const { event_name, action } = event; + const actionText = getActionText(action); + const handler = eventHandlers[event_name]; + + if (!handler) throw new Error(`No handler found for event: ${event_name}`); + + try { + return handler(event, actionText); + } catch (cause) { + throw new Error( + `Error processing ${event_name} event: ${(cause as Error).message}`, + { cause }, + ); + } +}; + +// Main execution:Processing GitHub Events and Outputting Results +const event = JSON.parse((await stdin()) || '{}') as GitHubAction; +const zh_cn = processEvent(event); + +if (zh_cn) console.log(JSON.stringify({ post: { zh_cn } })); +else + throw new Error( + `Unsupported ${event.event_name} event & ${event.action} action`, + ); diff --git a/.github/workflows/Lark-notification.yml b/.github/workflows/Lark-notification.yml index 138940b..e067047 100644 --- a/.github/workflows/Lark-notification.yml +++ b/.github/workflows/Lark-notification.yml @@ -2,377 +2,41 @@ name: Lark notification # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows on: + push: issues: pull_request: discussion: issue_comment: discussion_comment: + release: + types: + - published jobs: send-Lark-message: runs-on: ubuntu-latest steps: - - name: Issue content cleaning - id: issue_content - env: - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_BODY: ${{ github.event.issue.body }} - run: | - echo "$ISSUE_TITLE" | sed 's/"/\\"/g' > issue_title.txt - { - echo 'issue_title<> $GITHUB_OUTPUT - echo "$ISSUE_BODY" | sed 's/"/\\"/g' > issue_body.txt - { - echo 'issue_body<> $GITHUB_OUTPUT - - - name: Issue opened - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened') - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub issue 打开:${{ steps.issue_content.outputs.issue_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.issue.html_url }} - href: ${{ github.event.issue.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.issue.user.login }} - href: ${{ github.event.issue.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.issue.assignee.login }}" - href: "${{ github.event.issue.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.issue.labels }} - - - tag: text - text: 里程碑:${{ github.event.issue.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.issue_content.outputs.issue_body }}" + - uses: actions/checkout@v4 - - name: Issue edited - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'issues' && (github.event.action == 'edited' || github.event.action == 'transferred' || github.event.action == 'labeled' || github.event.action == 'unlabeled' || github.event.action == 'assigned' || github.event.action == 'unassigned') + - uses: denoland/setup-deno@v2 with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub issue 编辑:${{ steps.issue_content.outputs.issue_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.issue.html_url }} - href: ${{ github.event.issue.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.issue.user.login }} - href: ${{ github.event.issue.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.issue.assignee.login }}" - href: "${{ github.event.issue.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.issue.labels }} - - - tag: text - text: 里程碑:${{ github.event.issue.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.issue_content.outputs.issue_body }}" - - - name: Issue closed - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'issues' && github.event.action == 'closed' - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub issue 关闭:${{ steps.issue_content.outputs.issue_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.issue.html_url }} - href: ${{ github.event.issue.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.issue.user.login }} - href: ${{ github.event.issue.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.issue.assignee.login }}" - href: "${{ github.event.issue.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.issue.labels }} - - - tag: text - text: 里程碑:${{ github.event.issue.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.issue_content.outputs.issue_body }}" - - - name: PR content cleaning - id: PR_content - env: - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} - run: | - echo "$PR_TITLE" | sed 's/"/\\"/g' > PR_title.txt - { - echo 'PR_title<> $GITHUB_OUTPUT - echo "$PR_BODY" | sed 's/"/\\"/g' > PR_body.txt - { - echo 'PR_body<> $GITHUB_OUTPUT - - - name: PR opened - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened') - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub PR 打开:${{ steps.PR_content.outputs.PR_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.pull_request.html_url }} - href: ${{ github.event.pull_request.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.pull_request.user.login }} - href: ${{ github.event.pull_request.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.pull_request.assignee.login }}" - href: "${{ github.event.pull_request.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.pull_request.labels }} - - - tag: text - text: 里程碑:${{ github.event.pull_request.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.PR_content.outputs.PR_body }}" - - - name: PR edited - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'pull_request' && (github.event.action == 'edited' || github.event.action == 'labeled' || github.event.action == 'unlabeled' || github.event.action == 'assigned' || github.event.action == 'unassigned') - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub PR 编辑:${{ steps.PR_content.outputs.PR_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.pull_request.html_url }} - href: ${{ github.event.pull_request.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.pull_request.user.login }} - href: ${{ github.event.pull_request.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.pull_request.assignee.login }}" - href: "${{ github.event.pull_request.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.pull_request.labels }} - - - tag: text - text: 里程碑:${{ github.event.pull_request.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.PR_content.outputs.PR_body }}" - - - name: PR closed - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'pull_request' && github.event.action == 'closed' - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub PR 关闭:${{ steps.PR_content.outputs.PR_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.pull_request.html_url }} - href: ${{ github.event.pull_request.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.pull_request.user.login }} - href: ${{ github.event.pull_request.user.html_url }} - - - tag: text - text: 指派: - - tag: a - text: "${{ github.event.pull_request.assignee.login }}" - href: "${{ github.event.pull_request.assignee.html_url }}" - - - tag: text - text: 标签:${{ github.event.pull_request.labels }} - - - tag: text - text: 里程碑:${{ github.event.pull_request.milestone.title }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.PR_content.outputs.PR_body }}" - - - name: Discussion content cleaning - id: discussion_content - env: - DISCUSSION_TITLE: ${{ github.event.discussion.title }} - DISCUSSION_BODY: ${{ github.event.discussion.body }} - run: | - echo "$DISCUSSION_TITLE" | sed 's/"/\\"/g' > discussion_title.txt - { - echo 'discussion_title<> $GITHUB_OUTPUT - echo "$DISCUSSION_BODY" | sed 's/"/\\"/g' > discussion_body.txt - { - echo 'discussion_body<> $GITHUB_OUTPUT - - - name: Discussion created - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'discussion' && github.event.action == 'created' - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub 帖子发布:${{ steps.discussion_content.outputs.discussion_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.discussion.html_url }} - href: ${{ github.event.discussion.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.discussion.user.login }} - href: ${{ github.event.discussion.user.html_url }} - - - tag: text - text: 分类:${{ github.event.discussion.category }} - - - tag: text - text: 标签:${{ github.event.discussion.labels }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.discussion_content.outputs.discussion_body }}" - - - name: Discussion edited - uses: foxundermoon/feishu-action@v2 - if: github.event_name == 'discussion' && (github.event.action == 'edited' || github.event.action == 'transferred' || github.event.action == 'category_changed' || github.event.action == 'labeled' || github.event.action == 'unlabeled') - with: - url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} - msg_type: post - content: | - post: - zh_cn: - title: "GitHub 帖子修改:${{ steps.discussion_content.outputs.discussion_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.discussion.html_url }} - href: ${{ github.event.discussion.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.discussion.user.login }} - href: ${{ github.event.discussion.user.html_url }} - - - tag: text - text: 分类:${{ github.event.discussion.category }} - - - tag: text - text: 标签:${{ github.event.discussion.labels }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.discussion_content.outputs.discussion_body }}" + deno-version: v2.x - - name: Comment body cleaning - id: comment_body - env: - COMMENT_BODY: ${{ github.event.comment.body }} + - name: Event Message serialization + id: message run: | - echo "$COMMENT_BODY" | sed 's/"/\\"/g' > comment_body.txt + YAML=$(echo '${{ toJSON(github) }}' | deno --allow-all .github/scripts/transform-message.ts) { - echo 'comment_body<> $GITHUB_OUTPUT - - name: Issue/Discussion commented + - name: Send message to Lark + if: ${{ contains(steps.message.outputs.content, ':') }} uses: foxundermoon/feishu-action@v2 - if: (github.event_name == 'issue_comment' || github.event_name == 'discussion_comment') && (github.event.action == 'created' || github.event.action == 'edited') with: url: ${{ secrets.LARK_CHATBOT_HOOK_URL }} msg_type: post content: | - post: - zh_cn: - title: "GitHub 帖子评论:${{ steps.issue_content.outputs.issue_title }}" - content: - - - tag: text - text: 链接: - - tag: a - text: ${{ github.event.comment.html_url }} - href: ${{ github.event.comment.html_url }} - - - tag: text - text: 作者: - - tag: a - text: ${{ github.event.comment.user.login }} - href: ${{ github.event.comment.user.html_url }} - - - tag: text - text: 描述: - - tag: text - text: "${{ steps.comment_body.outputs.comment_body }}" + ${{ steps.message.outputs.content }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ac1a3a..172c8ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: CI & CD on: push: branches: - - '*' + - '**' jobs: Build-and-Deploy: env: diff --git a/components/LarkImage.tsx b/components/LarkImage.tsx new file mode 100644 index 0000000..e0cfbac --- /dev/null +++ b/components/LarkImage.tsx @@ -0,0 +1,36 @@ +import { TableCellValue } from 'mobx-lark'; +import { FC } from 'react'; +import { Image, ImageProps } from 'react-bootstrap'; + +import { fileURLOf } from '../models/Base'; +import { DefaultImage } from '../models/configuration'; + +export interface LarkImageProps extends Omit { + src?: TableCellValue; +} + +export const LarkImage: FC = ({ + src = DefaultImage, + alt, + ...props +}) => ( + {alt} { + const path = fileURLOf(src), + errorURL = decodeURI(image.src); + + if (!path) return; + + if (errorURL.endsWith(path)) { + if (!alt) image.src = DefaultImage; + } else if (!errorURL.endsWith(DefaultImage)) { + image.src = path; + } + }} + /> +); diff --git a/components/PageHead.tsx b/components/Layout/PageHead.tsx similarity index 61% rename from components/PageHead.tsx rename to components/Layout/PageHead.tsx index 11a7c18..896b0af 100644 --- a/components/PageHead.tsx +++ b/components/Layout/PageHead.tsx @@ -1,5 +1,7 @@ import Head from 'next/head'; -import { FC, PropsWithChildren } from 'react'; +import type { FC, PropsWithChildren } from 'react'; + +import { Name, Summary } from '../../models/configuration'; export type PageHeadProps = PropsWithChildren<{ title?: string; @@ -8,14 +10,11 @@ export type PageHeadProps = PropsWithChildren<{ export const PageHead: FC = ({ title, - description = '开源市集', + description = Summary, children, }) => ( - - {title} - {title && ' - '}开源市集 - + {`${title ? `${title} - ` : ''}${Name}`} {description && } diff --git a/components/Navigator/LanguageMenu.tsx b/components/Navigator/LanguageMenu.tsx index 225704a..74ee564 100644 --- a/components/Navigator/LanguageMenu.tsx +++ b/components/Navigator/LanguageMenu.tsx @@ -1,16 +1,16 @@ import { Option, Select } from 'idea-react'; import { observer } from 'mobx-react'; -import { FC } from 'react'; +import { FC, useContext } from 'react'; -import { i18n, LanguageName } from '../../models/Translation'; +import { I18nContext, LanguageName } from '../../models/Translation'; const LanguageMenu: FC = observer(() => { - const { currentLanguage } = i18n; + const i18n = useContext(I18nContext); return (