├── GitHub-Mark-32px.png
├── README.md
├── _config.yml
├── index.css
├── index.html
└── index.js
/GitHub-Mark-32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/naosense/github-id/93a66a006e61ddc1d23a8280e490baa5190b47cf/GitHub-Mark-32px.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## A simple and beautiful github id card | github-id
2 |
3 | Want one github ID card? Now visit `https://naosense.github.io/github-id/?q={your-github-id}` to get one, then you can paste it into your resume so that employers know you better, or paste it on your website to give others a general idea of your github.
4 |
5 | E.g. for the famous r programmer [hadley](https://naosense.github.io/github-id/?q=hadley), the card looks like
6 |
7 | 
8 |
9 | For [me](https://naosense.github.io/github-id/?q=naosense),
10 |
11 | 
12 |
13 | *Talk is cheap, show me your github!*
14 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-hacker
2 | title: Talk is cheap, show me your github | github-id
3 | description: An overview of your github profile
4 | google_analytics: UA-44085500-1
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol" !important;
4 | }
5 |
6 | body {
7 | background: #fff !important;
8 | }
9 |
10 | .wrapper {
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | width: 100%;
15 | height: 100vh;
16 | }
17 |
18 | #outer {
19 | position: relative;
20 | background: #fff;
21 | height: 350px;
22 | width: 550px;
23 | /*overflow: hidden;*/
24 | display: flex;
25 | align-items: center;
26 | border: 1px solid #aaa;
27 | border-radius: 7px;
28 | box-shadow: 1px 1px 2px #888 inset;
29 | }
30 |
31 | #avatar {
32 | position: absolute;
33 | top: 20px;
34 | right: 60px;
35 | width: 150px;
36 | z-index: 0;
37 | animation-delay: 0.5s;
38 | }
39 |
40 | #rank {
41 | width: 100%;
42 | height: 30px;
43 | position: absolute;
44 | top: 318px;
45 | left: 0;
46 | }
47 |
48 | #language {
49 | position: absolute;
50 | right: 10px;
51 | top: 130px;
52 | width: 250px;
53 | height: 250px;
54 | }
55 |
56 | #repo {
57 | width: 250px;
58 | height: 150px;
59 | }
60 |
61 | .content {
62 | animation-delay: 0.3s;
63 | position: absolute;
64 | left: 20px;
65 | z-index: 3
66 | }
67 |
68 | #user_id {
69 | color: #111;
70 | margin-bottom: 5px;
71 | }
72 |
73 | p {
74 | width: 280px;
75 | font-size: 13px;
76 | line-height: 1.4;
77 | color: #aaa;
78 | margin: 20px 0;
79 | }
80 |
81 | .bg {
82 | display: inline-block;
83 | color: #fff;
84 | background: cornflowerblue;
85 | padding: 2px 10px;
86 | border-radius: 50px;
87 | font-size: .7em;
88 | }
89 |
90 | .button {
91 | width: fit-content;
92 | height: fit-content;
93 | margin-top: 10px;
94 | }
95 |
96 | .button a {
97 | display: inline-block;
98 | overflow: hidden;
99 | position: relative;
100 | font-size: 11px;
101 | color: #111;
102 | text-decoration: none;
103 | padding: 10px 15px;
104 | border: 1px solid #aaa;
105 | font-weight: bold;
106 |
107 | }
108 |
109 | .button a:after {
110 | content: "";
111 | position: absolute;
112 | top: 0;
113 | right: -10px;
114 | width: 0%;
115 | background: #111;
116 | height: 100%;
117 | z-index: -1;
118 | transition: width 0.3s ease-in-out;
119 | transform: skew(-25deg);
120 | transform-origin: right;
121 | }
122 |
123 | .button a:hover:after {
124 | width: 150%;
125 | left: -10px;
126 | transform-origin: left;
127 | }
128 |
129 | .button a:hover {
130 | color: #fff;
131 | transition: all 0.5s ease;
132 | }
133 |
134 | .button a:nth-of-type(1) {
135 | border-radius: 50px 0 0 50px;
136 | border-right: none;
137 | }
138 |
139 | .button a:nth-of-type(2) {
140 | border-radius: 0px 50px 50px 0;
141 | }
142 |
143 | .cart-icon {
144 | padding-right: 8px;
145 | }
146 |
147 | .footer {
148 | color: #aaaaaa;
149 | font-size: 11px;
150 | position: absolute;
151 | bottom: 0;
152 | left: 0;
153 | }
154 |
155 | #capture {
156 | position: absolute;
157 | top: -50px;
158 | right: 0;
159 | border: 1px solid #aaa;
160 | border-radius: 50px ;
161 | padding: 1px 10px 2px 10px;
162 | display: inline-block;
163 | cursor: pointer;
164 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Talk is cheap, show me your github | github-id
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Take a photo
48 |
49 |
50 |

51 |
52 |
53 |
56 |
57 |
![]()
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | let parse_query = function (query_string) {
3 |
4 | if (is_empty(query_string)) {
5 | return {};
6 | }
7 | let query = {};
8 | query_string = query_string.substring(query_string.indexOf('?') + 1);
9 | console.log(query_string);
10 | let query_array = query_string.split('&');
11 | query_array.forEach(function (q) {
12 | let kv = q.split('=');
13 | console.log(kv);
14 | query[kv[0]] = kv[1];
15 | });
16 | return query;
17 |
18 | };
19 |
20 | let is_empty = function (s) {
21 | return s === null || s === undefined || s === '';
22 | };
23 |
24 | let datetime_to_datetime_str = function (datetime) {
25 | let year = datetime.getFullYear();
26 | let month = left_padding_zero((datetime.getMonth() + 1));
27 | let day = left_padding_zero(datetime.getDate());
28 | let hour = left_padding_zero(datetime.getHours());
29 | let min = left_padding_zero(datetime.getMinutes());
30 | return year + '-' + month + '-' + day + ' ' + hour + ':' + min;
31 | };
32 |
33 | let left_padding_zero = function (int) {
34 | return int < 10 ? '0' + int : int;
35 | };
36 |
37 | let invoke_github_api = function (url, callback) {
38 | $.ajax({
39 | headers: {
40 | Authorization: 'token ' + select_token(),
41 | Accept: 'application/vnd.github.v3.star+json; charset=utf-8'
42 | },
43 | url: url,
44 | dataType: 'json',
45 | type: 'get',
46 | async: true,
47 | success: function (data, status, xhr) {
48 | callback(data, xhr);
49 | },
50 | error: function (xhr, status, error) {
51 | console.error(xhr.responseText);
52 | callback({}, xhr);
53 | if (xhr.status === 403) {
54 | let query = parse_query(url);
55 | console.log('qq', query);
56 | let access_token = query['access_token'];
57 | if (is_empty(access_token)) {
58 | alert('Please wait for a while, printer is too hot');
59 | } else {
60 | $.ajax({
61 | headers: {
62 | Authorization: 'token ' + select_token(),
63 | Accept: 'application/vnd.github.v3.star+json; charset=utf-8'
64 | },
65 | url: 'https://api.github.com/rate_limit?access_token=' + access_token,
66 | dataType: 'json',
67 | type: 'get',
68 | async: true,
69 | success: function (data, status, xhr) {
70 | alert('Rate limit exceeded, come back at ' + datetime_to_datetime_str(new Date(data['resources']['core']['reset'] * 1000)));
71 | },
72 | error: function (xhr, status, error) {
73 | console.error(xhr.responseText);
74 | }
75 | });
76 | }
77 | }
78 | }
79 | });
80 | };
81 |
82 | let random_int = function (start, end) {
83 | return Math.floor(start + (end - start) * Math.random());
84 | };
85 |
86 | let select_token = function () {
87 | if (is_empty(query['token'])) {
88 | alert("Please specify a token in url like this: https://pingao777.github.io/github-id/?q={your-github-id}&token=your-own-token");
89 | return '';
90 | }
91 | return query['token'];
92 | };
93 |
94 | let format_email = function (user_id, email) {
95 | if (is_empty((email))) {
96 | return '';
97 | } else if (user_id.length * 3 + email.length > 55) {
98 | let end = 55 - user_id.length * 3;
99 | if (end < 1) {
100 | return '...';
101 | } else {
102 | return email.substring(0, end) + '...';
103 | }
104 | } else {
105 | return email;
106 | }
107 | };
108 |
109 | let simple_number = function (num, digits) {
110 | if (num < 1000) {
111 | return num;
112 | } else if (1000 <= num && num < 1000000) {
113 | return (num % 1000 === 0 ? num / 1000 : (num / 1000).toFixed(digits)) + 'k';
114 | } else {
115 | return (num % 1000000 === 0 ? num / 1000000 : (num / 1000000).toFixed(digits)) + 'm';
116 | }
117 | };
118 |
119 | let object_to_array = function (object) {
120 | let language_array = [];
121 | Object.keys(object).forEach(function (k, i) {
122 | language_array.push([k, object[k]]);
123 | });
124 | return language_array;
125 | };
126 |
127 | let display_rank = function (data) {
128 | let myChart = echarts.init(document.getElementById('rank'));
129 |
130 | let option = {
131 | grid: {
132 | top: 0,
133 | bottom: 0,
134 | left: -16,
135 | right: -16
136 | },
137 | xAxis: {
138 | type: 'category',
139 | show: false
140 | },
141 | yAxis: {
142 | type: 'value',
143 | show: false
144 |
145 | },
146 | series: [{
147 | data: data,
148 | type: 'line',
149 | showSymbol: false,
150 | areaStyle: {},
151 | color: 'rgba(40,167,69,0.2)'
152 |
153 | }]
154 | };
155 |
156 | myChart.setOption(option);
157 | };
158 |
159 | let display_language = function (q, name, data) {
160 | let myChart = echarts.init(document.getElementById('language'));
161 |
162 | let option = {
163 | tooltip: {},
164 | angleAxis: {
165 | min: 0,
166 | max: 1,
167 | axisTick: {
168 | show: false
169 | },
170 | axisLabel: {
171 | show: true,
172 | fontSize: 10,
173 | margin: 5,
174 | formatter: function (value, index) {
175 | if (index === 1) {
176 | return value * 100 + '%';
177 | } else {
178 | return '';
179 | }
180 | }
181 | },
182 | splitLine: {
183 | lineStyle: {
184 | type: 'dashed'
185 | }
186 | }
187 | },
188 | radiusAxis: {
189 | type: 'category',
190 | data: name,
191 | z: 10,
192 | axisLabel: {
193 | fontSize: 10,
194 | interval: 0
195 | }
196 | },
197 | polar: {
198 | radius: '55%'
199 | },
200 | series: [{
201 | type: 'bar',
202 | data: data,
203 | coordinateSystem: 'polar',
204 | name: 'Language Percent',
205 | color: 'rgba(40,167,69,1.0)'
206 | }]
207 | };
208 |
209 | myChart.setOption(option);
210 | };
211 |
212 | let display_repo = function (data) {
213 | let myChart = echarts.init(document.getElementById('repo'));
214 |
215 | let option = {
216 | title: {
217 | subtext: 'Popular Repositories',
218 | },
219 | tooltip: {
220 | trigger: 'axis',
221 | axisPointer: {
222 | type: 'shadow'
223 | }
224 | },
225 | grid: {
226 | top: 35,
227 | bottom: 20,
228 | left: 0,
229 | right: 15,
230 | containLabel: true
231 | },
232 | xAxis: {
233 | name: 'Stars',
234 | type: 'value',
235 | axisLabel: {
236 | formatter: function (value, index) {
237 | // 格式化成月/日,只在第一个刻度显示年份
238 | if (index % 2 === 1) {
239 | return simple_number(value, 1);
240 | } else {
241 | return '';
242 | }
243 | }
244 | },
245 | splitLine: {
246 | lineStyle: {
247 | type: 'dashed'
248 | }
249 | }
250 | },
251 | yAxis: {
252 | type: 'category',
253 | axisLabel: {
254 | show: false
255 | }
256 |
257 | },
258 | series: [
259 | {
260 | name: 'Stars',
261 | type: 'bar',
262 | label: {
263 | normal: {
264 | show: true,
265 | position: 'insideLeft',
266 | formatter: function (param) {
267 | return param['name'];
268 | }
269 | }
270 | },
271 | data: data
272 | },
273 | ],
274 | color: ['#005cc5']
275 | };
276 |
277 | myChart.setOption(option);
278 | };
279 |
280 | let render_chart = function (user_id) {
281 | let user_url = 'https://api.github.com/users/' + user_id;
282 | invoke_github_api(user_url, function (user_data) {
283 | let email = format_email(user_id, user_data['email']);
284 | let email_html = is_empty(email) ? '' : ' ' + email + '';
285 | $('#user_id').html(user_id + email_html);
286 | $('#avatar').attr('src', user_data['avatar_url']);
287 | $('#following').html('Following: ' + simple_number(user_data['following'], 1));
288 | $('#follower').html('Follower: ' + simple_number(user_data['followers'], 1));
289 |
290 | let progress_bar = $('#progress-bar');
291 | progress_bar.css('width', '10%');
292 |
293 | let repo_count = user_data['public_repos'];
294 | if (repo_count === 0) {
295 | progress_bar.css('width', '100%');
296 | progress_bar.css('background-color', '#fff');
297 | return;
298 | }
299 | let page_size = 100;
300 | let page_count = Math.ceil(repo_count / page_size);
301 | let repos = [];
302 | for (let page = 1; page <= page_count; page++) {
303 | let repo_url = 'https://api.github.com/users/' + user_id + '/repos?sort=created&direction=asc&per_page='
304 | + page_size + '&page=' + page;
305 |
306 | invoke_github_api(repo_url, function (repo_data) {
307 | repos = repos.concat(repo_data.map(function (e) {
308 | return [e['name'], e['stargazers_count']];
309 | }));
310 |
311 | progress_bar.css('width', (10 + 40 / repo_count * repos.length) + '%');
312 |
313 | if (repos.length === repo_count) {
314 | display_rank(repos);
315 | repos.sort(function (r1, r2) {
316 | return r2[1] - r1[1];
317 | });
318 |
319 | display_repo(repos.slice(0, 3).map(function (e) {
320 | return [e[1], e[0]]
321 | }).reverse());
322 |
323 | let repos_no_io = repos.filter(function (r) {
324 | return r[0].indexOf(user_id + '.github.io') === -1;
325 | });
326 |
327 | if (repos_no_io.length === 0) {
328 | progress_bar.css('width', '100%');
329 | progress_bar.css('background-color', '#fff');
330 | return;
331 | }
332 |
333 | let language = {};
334 | let load_repo_count = 0;
335 | for (let i = 0; i < repos_no_io.length; i++) {
336 | let r = repos_no_io[i];
337 |
338 | let language_url = 'https://api.github.com/repos/' + user_id + '/' + r[0] + '/languages';
339 | invoke_github_api(language_url, function (language_data, xhr) {
340 | if (xhr.status === 200) {
341 | let total_codes = 0;
342 | Object.keys(language_data).forEach(function (k, i) {
343 | total_codes += language_data[k];
344 | });
345 | Object.keys(language_data).forEach(function (k, i) {
346 | let percent = language_data[k] / (total_codes * repos_no_io.length);
347 | if (language.hasOwnProperty(k)) {
348 | language[k] += percent;
349 | } else {
350 | language[k] = percent;
351 | }
352 | });
353 | }
354 |
355 | load_repo_count++;
356 | progress_bar.css('width', (50 + 50 / repos_no_io.length * load_repo_count) + '%');
357 |
358 | if (load_repo_count === repos_no_io.length) {
359 |
360 | let language_array = object_to_array(language);
361 | language_array.sort(function (l1, l2) {
362 | return l2[1] - l1[1];
363 | });
364 |
365 | let lang_name = [];
366 | let lang_data = [];
367 | language_array.slice(0, 5).forEach(function (l) {
368 | lang_name.push(l[0]);
369 | lang_data.push(l[1]);
370 | });
371 |
372 | if (lang_name.length > 0) {
373 | display_language('', lang_name.reverse(), lang_data.reverse());
374 | }
375 |
376 | progress_bar.css('background-color', '#fff');
377 | }
378 |
379 | })
380 | }
381 | }
382 | });
383 | }
384 | });
385 | };
386 |
387 | let query = parse_query(window.location.search);
388 | let q = is_empty(query['q']) ? 'pingao777' : query['q'];
389 | if (is_empty(select_token())) {
390 | return;
391 | }
392 |
393 | render_chart(q);
394 |
395 | $('#capture').click(function () {
396 | domtoimage.toPng(document.getElementById('outer'))
397 | .then(function (dataUrl) {
398 | let link = document.createElement('a');
399 | link.download = 'github-id_' + q + '.png';
400 | link.href = dataUrl;
401 | link.click();
402 | })
403 | .catch(function (error) {
404 | console.error('Oops, something went wrong!', error);
405 | });
406 | });
407 |
408 | });
--------------------------------------------------------------------------------