Files
awesome-reviewers/leaderboard.html
2025-07-29 11:12:35 +03:00

378 lines
16 KiB
HTML

---
layout: default
title: Leaderboard
description: Top contributors powering Awesome Reviewers
---
<section class="hero leaderboard-hero">
<div class="container">
<a href="/" class="back-link">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
</svg>
Back to all reviewers
</a>
<h1>Leaderboard</h1>
<p>Top contributors behind the Awesome Reviewers prompts.</p>
</div>
</section>
<section class="library-header">
<div class="container">
<h2>Contributors (<span id="leader-count">{{ site.data.leaderboard | size }}</span>)</h2>
</div>
</section>
<section class="leaderboard">
<div class="container">
<table id="leaderboard-table" class="leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Reviewers</th>
<th>Repositories</th>
<th>Location</th>
<th>Company</th>
<th>Links</th>
<th>Last Contribution</th>
</tr>
</thead>
<tbody>
{% for user in site.data.leaderboard %}
<tr data-contributor="{{ user.name }}">
<td>{{ forloop.index }}</td>
<td class="lb-name">
{% if forloop.index == 1 %}🥇 {% elsif forloop.index == 2 %}🥈 {% elsif forloop.index == 3 %}🥉 {% endif %}
<img class="lb-avatar" src="https://github.com/{{ user.name }}.png?size=32" alt="{{ user.name }} avatar">
<a href="https://github.com/{{ user.name }}" target="_blank" rel="noopener noreferrer">{{ user.name }}</a>
</td>
<td data-sort="{{ user.reviewers_count }}">{{ user.reviewers_count }}</td>
<td data-sort="{{ user.repos_count }}">{{ user.repos_count }}</td>
<td class="loc-cell"></td>
<td class="company-cell"></td>
<td class="links-cell"></td>
<td data-sort="{{ user.last_contribution }}">{{ user.last_contribution | date: '%Y-%m-%d' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<div id="drawer" class="drawer">
<div class="drawer-overlay" onclick="closeDrawer()"></div>
<div class="drawer-panel">
<div class="drawer-header">
<button class="drawer-close" onclick="closeDrawer()">&times;</button>
</div>
<div id="drawer-content"></div>
</div>
</div>
<script>
function sortTable(col) {
const table = document.getElementById('leaderboard-table');
const tbody = table.tBodies[0];
const rows = Array.from(tbody.rows);
const asc = table.dataset.sortCol == col && table.dataset.sortDir === 'asc' ? false : true;
rows.sort(function(a, b) {
let aVal = a.cells[col].dataset.sort || a.cells[col].innerText;
let bVal = b.cells[col].dataset.sort || b.cells[col].innerText;
const numA = parseFloat(aVal); const numB = parseFloat(bVal);
if(!isNaN(numA) && !isNaN(numB)) {
return asc ? numA - numB : numB - numA;
}
return asc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
});
rows.forEach(r => tbody.appendChild(r));
table.dataset.sortCol = col;
table.dataset.sortDir = asc ? 'asc' : 'desc';
}
document.querySelectorAll('#leaderboard-table th').forEach((th, i) => {
th.addEventListener('click', () => sortTable(i));
});
let contributors = {};
fetch('/assets/data/contributors.json')
.then(r => r.json())
.then(data => {
contributors = data;
const hash = location.hash.slice(1);
if (hash && contributors[hash]) {
openContributorDrawer(hash);
}
loadProfileData();
})
.catch(err => {
console.error('Failed to load contributors dataset', err);
});
attachContributorEvents();
function attachContributorEvents() {
document.querySelectorAll('tr[data-contributor]').forEach(row => {
row.addEventListener('click', e => {
if (e.target.tagName.toLowerCase() === 'a') return;
const user = row.dataset.contributor;
openContributorDrawer(user);
});
});
}
function loadProfileData() {
const rows = document.querySelectorAll('tr[data-contributor]');
rows.forEach((row, index) => {
const user = row.dataset.contributor;
const stored = contributors[user] && contributors[user].profile;
if (stored) {
applyProfile(row, stored);
return;
}
setTimeout(() => {
fetch(`https://api.github.com/users/${user}`, {
headers: {
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
})
.then(r => (r.ok ? r.json() : null))
.then(data => {
if (!data) return;
if (contributors[user]) contributors[user].profile = data;
applyProfile(row, data);
})
.catch(() => {});
}, index * 100);
});
}
function applyProfile(row, data) {
const locCell = row.querySelector('.loc-cell');
const compCell = row.querySelector('.company-cell');
const linksCell = row.querySelector('.links-cell');
if (data.location && locCell) {
locCell.dataset.sort = data.location;
locCell.innerHTML = `<svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> ${data.location}`;
}
if (data.company && compCell) {
compCell.dataset.sort = data.company;
compCell.innerHTML = `<svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg> ${data.company}`;
}
if (linksCell) {
const links = [];
if (data.blog) {
links.push(`<a href="${data.blog}" target="_blank" rel="noopener noreferrer" title="Website"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></a>`);
}
if (data.twitter_username) {
links.push(`<a href="https://twitter.com/${data.twitter_username}" target="_blank" rel="noopener noreferrer" title="Twitter"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"/></svg></a>`);
}
if (data.email) {
links.push(`<a href="mailto:${data.email}" title="Email"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg></a>`);
}
linksCell.innerHTML = links.join(' ');
}
}
function openContributorDrawer(user) {
const info = contributors[user];
if (!info) return;
const drawer = document.getElementById('drawer');
const content = document.getElementById('drawer-content');
content.innerHTML = '';
const header = document.createElement('div');
header.className = 'contrib-header';
const avatar = document.createElement('img');
avatar.className = 'contrib-avatar';
avatar.src = `https://github.com/${user}.png?size=80`;
avatar.alt = `${user} avatar`;
header.appendChild(avatar);
const infoBox = document.createElement('div');
const nameEl = document.createElement('div');
nameEl.className = 'contrib-name';
nameEl.textContent = user;
infoBox.appendChild(nameEl);
const userLink = document.createElement('a');
userLink.href = `https://github.com/${user}`;
userLink.target = '_blank';
userLink.rel = 'noopener noreferrer';
userLink.className = 'contrib-username';
userLink.textContent = '@' + user;
infoBox.appendChild(userLink);
const stats = document.createElement('div');
stats.className = 'contrib-stats';
const totalComments = info.comments ? Object.values(info.comments).reduce((a,b) => a + b.length, 0) : 0;
stats.innerHTML = `
<div class="contrib-stat"><span class="contrib-stat-value">${info.repos.length}</span>Repos</div>
<div class="contrib-stat"><span class="contrib-stat-value">${info.entries.length}</span>Entries</div>
<div class="contrib-stat"><span class="contrib-stat-value">${totalComments}</span>Comments</div>
`;
infoBox.appendChild(stats);
const metaList = document.createElement('ul');
metaList.className = 'profile-meta';
infoBox.appendChild(metaList);
header.appendChild(infoBox);
content.appendChild(header);
const existing = info.profile;
const load = existing ? Promise.resolve(existing) : fetch(`https://api.github.com/users/${user}`, {
headers: {
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
}).then(r => r.ok ? r.json() : null);
load.then(async data => {
if (!data) return;
if (!existing) info.profile = data;
if (data.site_admin) {
const badge = document.createElement('span');
badge.className = 'verified-badge';
badge.textContent = '✓';
nameEl.appendChild(badge);
const row = document.querySelector(`tr[data-contributor="${user}"] td:nth-child(2)`);
if (row && !row.querySelector('.verified-badge')) {
const badgeClone = badge.cloneNode(true);
row.appendChild(badgeClone);
}
}
const items = [];
if (typeof data.followers === 'number' && typeof data.following === 'number') {
const fmt = n => n >= 1000 ? (n/1000).toFixed(1).replace(/\.0$/, '') + 'k' : n;
items.push(`${fmt(data.followers)} followers \u2022 ${fmt(data.following)} following`);
}
if (data.location) {
try {
if (!window._tzList) {
const l = await fetch('https://worldtimeapi.org/api/timezone');
window._tzList = l.ok ? await l.json() : null;
}
if (window._tzList) {
const zone = window._tzList.find(z => z.toLowerCase().includes(data.location.toLowerCase()));
if (zone) {
const tzRes = await fetch(`https://worldtimeapi.org/api/timezone/${encodeURIComponent(zone)}`);
if (tzRes.ok) {
const tz = await tzRes.json();
const dt = new Date(tz.datetime);
const time = dt.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
const offset = tz.utc_offset.replace(':00','');
items.push(`<svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> ${data.location.split(',')[0]} \u2013 ${time} (GMT${offset})`);
}
}
}
} catch (e) {}
}
if (data.company) {
items.push(`<svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg> ${data.company}`);
}
const links = [];
if (data.blog) {
links.push(`<a href="${data.blog}" target="_blank" rel="noopener noreferrer" title="Website"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></a>`);
}
if (data.twitter_username) {
links.push(`<a href="https://twitter.com/${data.twitter_username}" target="_blank" rel="noopener noreferrer" title="Twitter"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"/></svg></a>`);
}
if (data.email) {
links.push(`<a href="mailto:${data.email}" title="Email"><svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg></a>`);
}
if (links.length) {
items.push(links.join(' '));
}
items.forEach(html => {
const li = document.createElement('li');
li.innerHTML = html;
metaList.appendChild(li);
});
})
.catch(() => {});
function createSection(title, count) {
const details = document.createElement('details');
details.className = 'contrib-section';
const summary = document.createElement('summary');
summary.textContent = `${title} (${count})`;
details.appendChild(summary);
return details;
}
const reposDetails = createSection('Repositories', info.repos.length);
const repoList = document.createElement('ul');
info.repos.forEach(r => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `/?repos=${encodeURIComponent(r)}`;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.textContent = r;
li.appendChild(a);
repoList.appendChild(li);
});
reposDetails.appendChild(repoList);
content.appendChild(reposDetails);
const entriesDetails = createSection('Reviewer Entries', info.entries.length);
const entriesList = document.createElement('ul');
info.entries.forEach(e => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `/reviewers/${e.slug}/`;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.textContent = e.title;
li.appendChild(a);
entriesList.appendChild(li);
});
entriesDetails.appendChild(entriesList);
content.appendChild(entriesDetails);
const commentsDetails = createSection('Comments', info.comments ? Object.keys(info.comments).length : 0);
if (info.comments) {
Object.keys(info.comments).forEach(slug => {
const group = document.createElement('details');
const sum = document.createElement('summary');
sum.textContent = `${slug} (${info.comments[slug].length})`;
group.appendChild(sum);
const thread = document.createElement('div');
thread.className = 'chat-thread';
info.comments[slug].forEach(text => {
const msg = document.createElement('div');
msg.className = 'chat-message';
const bubble = document.createElement('div');
bubble.className = 'chat-bubble';
const body = document.createElement('div');
body.className = 'chat-body';
body.innerHTML = escapeHtml(text).replace(/\n/g, '<br>');
bubble.appendChild(body);
msg.appendChild(bubble);
thread.appendChild(msg);
});
group.appendChild(thread);
commentsDetails.appendChild(group);
});
}
content.appendChild(commentsDetails);
drawer.classList.add('open');
history.replaceState(null, '', `#${user}`);
}
function closeDrawer() {
const drawer = document.getElementById('drawer');
drawer.classList.remove('open');
history.replaceState(null, '', location.pathname);
}
window.addEventListener('hashchange', () => {
const h = location.hash.slice(1);
if (!h) {
closeDrawer();
} else if (contributors[h]) {
openContributorDrawer(h);
}
});
</script>