Skip to content

Commit 3513ff6

Browse files
authored
docs: replace the avatar with a base64 image (#652)
1 parent 92e3ae1 commit 3513ff6

20 files changed

+408
-129
lines changed

website/README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
44

55
## Requirements
66

7-
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above (which can be checked by running `node -v`). You can use [nvm](https://github.com/nvm-sh/nvm) for managing multiple Node versions on a single machine installed.
7+
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above (which can be checked by running `node -v`). You can
8+
use [nvm](https://github.com/nvm-sh/nvm) for managing multiple Node versions on a single machine installed.
89
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
910

1011
## Installation
@@ -27,7 +28,24 @@ Preview the Chinese website locally:
2728
pnpm start --locale zh-cn
2829
```
2930

30-
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
31+
This command starts a local development server and opens up a browser window. Most changes are reflected live without
32+
having to restart the server.
33+
34+
## Team Page
35+
36+
### Member
37+
38+
Update the member information in `src/pages/team/data/member.json` File.
39+
40+
### Avatar
41+
42+
```console
43+
pnpm github-avatar
44+
```
45+
46+
This command will fetch the base64 string of the GitHub avatar from file
47+
`src/pages/team/data/member.json`, and store the result in the `src/pages/team/data/` directory. The operation might
48+
take a little while.
3149

3250
## Internationalization
3351

@@ -49,7 +67,8 @@ pnpm write-translations --locale en
4967
pnpm build
5068
```
5169

52-
This command generates static content into the `build` directory and can be served using any static contents hosting service.
70+
This command generates static content into the `build` directory and can be served using any static contents hosting
71+
service.
5372

5473
## Directory Structure
5574

website/blog/authors.json

Lines changed: 82 additions & 0 deletions
Large diffs are not rendered by default.

website/blog/authors.yml

Lines changed: 0 additions & 53 deletions
This file was deleted.

website/docusaurus.config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ const config = {
6161
},
6262
blog: {
6363
showReadingTime: false,
64-
postsPerPage: 5,
64+
postsPerPage: 15,
6565
feedOptions: {
6666
type: 'all',
6767
},
6868
editUrl: `${repoUrl}/edit/${branch}/website/`,
6969
editLocalizedFiles: true,
70-
blogSidebarCount: 'ALL'
70+
blogSidebarCount: 'ALL',
71+
authorsMapPath: "authors.json"
7172
},
7273
theme: {
7374
customCss: './src/css/custom.css'

website/github-avatar.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
const fs = require("fs");
21+
const https = require("https");
22+
23+
const teamSrc = "src/pages/team/data/team.json";
24+
const avatarFile = "src/pages/team/data/github-avatar.json";
25+
const avatarSize = 100;
26+
const authorsFile = "blog/authors.json";
27+
28+
/**
29+
* Generates a random delay between min and max milliseconds
30+
* @param {number} min - Minimum delay in milliseconds
31+
* @param {number} max - Maximum delay in milliseconds
32+
* @returns {number} Random delay in milliseconds
33+
*/
34+
function getRandomDelay(min, max) {
35+
return Math.floor(Math.random() * (max - min + 1)) + min;
36+
}
37+
38+
/**
39+
* Fetches avatar image from GitHub and converts it to base64
40+
* @param {string} githubId - GitHub ID
41+
* @returns {Promise<string>} Base64 encoded avatar image
42+
*/
43+
function fetchAvatarAsBase64(githubId) {
44+
return new Promise((resolve, reject) => {
45+
const avatarUrl = `https://avatars.githubusercontent.com/u/${githubId}?v=4&s=${avatarSize}`;
46+
47+
https
48+
.get(avatarUrl, (response) => {
49+
// Check if request was successful
50+
if (response.statusCode !== 200) {
51+
reject(new Error(`Failed to fetch avatar from ${avatarUrl}: ${response.statusCode}`));
52+
return;
53+
}
54+
55+
const chunks = [];
56+
57+
// Collect data chunks
58+
response.on("data", (chunk) => {
59+
chunks.push(chunk);
60+
});
61+
62+
// Convert to base64 when complete
63+
response.on("end", () => {
64+
const buffer = Buffer.concat(chunks);
65+
const base64 = buffer.toString("base64");
66+
resolve(base64);
67+
});
68+
})
69+
.setTimeout(10000, () => {
70+
reject(new Error(`Failed to fetch avatar ${avatarUrl}: timed out`));
71+
})
72+
.on("error", (error) => {
73+
reject(error);
74+
});
75+
});
76+
}
77+
78+
/**
79+
* Get GitHub name from URL
80+
* @param {string} url - GitHub URL
81+
* @returns {string} GitHub name
82+
*/
83+
function getGitName(url) {
84+
return url.replace('https://github.com/', '');
85+
}
86+
87+
/**
88+
* Processes a list of githubIds and adds avatar_base64 property
89+
* @param {Array} ids - Array of id
90+
* @returns {Promise<Array>} Array of avatar_base64
91+
*/
92+
async function processAvatars(ids) {
93+
const processedArray = [];
94+
95+
for (let i = 0; i < ids.length; i++) {
96+
const _id = ids[i];
97+
98+
try {
99+
console.log(`-- Fetching avatar for ${_id} ... [${i + 1}/${ids.length}]`);
100+
101+
// Fetch avatar and convert to base64
102+
const avatarBase64 = await fetchAvatarAsBase64(_id);
103+
104+
processedArray.push({
105+
id: _id,
106+
avatar_base64: avatarBase64
107+
});
108+
console.log(`✓ Successfully processed ${_id}`);
109+
} catch (error) {
110+
console.error(`✗ Error processing ${_id}: ${error.message}`);
111+
}
112+
113+
// Add random delay between 100-2000 millisecond before next request (except for the last member)
114+
if (i < ids.length - 1) {
115+
await new Promise((resolve) => setTimeout(resolve, getRandomDelay(100, 2000)));
116+
}
117+
}
118+
return processedArray;
119+
}
120+
121+
/**
122+
* Processes blog authors data and adds avatar_base64 property
123+
* @param {Object} teamData - Team data
124+
* @param {Array} avatars - Array of avatars
125+
* @returns {Promise<Object>} Blog authors data
126+
*/
127+
async function processBlogAuthors(teamData, avatars) {
128+
const blogAuthorsMapPath = {};
129+
(teamData.pmc.concat(teamData.committer) || []).forEach((m) => {
130+
const gitName = getGitName(m.gitUrl);
131+
const avatarObj = avatars.find((item) => item.id === m.githubId);
132+
blogAuthorsMapPath[gitName] = {
133+
"name": m.name,
134+
"url": m.gitUrl,
135+
"image_url": "data:image/png;base64," + avatarObj.avatar_base64,
136+
"socials": {
137+
"github": gitName
138+
}
139+
}
140+
});
141+
142+
return blogAuthorsMapPath;
143+
}
144+
145+
/**
146+
* Main function
147+
*/
148+
async function main() {
149+
try {
150+
const uniqueGithubIdsSet = new Set();
151+
152+
// 1. Read and parse team
153+
console.log(`==> Reading ${teamSrc} file`);
154+
const teamSrcData = JSON.parse(fs.readFileSync(teamSrc, "utf8"));
155+
156+
// PMC && Committer
157+
(teamSrcData.pmc.concat(teamSrcData.committer) || []).forEach((d) => {
158+
if (d.githubId) {
159+
uniqueGithubIdsSet.add(d.githubId);
160+
}
161+
});
162+
163+
const uniqueGithubArray = Array.from(uniqueGithubIdsSet);
164+
165+
console.log("\n==> Processing avatars");
166+
const avatarsArray = await processAvatars(uniqueGithubArray);
167+
168+
// 2. Write files
169+
console.log(`\n==> Write to ${avatarFile}`);
170+
fs.writeFileSync(avatarFile, JSON.stringify(avatarsArray, null, 2));
171+
172+
// 3. Blog authors
173+
const blogAuthorsMapPaths = await processBlogAuthors(teamSrcData, avatarsArray);
174+
console.log(`\n==> Write to ${authorsFile}`);
175+
fs.writeFileSync(authorsFile, JSON.stringify(blogAuthorsMapPaths, null, 2));
176+
177+
console.log("\n✓ Done!");
178+
} catch (error) {
179+
console.error("Error:", error.message);
180+
process.exit(1);
181+
}
182+
}
183+
184+
main();

website/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"write-translations": "docusaurus write-translations",
1616
"write-heading-ids": "docusaurus write-heading-ids",
1717
"md-lint": "markdownlint-cli2 --config ./.markdownlint-cli2.jsonc \"./**/*.md\" \"#node_modules\"",
18-
"md-lint-fix": "yarn md-lint --fix"
18+
"md-lint-fix": "pnpm md-lint --fix",
19+
"github-avatar": "node github-avatar.js"
1920
},
2021
"dependencies": {
2122
"@docusaurus/core": "^3.9.1",

website/src/pages/team/data/github-avatar.json

Lines changed: 42 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)