Skip to content

Commit 9c63120

Browse files
committed
Merge remote-tracking branch 'origin' into issue/card/refine
2 parents 6114780 + 62f89b8 commit 9c63120

File tree

33 files changed

+2818
-1692
lines changed

33 files changed

+2818
-1692
lines changed

.github/scripts/deno.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"nodeModulesDir": "none"
3+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { components } from 'npm:@octokit/openapi-types';
2+
import { stdin } from 'npm:zx';
3+
4+
type GitHubSchema = components['schemas'];
5+
6+
type GitHubUser = GitHubSchema['simple-user'];
7+
8+
interface GitHubAction
9+
extends Record<'event_name' | 'actor' | 'server_url' | 'repository', string> {
10+
action?: string;
11+
ref?: string;
12+
ref_name?: string;
13+
event: {
14+
head_commit?: GitHubSchema['git-commit'];
15+
issue?: GitHubSchema['webhook-issues-opened']['issue'];
16+
pull_request?: GitHubSchema['pull-request'];
17+
discussion?: GitHubSchema['discussion'];
18+
comment?: GitHubSchema['issue-comment'];
19+
release?: GitHubSchema['release'];
20+
};
21+
}
22+
23+
// Helper functions
24+
const getActionText = (action?: string) =>
25+
action === 'closed' ? '关闭' : action?.includes('open') ? '打开' : '编辑';
26+
27+
const createLink = (href: string, text = href) => ({ tag: 'a', href, text });
28+
29+
const createText = (text: string) => ({ tag: 'text', text });
30+
31+
// create user link
32+
const createUserLink = (user: GitHubUser) =>
33+
user ? createLink(user.html_url, user.login) : createText('无');
34+
35+
const createContentItem = (label: string, value?: string | { tag: string; text: string }) =>
36+
[
37+
createText(label),
38+
typeof value === 'string' ? createText(value || '无') : value || createText('无'),
39+
] as [object, object];
40+
41+
type EventHandler = (
42+
event: GitHubAction,
43+
actionText: string,
44+
) => {
45+
title: string;
46+
content: [object, object][];
47+
};
48+
49+
// Event handlers
50+
const eventHandlers: Record<string, EventHandler> = {
51+
push: ({ event: { head_commit }, ref, ref_name, server_url, repository, actor }) => ({
52+
title: 'GitHub 代码提交',
53+
content: [
54+
[createText('提交链接:'), createLink(head_commit!.url)],
55+
[createText('代码分支:'), createLink(`${server_url}/${repository}/tree/${ref_name}`, ref)],
56+
[createText('提交作者:'), createLink(`${server_url}/${actor}`, actor)],
57+
[createText('提交信息:'), createText(head_commit!.message)],
58+
],
59+
}),
60+
61+
issues: ({ event: { issue } }, actionText) => ({
62+
title: `GitHub issue ${actionText}${issue?.title}`,
63+
content: [
64+
[createText('链接:'), createLink(issue!.html_url)],
65+
[createText('作者:'), createLink(issue!.user!.html_url!, issue!.user!.login)],
66+
[
67+
createText('指派:'),
68+
issue?.assignee
69+
? createLink(issue.assignee.html_url!, issue.assignee.login)
70+
: createText('无'),
71+
],
72+
[createText('标签:'), createText(issue?.labels?.map(({ name }) => name).join(', ') || '无')],
73+
[createText('里程碑:'), createText(issue?.milestone?.title || '无')],
74+
[createText('描述:'), createText(issue?.body || '无')],
75+
],
76+
}),
77+
78+
pull_request: ({ event: { pull_request } }, actionText) => ({
79+
title: `GitHub PR ${actionText}${pull_request?.title}`,
80+
content: [
81+
[createText('链接:'), createLink(pull_request!.html_url)],
82+
[createText('作者:'), createLink(pull_request!.user.html_url, pull_request!.user.login)],
83+
[
84+
createText('指派:'),
85+
pull_request?.assignee
86+
? createLink(pull_request.assignee.html_url, pull_request.assignee.login)
87+
: createText('无'),
88+
],
89+
[
90+
createText('标签:'),
91+
createText(pull_request?.labels?.map(({ name }) => name).join(', ') || '无'),
92+
],
93+
[createText('里程碑:'), createText(pull_request?.milestone?.title || '无')],
94+
[createText('描述:'), createText(pull_request?.body || '无')],
95+
],
96+
}),
97+
98+
discussion: ({ event: { discussion } }, actionText) => ({
99+
title: `GitHub 讨论 ${actionText}${discussion?.title || '无'}`,
100+
content: [
101+
createContentItem('链接:', discussion?.html_url),
102+
createContentItem('作者:', createUserLink(discussion!.user as GitHubUser)),
103+
createContentItem('描述:', discussion?.body || '无'),
104+
],
105+
}),
106+
107+
issue_comment: ({ event: { comment, issue } }) => ({
108+
title: `GitHub issue 评论:${issue?.title || '未知 issue'}`,
109+
content: [
110+
createContentItem('链接:', comment?.html_url),
111+
createContentItem('作者:', createUserLink(comment!.user!)),
112+
createContentItem('描述:', comment?.body || '无'),
113+
],
114+
}),
115+
116+
discussion_comment: ({ event: { comment, discussion } }) => ({
117+
title: `GitHub 讨论评论:${discussion?.title || '无'}`,
118+
content: [
119+
createContentItem('链接:', comment?.html_url),
120+
createContentItem('作者:', createUserLink(comment!.user!)),
121+
createContentItem('描述:', comment?.body || '无'),
122+
],
123+
}),
124+
125+
release: ({ event: { release } }) => ({
126+
title: `GitHub Release 发布:${release!.name || release!.tag_name}`,
127+
content: [
128+
createContentItem('链接:', release!.html_url),
129+
createContentItem('作者:', createUserLink(release!.author)),
130+
createContentItem('描述:', release!.body!),
131+
],
132+
}),
133+
};
134+
135+
// Main processor
136+
const processEvent = (event: GitHubAction) => {
137+
const { event_name, action } = event;
138+
const actionText = getActionText(action);
139+
const handler = eventHandlers[event_name];
140+
141+
if (!handler) throw new Error(`No handler found for event: ${event_name}`);
142+
143+
try {
144+
return handler(event, actionText);
145+
} catch (cause) {
146+
throw new Error(`Error processing ${event_name} event: ${(cause as Error).message}`, { cause });
147+
}
148+
};
149+
150+
// Main execution:Processing GitHub Events and Outputting Results
151+
const event = JSON.parse((await stdin()) || '{}') as GitHubAction;
152+
const zh_cn = processEvent(event);
153+
154+
if (zh_cn) console.log(JSON.stringify({ post: { zh_cn } }));
155+
else throw new Error(`Unsupported ${event.event_name} event & ${event.action} action`);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Lark notification
2+
3+
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
4+
on:
5+
push:
6+
issues:
7+
pull_request:
8+
discussion:
9+
issue_comment:
10+
discussion_comment:
11+
release:
12+
types:
13+
- published
14+
15+
jobs:
16+
send-Lark-message:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: denoland/setup-deno@v2
22+
with:
23+
deno-version: v2.x
24+
25+
- name: Event Message serialization
26+
id: message
27+
run: |
28+
YAML=$(echo '${{ toJSON(github) }}' | deno --allow-all .github/scripts/transform-message.ts)
29+
{
30+
echo 'content<<EOF'
31+
echo $YAML
32+
echo 'EOF'
33+
} >> $GITHUB_OUTPUT
34+
35+
- name: Send message to Lark
36+
if: ${{ contains(steps.message.outputs.content, ':') }}
37+
uses: foxundermoon/feishu-action@v2
38+
with:
39+
url: ${{ secrets.LARK_CHATBOT_HOOK_URL }}
40+
msg_type: post
41+
content: |
42+
${{ steps.message.outputs.content }}

.gitignore

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
/node_modules
55
/.pnp
66
.pnp.js
7+
package-lock.json
8+
yarn.lock
79

810
# testing
911
/coverage
@@ -33,10 +35,7 @@ yarn-debug.log*
3335
yarn-error.log*
3436

3537
# local env files
36-
.env.local
37-
.env.development.local
38-
.env.test.local
39-
.env.production.local
38+
.env*local
4039

4140
# vercel
4241
.vercel

.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
public-hoist-pattern[] = *import-in-the-middle*
22
public-hoist-pattern[] = *require-in-the-middle*
33
auto-install-peers = false
4+
5+
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
6+
@idea2app:registry=https://npm.pkg.github.com
7+
always-auth=true

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ COPY . /app
1313
WORKDIR /app
1414

1515
FROM base AS build
16-
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm i --frozen-lockfile
16+
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm e i --frozen-lockfile
1717
RUN CI=true pnpm build
1818

1919
FROM base

components/Git/Issue/Card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const IssueCard: FC<IssueCardProps> = ({
4848
)}
4949
</div>
5050

51-
<article dangerouslySetInnerHTML={{ __html: marked(body || '') }} />
51+
<article dangerouslySetInnerHTML={{ __html: marked(body || '', { async: false }) }} />
5252

5353
<footer className="flex items-center justify-between">
5454
{user && (

components/Member/Card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const MemberCard: FC<MemberCardProps> = observer(
5959
</ul>
6060

6161
<p
62-
dangerouslySetInnerHTML={{ __html: marked((summary as string) || '') }}
62+
dangerouslySetInnerHTML={{ __html: marked((summary as string) || '', { async: false }) }}
6363
className="text-neutral-500"
6464
/>
6565
</li>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { RequirementEvaluation, UserRole } from '@idea2app/data-server';
2+
import { Box, Typography } from '@mui/material';
3+
import { observer } from 'mobx-react';
4+
import { FC, useContext } from 'react';
5+
6+
import { I18nContext } from '../../models/Translation';
7+
import userStore from '../../models/User';
8+
9+
export const EvaluationDisplay: FC<RequirementEvaluation> = observer(
10+
({
11+
title,
12+
scopes = [],
13+
developerCount,
14+
designerCount,
15+
workload,
16+
monthPeriod,
17+
budget,
18+
factor,
19+
}) => {
20+
const { t } = useContext(I18nContext),
21+
{ roles } = userStore.session || {};
22+
23+
return (
24+
<Box
25+
sx={{
26+
'& .evaluation-item': {
27+
marginBottom: 1,
28+
fontSize: '0.875rem',
29+
padding: '4px 0',
30+
borderLeft: '3px solid',
31+
borderColor: 'primary.light',
32+
paddingLeft: 1,
33+
},
34+
'& ul': {
35+
margin: '8px 0',
36+
paddingLeft: '20px',
37+
},
38+
'& li': {
39+
marginBottom: '4px',
40+
},
41+
}}
42+
>
43+
{title && (
44+
<Box className="evaluation-item">
45+
<Typography component="h3" sx={{ fontWeight: 600, mb: 1 }}>
46+
{title}
47+
</Typography>
48+
</Box>
49+
)}
50+
{scopes[0] && (
51+
<Box className="evaluation-item">
52+
<Typography component="h4" sx={{ fontWeight: 600 }}>
53+
{t('development_scopes')}
54+
</Typography>
55+
<Box component="ul" sx={{ mt: 0.5 }}>
56+
{scopes.map(scope => (
57+
<Box key={scope} component="li" sx={{ ml: 1 }}>
58+
{scope}
59+
</Box>
60+
))}
61+
</Box>
62+
</Box>
63+
)}
64+
{workload && (
65+
<Box className="evaluation-item">
66+
<Typography component="h4" sx={{ fontWeight: 600 }}>
67+
{t('workload')}
68+
</Typography>{' '}
69+
{workload} {t('hours')}
70+
</Box>
71+
)}
72+
{monthPeriod && (
73+
<Box className="evaluation-item">
74+
<Typography component="h4" sx={{ fontWeight: 600 }}>
75+
{t('timeline')}
76+
</Typography>{' '}
77+
{monthPeriod} {t('months')}
78+
</Box>
79+
)}
80+
{budget && (
81+
<Box className="evaluation-item">
82+
<Typography component="h4" sx={{ fontWeight: 600 }}>
83+
{t('budget')}
84+
</Typography>{' '}
85+
RMB¥{budget.toLocaleString()}
86+
</Box>
87+
)}
88+
{(developerCount || designerCount) && (
89+
<Box className="evaluation-item">
90+
<Typography component="h4" sx={{ fontWeight: 600 }}>
91+
{t('team_size')}
92+
</Typography>{' '}
93+
{[
94+
developerCount && `${developerCount} ${t('developers')}`,
95+
designerCount && `${designerCount} ${t('designers')}`,
96+
]
97+
.filter(Boolean)
98+
.join(', ')}
99+
</Box>
100+
)}
101+
{roles && roles.includes(2 as UserRole.Client) && (
102+
<Box className="evaluation-item">
103+
<Typography component="h4" sx={{ fontWeight: 600 }}>
104+
{t('complexity_factor')}
105+
</Typography>{' '}
106+
{factor}
107+
</Box>
108+
)}
109+
</Box>
110+
);
111+
},
112+
);

0 commit comments

Comments
 (0)