├── 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 | ![hadley](http://wocanmei-hexo.nos-eastchina1.126.net/github-id/github-id_hadley7.png) 8 | 9 | For [me](https://naosense.github.io/github-id/?q=naosense), 10 | 11 | ![naosense](http://wocanmei-hexo.nos-eastchina1.126.net/github-id/github-id_pingao7777.png) 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 | Fork me on GitHub 42 |
43 |
44 |
45 |
46 |
47 | Take a photo 48 |
49 |
50 | 51 |

52 |
53 |
54 | 55 |
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 | }); --------------------------------------------------------------------------------