init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
import React from 'https://esm.sh/react@18.2.0';
// eslint-disable-next-line import/extensions
import { ImageResponse } from 'https://deno.land/x/og_edge/mod.ts';
const MAX_AUTHORS = 5;
export default async function handler(req: Request) {
const params = new URL(req.url).searchParams;
const title = params.get('title');
const authors = params.get('authors');
const product = params.get('product');
const description = params.get('description');
const parsedAuthors =
authors &&
authors
.split(',')
.map((author) => {
const [name, github] = author.split('@');
return { name: name.trim(), github: github.trim() };
})
.filter(({ name, github }) => name && github);
const withAuthors = parsedAuthors && parsedAuthors.length > 0;
let starCount = 0;
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
background: 'linear-gradient(180deg, #FFFFFF 0%, #F0F7FF 75.52%)',
overflow: 'hidden',
position: 'relative',
display: 'flex',
}}
>
<div
style={{
marginTop: 100,
marginLeft: 100,
marginRight: 100,
marginBottom: 100,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
}}
>
<div
style={{
height: 40,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}}
>
<svg width="45" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.618 8.779 1.566.551a.625.625 0 0 0-.941.54v20.972c0 .659.346 1.27.91 1.608l4.393 2.636c.417.25.947-.05.947-.536V11.683a.25.25 0 0 1 .375-.217l8.376 4.826a1.25 1.25 0 0 0 1.248 0l8.376-4.829a.25.25 0 0 1 .375.217v7.62c0 .435-.226.838-.596 1.066l-7.856 4.82a.625.625 0 0 0-.298.533v7.046c0 .223.119.429.312.54l10.925 6.326c.394.228.88.224 1.27-.01l14.386-8.632a1.25 1.25 0 0 0 .607-1.072V16.104a.625.625 0 0 0-.947-.536l-4.696 2.818a1.25 1.25 0 0 0-.607 1.072v7.063c0 .22-.115.423-.303.536l-8.484 5.09a1.25 1.25 0 0 1-1.202.046L22.5 29.375l8.768-5.26a1.25 1.25 0 0 0 .607-1.073V1.09a.625.625 0 0 0-.94-.54L16.881 8.78a1.25 1.25 0 0 1-1.264 0Z"
fill="#007FFF"
/>
<path
d="M44.375 1.104v6.938c0 .44-.23.846-.607 1.072l-4.696 2.818a.625.625 0 0 1-.947-.536V4.458c0-.44.23-.846.607-1.072L43.428.568c.417-.25.947.05.947.536Z"
fill="#007FFF"
/>
</svg>
<div
style={{
height: 40,
width: 2,
backgroundColor: '#DAE2ED',
margin: '0 24px',
}}
/>
<p
style={{
fontFamily: 'General Sans',
fontSize: '24px',
fontWeight: 600,
lineHeight: '40px',
letterSpacing: 1,
color: '#007FFF',
}}
>
{product}
</p>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'left',
}}
>
{title &&
title.split('\\n').map((line) => (
<p
style={{
margin: 0,
flexWrap: 'wrap',
fontFamily: 'General Sans',
fontStyle: 'normal',
fontWeight: 600,
fontSize: '72px',
lineHeight: 1.2,
color: '#0B0D0E',
}}
>
{line.split('*').flatMap((text, index) => {
if (index > 0) {
starCount += 1;
}
const isBlue = starCount % 2 === 1;
return text.split(' ').map((word) => (
<span
style={{
color: isBlue ? '#007FFF' : '#0B0D0E',
marginRight: word.length > 0 ? 15 : 0,
}}
>
{word}
</span>
));
})}
</p>
))}
{description && (
<p
style={{
fontFamily: 'IBM Plex Sans',
fontSize: '36px',
fontWeight: 500,
color: '#000',
lineHeight: 1.5,
marginTop: 8,
marginBottom: 0,
marginLeft: 0,
marginRight: 0,
width: '100%',
}}
>
{description}
</p>
)}
</div>
<div
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
maxHeight: 180, // Limit to 2 lines of authors
overflow: 'hidden',
paddingTop: -20,
}}
>
{withAuthors &&
parsedAuthors.slice(0, MAX_AUTHORS).map(({ name, github }) => {
return (
<div
style={{
maxWidth: 1080,
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginRight: 40,
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 70,
height: 70,
borderRadius: '50%',
background: '#CCE5FF',
}}
>
<img
src={`https://github.com/${github}.png`}
width={62}
height={62}
style={{ borderRadius: '50%' }}
alt=""
/>
</div>
<div
style={{
marginLeft: 20,
display: 'flex',
flexDirection: 'column',
}}
>
<span
style={{
fontFamily: 'IBM Plex Sans',
fontSize: '26px',
fontWeight: '500',
lineHeight: 1.5,
textAlign: 'left',
color: '#101418',
}}
>
{name}
</span>
<span
style={{
fontFamily: 'IBM Plex Sans',
fontSize: '20px',
fontWeight: '500',
lineHeight: 1.5,
textAlign: 'left',
color: '#007FFF',
}}
>
@{github}
</span>
</div>
</div>
);
})}
</div>
</div>
</div>
),
{
width: 1280,
height: 640,
// debug: true,
fonts: [
{
name: 'IBM Plex Sans',
data: await fetch('https://fonts.cdnfonts.com/s/15449/IBMPlexSans-Medium.woff').then(
(a) => a.arrayBuffer(),
),
weight: 500,
style: 'normal',
},
{
name: 'General Sans',
data: await fetch('https://fonts.cdnfonts.com/s/85793/GeneralSans-Semibold.woff').then(
(a) => a.arrayBuffer(),
),
weight: 600,
style: 'normal',
},
{
name: 'General Sans',
data: await fetch('https://fonts.cdnfonts.com/s/85793/GeneralSans-Bold.woff').then((a) =>
a.arrayBuffer(),
),
weight: 700,
style: 'normal',
},
],
// Manage the caching
headers: {
// Cache control is already done by the package (https://github.com/ascorbic/og-edge/blob/d533ef878801d7f808eb004f254e782ec6ba1e3c/mod.ts#L233-L240)
'Netlify-Vary': 'query',
},
},
);
}
export const config = {
cache: 'manual',
path: '/edge-functions/og-image',
};

View File

@@ -0,0 +1,41 @@
/**
* @param {object} event
* @param {string} event.body - https://jsoneditoronline.org/#left=cloud.fb1a4fa30a4f475fa6887071c682e2c1
*/
exports.handler = async (event) => {
const { payload } = JSON.parse(event.body);
const repo = payload.review_url.match(/github\.com\/(.*)\/pull\/(.*)/);
if (!repo) {
throw new Error(`No repo found at review_url: ${payload.review_url}`);
}
// eslint-disable-next-line no-console
console.info(`repo:`, repo[1]);
// eslint-disable-next-line no-console
console.info(`PR:`, repo[2]);
// eslint-disable-next-line no-console
console.info(`url:`, payload.deploy_ssl_url);
// for more details > https://circleci.com/docs/2.0/api-developers-guide/#
await fetch(`https://circleci.com/api/v2/project/gh/${repo[1]}/pipeline`, {
method: 'POST',
headers: {
'Content-type': 'application/json',
// token from https://app.netlify.com/sites/material-ui/settings/deploys#environment-variables
'Circle-Token': process.env.CIRCLE_CI_TOKEN,
},
body: JSON.stringify({
// For PR, /head is needed. https://support.circleci.com/hc/en-us/articles/360049841151
branch: `pull/${repo[2]}/head`,
parameters: {
// the parameters defined in .circleci/config.yml
workflow: 'e2e-website', // name of the workflow
'e2e-base-url': payload.deploy_ssl_url, // deploy preview url
},
}),
});
return {
statusCode: 200,
body: {},
};
};

View File

@@ -0,0 +1,216 @@
import querystring from 'node:querystring';
import { App, AwsLambdaReceiver, BlockAction, ButtonAction } from '@slack/bolt';
import { Handler } from '@netlify/functions';
const X_FEEBACKS_CHANNEL_ID = 'C04U3R2V9UK';
const JOY_FEEBACKS_CHANNEL_ID = 'C050VE13HDL';
const TOOLPAD_FEEBACKS_CHANNEL_ID = 'C050MHU703Z';
const CORE_FEEBACKS_CHANNEL_ID = 'C041SDSF32L';
const BASE_UI_FEEBACKS_CHANNEL_ID = 'C075LJG1LMP';
const MATERIAL_UI_FEEBACKS_CHANNEL_ID = 'C0757QYLK7V';
// const PIGMENT_CSS_FEEBACKS_CHANNEL_ID = 'C074TBW0JKZ';
const X_GRID_FEEBACKS_CHANNEL_ID = 'C0757R0KW67';
const X_CHARTS_FEEBACKS_CHANNEL_ID = 'C0757UBND98';
const X_EXPLORE_FEEBACKS_CHANNEL_ID = 'C074TBYQK2T';
// const DESIGN_KITS_FEEBACKS_CHANNEL_ID = 'C075ADGN0UU';
// The design feedback alert was removed in https://github.com/mui/material-ui/pull/39691
// This dead code is here to simplify the creation of special feedback channel
const DESIGN_FEEDBACKS_CHANNEL_ID = 'C05HHSFH2QJ';
export type MuiProductId =
| 'null'
| 'base-ui'
| 'material-ui'
| 'joy-ui'
| 'system'
| 'docs-infra'
| 'docs'
| 'x-data-grid'
| 'x-date-pickers'
| 'x-charts'
| 'x-tree-view'
| 'toolpad-studio'
| 'toolpad-core';
const getSlackChannelId = (
url: string,
productId: MuiProductId,
specialCases: { isDesignFeedback?: boolean },
) => {
const { isDesignFeedback } = specialCases;
if (isDesignFeedback) {
return DESIGN_FEEDBACKS_CHANNEL_ID;
}
switch (productId) {
case 'base-ui':
return BASE_UI_FEEBACKS_CHANNEL_ID;
case 'material-ui':
case 'system':
return MATERIAL_UI_FEEBACKS_CHANNEL_ID;
case 'joy-ui':
return JOY_FEEBACKS_CHANNEL_ID;
case 'x-data-grid':
return X_GRID_FEEBACKS_CHANNEL_ID;
case 'x-date-pickers':
case 'x-tree-view':
return X_EXPLORE_FEEBACKS_CHANNEL_ID;
case 'x-charts':
return X_CHARTS_FEEBACKS_CHANNEL_ID;
case 'toolpad-studio':
case 'toolpad-core':
return TOOLPAD_FEEBACKS_CHANNEL_ID;
default:
break;
}
// Fallback
if (url.includes('/x/')) {
return X_FEEBACKS_CHANNEL_ID;
}
return CORE_FEEBACKS_CHANNEL_ID;
};
// Setup of the slack bot (taken from https://slack.dev/bolt-js/deployments/aws-lambda)
const awsLambdaReceiver = new AwsLambdaReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
});
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
receiver: awsLambdaReceiver,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
// Define slack actions to answer
app.action<BlockAction<ButtonAction>>('delete_action', async ({ ack, body, client, logger }) => {
try {
await ack();
const { channel, message } = body;
const channelId = channel?.id;
if (!channelId) {
throw new Error('feedback-management: Unknown channel Id');
}
await client.chat.delete({
channel: channelId,
ts: message!.ts,
as_user: true,
token: process.env.SLACK_BOT_TOKEN,
});
} catch (error) {
logger.error(JSON.stringify(error, null, 2));
}
});
export const handler: Handler = async (event, context, callback) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 404 };
}
try {
const { payload } = querystring.parse(event.body ?? '') as { payload: any };
const data = JSON.parse(payload);
if (data.callback_id === 'send_feedback') {
// We send the feedback to the appropriate slack channel
const {
rating,
comment,
currentLocationURL,
commentSectionURL: inCommentSectionURL,
commentSectionTitle,
githubRepo,
productId,
} = data;
// The design feedback alert was removed in https://github.com/mui/material-ui/pull/39691
// This dead code is here to simplify the creation of special feedback channel
const isDesignFeedback = inCommentSectionURL.includes('#new-docs-api-feedback');
const commentSectionURL = isDesignFeedback ? '' : inCommentSectionURL;
const simpleSlackMessage = [
`New comment ${rating === 1 ? '👍' : ''}${rating === 0 ? '👎' : ''}`,
`>${comment.split('\n').join('\n>')}`,
`sent from ${currentLocationURL}${
commentSectionTitle ? ` (from section <${commentSectionURL}|${commentSectionTitle})>` : ''
}`,
].join('\n\n');
const githubNewIssueParams = new URLSearchParams({
title: '[ ] Docs feedback',
body: `Feedback received:
${comment}
from ${commentSectionURL}
`,
});
await app.client.chat.postMessage({
channel: getSlackChannelId(currentLocationURL, productId, { isDesignFeedback }),
text: simpleSlackMessage, // Fallback for notification
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: simpleSlackMessage,
},
},
{
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: 'Create issue',
emoji: true,
},
url: `${githubRepo}/issues/new?${githubNewIssueParams}`,
},
{
type: 'button',
text: {
type: 'plain_text',
text: 'Delete',
},
value: JSON.stringify({
comment,
currentLocationURL,
commentSectionURL,
}),
style: 'danger',
action_id: 'delete_action',
},
],
},
],
as_user: true,
unfurl_links: false,
unfurl_media: false,
});
} else {
const awsHandler = await awsLambdaReceiver.start();
// @ts-ignore
return awsHandler(event, context, callback);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(error, null, 2));
return {
statusCode: 500,
body: JSON.stringify({}),
};
}
return {
statusCode: 200,
body: JSON.stringify({}),
};
};