mirror of
https://github.com/samber/awesome-prometheus-alerts.git
synced 2026-06-20 16:46:37 +08:00
feat: add GitHub star nudges across the site
- Prepend attribution comment to "Copy all" exporter clipboard
- Show inline ⭐ Star nudge on individual rule copy (3s, dismisses automatically)
- Change StatsBar stars label to "engineers starred" for social proof
- Add milestone progress bar toward 10k stars in StatsBar
- Fix header/StatsBar showing "0" when SSR GitHub API fetch fails (use "—" placeholder)
This commit is contained in:
parent
d38511d7cb
commit
bb055773b4
5 changed files with 112 additions and 29 deletions
|
|
@ -1,31 +1,53 @@
|
|||
---
|
||||
import { GITHUB_URL } from '../data/site';
|
||||
|
||||
interface Props {
|
||||
targetId: string;
|
||||
label?: string;
|
||||
variant?: 'icon' | 'text';
|
||||
class?: string;
|
||||
withAttribution?: boolean;
|
||||
nudge?: boolean;
|
||||
}
|
||||
|
||||
const { targetId, label = 'Copy', variant = 'icon', class: extraClass = '' } = Astro.props;
|
||||
const { targetId, label = 'Copy', variant = 'icon', class: extraClass = '', withAttribution = false, nudge = false } = Astro.props;
|
||||
const btnId = `copy-btn-${targetId}`;
|
||||
const nudgeId = `star-nudge-${btnId}`;
|
||||
---
|
||||
|
||||
{variant === 'icon' ? (
|
||||
<button
|
||||
id={btnId}
|
||||
data-copy-target={targetId}
|
||||
aria-label="Copy to clipboard"
|
||||
class={`copy-btn inline-flex items-center gap-1.5 px-2 py-1 text-xs rounded text-slate-400 dark:text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors ${extraClass}`}
|
||||
>
|
||||
<svg class="copy-icon w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<svg class="check-icon w-3.5 h-3.5 hidden text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span class="copy-label sr-only">Copy</span>
|
||||
<span class="copied-label hidden text-green-500 not-sr-only text-xs">Copied!</span>
|
||||
</button>
|
||||
<span class={`inline-flex items-center gap-1 ${extraClass}`}>
|
||||
<button
|
||||
id={btnId}
|
||||
data-copy-target={targetId}
|
||||
aria-label="Copy to clipboard"
|
||||
class="copy-btn inline-flex items-center gap-1.5 px-2 py-1 text-xs rounded text-slate-400 dark:text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors"
|
||||
>
|
||||
<svg class="copy-icon w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<svg class="check-icon w-3.5 h-3.5 hidden text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span class="copy-label sr-only">Copy</span>
|
||||
<span class="copied-label hidden text-green-500 not-sr-only text-xs">Copied!</span>
|
||||
</button>
|
||||
{nudge && (
|
||||
<a
|
||||
id={nudgeId}
|
||||
href={GITHUB_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hidden items-center gap-0.5 text-xs text-yellow-500 hover:text-yellow-600 dark:text-yellow-400 dark:hover:text-yellow-300 transition-colors whitespace-nowrap"
|
||||
aria-label="Star on GitHub"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||
</svg>
|
||||
Star
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
id={btnId}
|
||||
|
|
@ -43,7 +65,7 @@ const btnId = `copy-btn-${targetId}`;
|
|||
</button>
|
||||
)}
|
||||
|
||||
<script define:vars={{ btnId }}>
|
||||
<script define:vars={{ btnId, nudgeId, withAttribution, nudge, GITHUB_URL }}>
|
||||
const btn = document.getElementById(btnId);
|
||||
if (!(btn instanceof HTMLButtonElement)) return;
|
||||
if (btn.dataset.copyBound === 'true') return;
|
||||
|
|
@ -55,13 +77,17 @@ const btnId = `copy-btn-${targetId}`;
|
|||
const target = document.getElementById(targetId);
|
||||
if (!target) return;
|
||||
|
||||
const text = target.textContent ?? '';
|
||||
let text = target.textContent ?? '';
|
||||
text = withAttribution
|
||||
? `# Source: ${GITHUB_URL}\n${text.trim()}`
|
||||
: text.trim();
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text.trim());
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch {
|
||||
// Fallback for older browsers
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text.trim();
|
||||
ta.value = text;
|
||||
ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
|
|
@ -87,6 +113,19 @@ const btnId = `copy-btn-${targetId}`;
|
|||
copiedLabel?.classList.add('hidden');
|
||||
}, 2000);
|
||||
|
||||
// Inline star nudge
|
||||
if (nudge) {
|
||||
const nudgeEl = document.getElementById(nudgeId);
|
||||
if (nudgeEl) {
|
||||
nudgeEl.classList.remove('hidden');
|
||||
nudgeEl.classList.add('inline-flex');
|
||||
setTimeout(() => {
|
||||
nudgeEl.classList.add('hidden');
|
||||
nudgeEl.classList.remove('inline-flex');
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('copy-success'));
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const exporterPrefix = showExporterNumber
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<CopyButton targetId={allRulesId} label="Copy all" variant="text" />
|
||||
<CopyButton targetId={allRulesId} label="Copy all" variant="text" withAttribution={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ try {
|
|||
}
|
||||
} catch {}
|
||||
|
||||
const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars);
|
||||
const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars)) : '—';
|
||||
---
|
||||
|
||||
<!-- Main header -->
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const codeId = `code-${anchorId}`;
|
|||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
</a>
|
||||
<CopyButton targetId={codeId} variant="icon" />
|
||||
<CopyButton targetId={codeId} variant="icon" nudge={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
---
|
||||
import { getTotalRuleCount, getTotalExporterCount, data } from '../data/rules';
|
||||
import { GITHUB_API_REPO_URL } from '../data/site';
|
||||
import { GITHUB_API_REPO_URL, GITHUB_URL } from '../data/site';
|
||||
|
||||
const totalRules = getTotalRuleCount();
|
||||
const totalExporters = getTotalExporterCount();
|
||||
const totalGroups = data.groups.length;
|
||||
|
||||
const STAR_MILESTONE = 10000;
|
||||
const MILESTONE_LABEL = '10k';
|
||||
|
||||
let stars = 0;
|
||||
try {
|
||||
const res = await fetch(GITHUB_API_REPO_URL, {
|
||||
|
|
@ -17,7 +20,10 @@ try {
|
|||
}
|
||||
} catch {}
|
||||
|
||||
const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars);
|
||||
const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars)) : '—';
|
||||
const progressPct = stars > 0 ? Math.min(100, (stars / STAR_MILESTONE) * 100).toFixed(1) : '0';
|
||||
const starsFormatted = stars > 0 ? stars.toLocaleString('en') : '';
|
||||
const milestoneFormatted = STAR_MILESTONE.toLocaleString('en');
|
||||
---
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-6 sm:gap-10 py-4 text-center">
|
||||
|
|
@ -33,33 +39,70 @@ const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(star
|
|||
<div class="text-2xl font-bold text-brand dark:text-brand-dark">{totalGroups}</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">categories</div>
|
||||
</div>
|
||||
<a href="https://github.com/samber/awesome-prometheus-alerts" target="_blank" rel="noopener noreferrer" class="group">
|
||||
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" class="group">
|
||||
<div class="text-2xl font-bold text-brand dark:text-brand-dark flex items-center justify-center gap-1">
|
||||
<svg class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
<span id="statsbar-stars">{starsLabel}</span>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5 group-hover:text-brand dark:group-hover:text-brand-dark transition-colors">GitHub stars</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5 group-hover:text-brand dark:group-hover:text-brand-dark transition-colors">engineers starred</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!-- Milestone progress bar -->
|
||||
<div id="star-milestone-row" class={`mt-2 px-4 ${stars === 0 ? 'opacity-0' : ''}`}>
|
||||
<div class="max-w-xs mx-auto">
|
||||
<div class="flex items-center justify-between text-xs text-slate-400 dark:text-slate-500 mb-1">
|
||||
<span>
|
||||
<span id="statsbar-stars-count">{starsFormatted}</span><span id="statsbar-milestone-suffix">{stars > 0 ? ` / ${milestoneFormatted} — help us reach ${MILESTONE_LABEL}!` : ''}</span>
|
||||
</span>
|
||||
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" class="text-yellow-500 hover:text-yellow-600 dark:text-yellow-400 dark:hover:text-yellow-300 font-medium transition-colors flex items-center gap-0.5 ml-2 flex-shrink-0">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||
</svg>
|
||||
Star it
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-slate-100 dark:bg-slate-800 rounded-full h-1.5">
|
||||
<div
|
||||
id="statsbar-star-bar"
|
||||
class="bg-yellow-400 dark:bg-yellow-500 h-1.5 rounded-full transition-all duration-700"
|
||||
style={`width: ${progressPct}%`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script define:vars={{ STAR_MILESTONE, MILESTONE_LABEL }}>
|
||||
const starsEl = document.getElementById('statsbar-stars');
|
||||
const starsCountEl = document.getElementById('statsbar-stars-count');
|
||||
const milestoneSuffixEl = document.getElementById('statsbar-milestone-suffix');
|
||||
const barEl = document.getElementById('statsbar-star-bar');
|
||||
const milestoneRow = document.getElementById('star-milestone-row');
|
||||
|
||||
if (starsEl) {
|
||||
const CACHE_KEY = 'gh_stars_apa';
|
||||
const CACHE_TTL = 3600 * 1000;
|
||||
|
||||
function fmt(n: number): string {
|
||||
function fmt(n) {
|
||||
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
||||
}
|
||||
|
||||
function updateMilestone(n) {
|
||||
if (starsCountEl) starsCountEl.textContent = n.toLocaleString('en');
|
||||
if (milestoneSuffixEl) milestoneSuffixEl.textContent = ` / ${STAR_MILESTONE.toLocaleString('en')} — help us reach ${MILESTONE_LABEL}!`;
|
||||
if (barEl) barEl.style.width = `${Math.min(100, (n / STAR_MILESTONE) * 100).toFixed(1)}%`;
|
||||
if (milestoneRow) milestoneRow.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
const cached = sessionStorage.getItem(CACHE_KEY);
|
||||
let isFresh = false;
|
||||
if (cached) {
|
||||
const { value, ts } = JSON.parse(cached);
|
||||
if (Date.now() - ts < CACHE_TTL) {
|
||||
starsEl.textContent = fmt(value);
|
||||
updateMilestone(value);
|
||||
isFresh = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +114,7 @@ const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(star
|
|||
.then((data) => {
|
||||
if (data?.stargazers_count) {
|
||||
starsEl.textContent = fmt(data.stargazers_count);
|
||||
updateMilestone(data.stargazers_count);
|
||||
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ value: data.stargazers_count, ts: Date.now() }));
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue