Skip to content

Commit 5e99474

Browse files
kangju2000abernier
andauthored
feat: Backers + Contributors mdx components (#339)
* feat: Backer, Contributors mdx components * fix: Handle empty backers and contributors lists in mdx components * CONTRIBUTORS_PAT + authoring doc * pmndrs/docs contrib --------- Co-authored-by: Antoine BERNIER <[email protected]>
1 parent 15e5769 commit 5e99474

File tree

11 files changed

+207
-10
lines changed

11 files changed

+207
-10
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ jobs:
9292
-e THEME_IMPORTANT \
9393
-e THEME_WARNING \
9494
-e THEME_CAUTION \
95+
-e CONTRIBUTORS_PAT \
9596
ghcr.io/pmndrs/docs:2 npm run build
9697
env:
9798
BASE_PATH: ${{ steps.set-base-path.outputs.BASE_PATH }}
@@ -115,6 +116,7 @@ jobs:
115116
THEME_IMPORTANT: '${{ inputs.theme_important }}'
116117
THEME_WARNING: '${{ inputs.theme_warning }}'
117118
THEME_CAUTION: '${{ inputs.theme_caution }}'
119+
CONTRIBUTORS_PAT: ${{ secrets.GITHUB_TOKEN }}
118120
119121
- uses: actions/upload-pages-artifact@v3
120122
with:

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ jobs:
7070
--build-env THEME_IMPORTANT="#8957e5" \
7171
--build-env THEME_WARNING="#d29922" \
7272
--build-env THEME_CAUTION="#da3633" \
73+
--build-env CONTRIBUTORS_PAT="${{ secrets.GITHUB_TOKEN }}" \
7374
> deployment-url.txt
7475
7576
echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT

docs/getting-started/authoring.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,32 @@ or better:
414414
<Hint>I'm a deprecated hint. Use `Gha` instead.</Hint>
415415

416416
</details>
417+
418+
### `Contributors`
419+
420+
```md
421+
<Contributors owner="pmndrs" repo="docs" />
422+
```
423+
424+
> [!WARNING]
425+
> [`CONTRIBUTORS_PAT`](introduction#configuration:~:text=CONTRIBUTORS_PAT) needs to be set. Otherwise, it will display John Doe.
426+
427+
<details>
428+
<summary>Result</summary>
429+
430+
<Contributors owner="pmndrs" repo="docs" />
431+
432+
</details>
433+
434+
### Backers
435+
436+
```md
437+
<Backers repo="react-three-fiber" />
438+
```
439+
440+
<details>
441+
<summary>Result</summary>
442+
443+
<Backers repo="react-three-fiber" />
444+
445+
</details>

docs/getting-started/introduction.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ $ npm ci
4747
| `THEME_IMPORTANT` | "important" color | `#8957e5` |
4848
| `THEME_WARNING` | "warning" color | `#d29922` |
4949
| `THEME_CAUTION` | "caution" color | `#da3633` |
50+
| `CONTRIBUTORS_PAT` | GitHub token for contributors API (see: https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators) | `ghp_1234567890` |
5051

5152
\* Required
5253

@@ -116,6 +117,7 @@ $ (
116117
export THEME_IMPORTANT="#8957e5"
117118
export THEME_WARNING="#d29922"
118119
export THEME_CAUTION="#da3633"
120+
export CONTRIBUTORS_PAT=
119121

120122
kill $(lsof -ti:"$_PORT")
121123
npx serve $MDX -p $_PORT --no-port-switching --no-clipboard &
@@ -163,6 +165,7 @@ $ (
163165
export THEME_IMPORTANT="#8957e5"
164166
export THEME_WARNING="#d29922"
165167
export THEME_CAUTION="#da3633"
168+
export CONTRIBUTORS_PAT=
166169

167170
npm run build
168171

@@ -211,6 +214,7 @@ $ (
211214
export THEME_IMPORTANT="#8957e5"
212215
export THEME_WARNING="#d29922"
213216
export THEME_CAUTION="#da3633"
217+
export CONTRIBUTORS_PAT=
214218

215219
rm -rf "$MDX/out"
216220

@@ -238,6 +242,7 @@ $ (
238242
-e THEME_IMPORTANT \
239243
-e THEME_WARNING \
240244
-e THEME_CAUTION \
245+
-e CONTRIBUTORS_PAT \
241246
pmndrs-docs npm run build
242247

243248
kill $(lsof -ti:"$_PORT")

package-lock.json

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
},
3232
"dependencies": {
3333
"@codesandbox/sandpack-react": "^2.19.8",
34+
"@octokit/core": "^6.1.2",
3435
"@radix-ui/react-collapsible": "^1.1.0",
3536
"@radix-ui/react-dialog": "^1.1.1",
3637
"@radix-ui/react-visually-hidden": "^1.1.0",

src/components/mdx/People/People.tsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import cn from '@/lib/cn'
2+
import { initials } from '@/utils/text'
3+
import { Octokit } from '@octokit/core'
4+
import Image from 'next/image'
5+
import { cache, ComponentProps } from 'react'
6+
import backerBadge from './backer-badge.svg'
7+
8+
// ██████ ██████ ███ ██ ████████ ██████ ██ ██████ ██ ██ ████████ ██████ ██████ ███████
9+
// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
10+
// ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██████ ██ ██ ██ ██ ██ ██████ ███████
11+
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
12+
// ██████ ██████ ██ ████ ██ ██ ██ ██ ██████ ██████ ██ ██████ ██ ██ ███████
13+
14+
const octokit = new Octokit({
15+
auth: process.env.CONTRIBUTORS_PAT,
16+
})
17+
18+
export async function Contributors({
19+
owner,
20+
repo,
21+
limit = 50,
22+
className,
23+
...props
24+
}: { owner: string; repo: string; limit: number } & ComponentProps<'ul'>) {
25+
const contributors = (
26+
await cachedFetchContributors(owner, repo).catch(
27+
(err) =>
28+
Array.from({ length: 100 }).map(() => ({
29+
login: 'jdoe',
30+
html_url: 'https://github.com/jdoe',
31+
})) as Awaited<ReturnType<typeof cachedFetchContributors>>,
32+
)
33+
).slice(0, limit)
34+
35+
return (
36+
<div>
37+
<ul className={cn('flex flex-wrap gap-1', className)} {...props}>
38+
{contributors.map(({ html_url, avatar_url, login }) => (
39+
<li key={login}>
40+
<Avatar profileUrl={html_url} imageUrl={avatar_url} name={login} />
41+
</li>
42+
))}
43+
</ul>
44+
</div>
45+
)
46+
}
47+
48+
async function fetchContributors(owner: string, repo: string) {
49+
const res = await octokit.request(`GET /repos/{owner}/{repo}/collaborators`, {
50+
owner,
51+
repo,
52+
headers: {
53+
'X-GitHub-Api-Version': '2022-11-28',
54+
},
55+
})
56+
return res.data
57+
}
58+
const cachedFetchContributors = cache(fetchContributors)
59+
60+
// ██████ █████ ██████ ██ ██ ███████ ██████ ███████
61+
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
62+
// ██████ ███████ ██ █████ █████ ██████ ███████
63+
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
64+
// ██████ ██ ██ ██████ ██ ██ ███████ ██ ██ ███████
65+
66+
export async function Backers({
67+
repo,
68+
limit = 50,
69+
className,
70+
...props
71+
}: ComponentProps<'ul'> & {
72+
repo: string
73+
limit: number
74+
}) {
75+
const backers = (await fetchBackers(repo)).slice(0, limit)
76+
77+
return (
78+
<div>
79+
<ul className={cn('flex flex-wrap gap-1', className)} {...props}>
80+
{backers.map((backer) => (
81+
<li key={backer.name}>
82+
<Avatar profileUrl={backer.profile} imageUrl={backer.image} name={backer.name} />
83+
</li>
84+
))}
85+
</ul>
86+
<p>
87+
<a
88+
href={`https://opencollective.com/${repo}#backers`}
89+
target="_blank"
90+
rel="noopener noreferrer"
91+
>
92+
<Image src={backerBadge} alt="Backer" />
93+
</a>
94+
</p>
95+
</div>
96+
)
97+
}
98+
99+
async function fetchBackers(repo: string) {
100+
const res = await fetch(`https://opencollective.com/${repo}/members/users.json`)
101+
const backers: {
102+
profile: string
103+
name: string
104+
image: string
105+
totalAmountDonated: number
106+
}[] = await res.json()
107+
108+
const backersMap = new Map(backers.map((backer) => [backer.name, backer]))
109+
backersMap.forEach((backer) => {
110+
const existingBacker = backersMap.get(backer.name)
111+
if (existingBacker && backer.totalAmountDonated >= existingBacker.totalAmountDonated) {
112+
backersMap.set(backer.name, backer) // replace with the backer with the highest donation
113+
}
114+
})
115+
const uniqueBackers = Array.from(backersMap.values())
116+
117+
return uniqueBackers.sort((a, b) => b.totalAmountDonated - a.totalAmountDonated)
118+
}
119+
120+
const cachedFetchBackers = cache(fetchBackers)
121+
122+
//
123+
// common
124+
//
125+
126+
function Avatar({
127+
profileUrl,
128+
imageUrl,
129+
name,
130+
className,
131+
...props
132+
}: { profileUrl: string; imageUrl: string; name: string } & ComponentProps<'a'>) {
133+
return (
134+
<a
135+
href={profileUrl}
136+
target="_blank"
137+
rel="noopener noreferrer"
138+
className={cn(
139+
className,
140+
'bg-surface-container-high inline-flex size-12 items-center justify-center overflow-clip rounded-full',
141+
)}
142+
{...props}
143+
>
144+
{imageUrl ? (
145+
<Image width="48" height="48" src={imageUrl} alt={name} className="size-full" />
146+
) : (
147+
initials(name)
148+
)}
149+
</a>
150+
)
151+
}

src/components/mdx/People/backer-badge.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/mdx/People/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './People'

src/components/mdx/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './Hint'
77
export * from './Img'
88
export * from './Intro'
99
export * from './Keypoints'
10+
export * from './People'
1011
export * from './Summary'
1112
export * from './Toc'
1213

0 commit comments

Comments
 (0)