- Focus traps for fediverse modal and lightbox dialogs (C3, C4) - Search widget input label (C5) - Blogroll widget tab ARIA semantics (C6) - Footer social links "opens in new tab" warning (S5) - Reply context aria-label on aside (S8) - Photo alt text fallback includes post title (S10) - Post categories use list markup (M3) - Funkwhale now-playing bars aria-hidden (M7) - TOC uses static Tailwind classes instead of dynamic (M9) - Footer headings use proper aria heading roles (M15) - Header anchor opacity increased to 1 for contrast (M18) - Custom HTML widgets labeled as regions (M19) - Empty collection placeholder role=status (M22) - GitHub widget loading state announced (N5) - Subscribe icon contrast improved (m1) - All Permalink links have aria-label with post context (m3) - Podroll audio element aria-label (m4) - Obfuscated email link aria-label (m6) - Fediverse follow button uses aria-label (M10) Score: 53.6% → 92.9% (26/28 WCAG criteria passing) Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
222 lines
13 KiB
Plaintext
222 lines
13 KiB
Plaintext
{# GitHub Activity Widget - Tabbed Commits/Repos/Featured/PRs with live API data #}
|
|
<is-land on:visible>
|
|
<div class="widget" x-data="githubWidget('{{ site.feeds.github }}')" x-init="init()">
|
|
<h3 class="widget-title flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
GitHub
|
|
</h3>
|
|
|
|
{# Tab buttons — order: Commits, Repos, Featured, PRs #}
|
|
<div class="flex gap-1 mb-4 border-b border-surface-200 dark:border-surface-700" role="tablist" aria-label="GitHub activity">
|
|
<button
|
|
@click="activeTab = 'commits'"
|
|
:class="activeTab === 'commits' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
|
|
:aria-selected="(activeTab === 'commits').toString()"
|
|
role="tab" id="gh-tab-commits" aria-controls="gh-panel-commits"
|
|
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
|
|
>
|
|
Commits
|
|
</button>
|
|
<button
|
|
@click="activeTab = 'repos'"
|
|
:class="activeTab === 'repos' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
|
|
:aria-selected="(activeTab === 'repos').toString()"
|
|
role="tab" id="gh-tab-repos" aria-controls="gh-panel-repos"
|
|
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
|
|
>
|
|
Repos
|
|
</button>
|
|
<button
|
|
@click="activeTab = 'featured'"
|
|
:class="activeTab === 'featured' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
|
|
:aria-selected="(activeTab === 'featured').toString()"
|
|
role="tab" id="gh-tab-featured" aria-controls="gh-panel-featured"
|
|
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
|
|
>
|
|
Featured
|
|
</button>
|
|
<button
|
|
@click="activeTab = 'prs'"
|
|
:class="activeTab === 'prs' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
|
|
:aria-selected="(activeTab === 'prs').toString()"
|
|
role="tab" id="gh-tab-prs" aria-controls="gh-panel-prs"
|
|
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
|
|
>
|
|
PRs
|
|
</button>
|
|
</div>
|
|
|
|
{# Tab content — fixed height container to prevent layout shift #}
|
|
<div class="h-[420px] overflow-y-auto">
|
|
|
|
{# Loading state #}
|
|
<div x-show="loading" class="text-sm text-surface-600 dark:text-surface-400 py-4 text-center" role="status" aria-live="polite">
|
|
Loading...
|
|
</div>
|
|
|
|
{# Commits Tab #}
|
|
<div x-show="activeTab === 'commits' && !loading" x-cloak role="tabpanel" id="gh-panel-commits" aria-labelledby="gh-tab-commits">
|
|
<ul x-show="commits.length > 0" class="space-y-3">
|
|
<template x-for="commit in commits.slice(0, 5)" :key="commit.sha">
|
|
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
|
|
<a :href="commit.url" target="_blank" rel="noopener" class="block group">
|
|
<p class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-surface-900 dark:group-hover:text-surface-100 transition-colors line-clamp-2" x-text="commit.message"></p>
|
|
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
|
|
<code class="text-xs font-mono bg-surface-100 dark:bg-surface-800 px-1 py-0.5 rounded" x-text="commit.sha"></code>
|
|
<span class="truncate" x-text="commit.repo?.split('/')[1] || commit.repo"></span>
|
|
<span class="font-mono" x-text="formatDate(commit.date)"></span>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<div x-show="commits.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No recent commits.</div>
|
|
</div>
|
|
|
|
{# Repos Tab #}
|
|
<div x-show="activeTab === 'repos' && !loading" x-cloak role="tabpanel" id="gh-panel-repos" aria-labelledby="gh-tab-repos">
|
|
<ul x-show="repos.length > 0" class="space-y-3">
|
|
<template x-for="repo in repos.slice(0, 5)" :key="repo.name">
|
|
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
|
|
<a :href="repo.html_url" target="_blank" rel="noopener" class="block group">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-medium text-sm text-surface-700 dark:text-surface-300 group-hover:underline truncate" x-text="repo.name"></span>
|
|
<span x-show="repo.language" class="text-xs px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400 flex-shrink-0" x-text="repo.language"></span>
|
|
</div>
|
|
<p x-show="repo.description" class="text-xs text-surface-600 dark:text-surface-400 mt-1 line-clamp-2" x-text="repo.description"></p>
|
|
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
|
|
<span x-show="repo.stargazers_count > 0" class="flex items-center gap-1">
|
|
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
|
|
<span x-text="repo.stargazers_count"></span>
|
|
</span>
|
|
<span class="font-mono" x-text="formatDate(repo.updated_at)"></span>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<div x-show="repos.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No repositories found.</div>
|
|
</div>
|
|
|
|
{# Featured Tab #}
|
|
<div x-show="activeTab === 'featured' && !loading" x-cloak role="tabpanel" id="gh-panel-featured" aria-labelledby="gh-tab-featured">
|
|
<ul x-show="featured.length > 0" class="space-y-3">
|
|
<template x-for="repo in featured.slice(0, 5)" :key="repo.fullName || repo.name">
|
|
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
|
|
<a :href="repo.url" target="_blank" rel="noopener" class="block group">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-medium text-sm text-surface-700 dark:text-surface-300 group-hover:underline truncate" x-text="repo.name"></span>
|
|
<span x-show="repo.language" class="text-xs px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400 flex-shrink-0" x-text="repo.language"></span>
|
|
</div>
|
|
<p x-show="repo.description" class="text-xs text-surface-600 dark:text-surface-400 mt-1 line-clamp-2" x-text="repo.description"></p>
|
|
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
|
|
<span x-show="repo.stars > 0" class="flex items-center gap-1">
|
|
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
|
|
<span x-text="repo.stars"></span>
|
|
</span>
|
|
<span x-show="repo.forks > 0" class="flex items-center gap-1">
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"/></svg>
|
|
<span x-text="repo.forks"></span>
|
|
</span>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<div x-show="featured.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No featured projects.</div>
|
|
</div>
|
|
|
|
{# PRs Tab #}
|
|
<div x-show="activeTab === 'prs' && !loading" x-cloak role="tabpanel" id="gh-panel-prs" aria-labelledby="gh-tab-prs">
|
|
<ul x-show="contributions.length > 0" class="space-y-3">
|
|
<template x-for="item in contributions.slice(0, 5)" :key="item.url">
|
|
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
|
|
<a :href="item.url" target="_blank" rel="noopener" class="block group">
|
|
<div class="flex items-center gap-2">
|
|
<span
|
|
class="flex-shrink-0 w-4 h-4 rounded-full flex items-center justify-center"
|
|
:class="item.type === 'pr' ? 'bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400' : 'bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-400'"
|
|
>
|
|
<svg x-show="item.type === 'pr'" class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
|
<svg x-show="item.type === 'issue'" class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2"/><line x1="12" y1="8" x2="12" y2="12" stroke-width="2"/><line x1="12" y1="16" x2="12.01" y2="16" stroke-width="2"/></svg>
|
|
</span>
|
|
<span class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-surface-900 dark:group-hover:text-surface-100 transition-colors truncate" x-text="item.title"></span>
|
|
</div>
|
|
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-600 dark:text-surface-400 pl-6">
|
|
<span x-text="item.repo?.split('/')[1] || item.repo"></span>
|
|
<span x-show="item.number" x-text="'#' + item.number"></span>
|
|
<span class="font-mono" x-text="formatDate(item.date)"></span>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
<div x-show="contributions.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No recent PRs or issues.</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{# Footer link #}
|
|
{% if site.feeds.github %}
|
|
<a href="https://github.com/{{ site.feeds.github }}" target="_blank" rel="noopener" class="text-sm text-accent-600 dark:text-accent-400 hover:underline mt-3 inline-flex items-center gap-1">
|
|
View on GitHub
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
function githubWidget(username) {
|
|
return {
|
|
activeTab: 'commits',
|
|
loading: true,
|
|
commits: [],
|
|
repos: [],
|
|
featured: [],
|
|
contributions: [],
|
|
|
|
async init() {
|
|
try {
|
|
const fetches = [
|
|
fetch('/githubapi/api/commits').then(r => r.ok ? r.json() : null).catch(() => null),
|
|
fetch('/githubapi/api/featured').then(r => r.ok ? r.json() : null).catch(() => null),
|
|
fetch('/githubapi/api/contributions').then(r => r.ok ? r.json() : null).catch(() => null),
|
|
];
|
|
if (username) {
|
|
fetches.push(
|
|
fetch('https://api.github.com/users/' + username + '/repos?sort=updated&per_page=10&type=owner', {
|
|
headers: { 'Accept': 'application/vnd.github.v3+json' }
|
|
}).then(r => r.ok ? r.json() : null).catch(() => null)
|
|
);
|
|
}
|
|
const [commitsRes, featuredRes, contribRes, reposRes] = await Promise.all(fetches);
|
|
this.commits = commitsRes?.commits || [];
|
|
this.featured = featuredRes?.featured || [];
|
|
this.contributions = contribRes?.contributions || [];
|
|
this.repos = (reposRes || []).filter(r => !r.fork && !r.private);
|
|
} catch (err) {
|
|
console.error('GitHub widget error:', err);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '';
|
|
const d = new Date(dateStr);
|
|
const now = new Date();
|
|
const diffMs = now - d;
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
if (diffHours < 1) return 'just now';
|
|
if (diffHours < 24) return diffHours + 'h ago';
|
|
if (diffDays < 7) return diffDays + 'd ago';
|
|
return d.toLocaleDateString('en', { month: 'short', day: 'numeric' });
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
</is-land>
|